Author: Avi Kivity Date: 2026-09-06T13:36:48-07:00 New Revision: 08fb771d71de39421d1e9e200c8b8a0b06d74220
URL: https://github.com/llvm/llvm-project/commit/08fb771d71de39421d1e9e200c8b8a0b06d74220 DIFF: https://github.com/llvm/llvm-project/commit/08fb771d71de39421d1e9e200c8b8a0b06d74220.diff LOG: Revert "[DebugInfo] Ignore undefined constexpr constructors in constructor homing." (#221566) This reverts commit https://github.com/llvm/llvm-project/commit/6ba0802b406e0963720a698702b9136578dde149 (https://github.com/llvm/llvm-project/pull/218165). canUseCtorHoming() used to bail out on hasConstexprNonCopyMoveConstructor(). That commit narrowed the exemption to *defined* constexpr constructors, on the assumption that a constructor which is not defined here will be defined - and will therefore emit the type's debug info - in whichever translation unit constructs the object. That assumption does not hold when the constructor is never invoked at all. A constructor of a class template specialization is only instantiated, and hence only defined, where it is used. If nothing ever constructs the specialization, its constructors are defined in no translation unit, and so nothing anywhere homes the type - even though the type still has to be complete in order to read a member. (The reverted commit's stated rationale, that "declared constexpr constructors are not callable in a TU that doesn't see their definition", does not hold either: such a constructor can be called outside a constant expression, with the definition supplied by another translation unit, and clang emits an ordinary external call for it. But that is not what breaks here.) Unions of layout-compatible types are where this bites. The common initial sequence rule ([class.mem.general]p26, 9.2/19 in C++11) lets a program write through one union member and read through another where the two share a common initial sequence, and absl::container_internal::map_slot_type is built on it: it aliases std::pair<const K, V> and std::pair<K, V> so that flat_hash_map<K, V> can hand out pair<const K, V> references while still moving keys efficiently. Slots are created and destroyed through the mutable pair and only read through the const-key one, so a std::pair<const K, V> is never constructed, none of its constructors is ever instantiated, and its type is emitted nowhere. Reduced, and linkable - the following compiles, links and returns 0: template <class A, class B> struct mypair { A first; B second; constexpr mypair() : first(), second() {} constexpr mypair(const A& a, const B& b) : first(a), second(b) {} }; template <class K, class V> union map_slot_type { map_slot_type() {} ~map_slot_type() {} using value_type = mypair<const K, V>; // read, never constructed using mutable_value_type = mypair<K, V>; // constructed value_type value; mutable_value_type mutable_value; K key; }; map_slot_type<unsigned long, void*> slot; int marker; int main() { slot.mutable_value = mypair<unsigned long, void*>(42, &marker); return (slot.value.first == 42 && slot.value.second == &marker) ? 0 : 1; } mypair<unsigned long, void*> is constructed and keeps its debug info. mypair<const unsigned long, void*> is only read, and is emitted as a declaration in every translation unit, so a debugger cannot show first and second: clang 22.1.8 DW_AT_byte_size (0x10) clang 24.0.0git DW_AT_declaration (true) This is how it was found - gdb reporting "<incomplete type>" for std::pair<const size_t, ...> in ScyllaDB, which broke the flat_hash_map readers in its gdb scripts. Keep the test coverage the reverted commit added for the cases whose expected output is unchanged, restore the DeclaredConstexpr expectation to full debug info, and add the aliased-pair case above. Fixes https://github.com/llvm/llvm-project/issues/221560 Co-authored-by: Claude Opus 5 (1M context) <[email protected]> Added: Modified: clang/lib/CodeGen/CGDebugInfo.cpp clang/test/DebugInfo/CXX/limited-ctor.cpp Removed: ################################################################################ diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index 15080e5e47b3e..02864621d60a3 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -3239,17 +3239,11 @@ static bool canUseCtorHoming(const CXXRecordDecl *RD) { if (isClassOrMethodDLLImport(RD)) return false; - if (RD->isLambda() || RD->isAggregate() || RD->hasTrivialDefaultConstructor()) + if (RD->isLambda() || RD->isAggregate() || + RD->hasTrivialDefaultConstructor() || + RD->hasConstexprNonCopyMoveConstructor()) return false; - // Skip this optimization if the class has an implicit constexpr default - // constructor, since those constructors can be invoked without emitting type - // information for the constructor. - if (RD->needsImplicitDefaultConstructor() && - RD->defaultedDefaultConstructorIsConstexpr()) - return false; - - bool HasNonDeletedCtor = false; for (const CXXConstructorDecl *Ctor : RD->ctors()) { if (Ctor->isCopyOrMoveConstructor()) continue; @@ -3260,15 +3254,11 @@ static bool canUseCtorHoming(const CXXRecordDecl *RD) { // copy/move constructor, which does not enable homing. if (CtorDef->isDelegatingConstructor()) continue; - // Skip this optimization if we see a defined constexpr constructor, which - // can be invoked without emitting type info. - if (Ctor->isConstexpr() && !Ctor->isDeleted()) - return false; } if (!Ctor->isDeleted()) - HasNonDeletedCtor = true; + return true; } - return HasNonDeletedCtor; + return false; } static bool shouldOmitDefinition(llvm::codegenoptions::DebugInfoKind DebugKind, diff --git a/clang/test/DebugInfo/CXX/limited-ctor.cpp b/clang/test/DebugInfo/CXX/limited-ctor.cpp index 613faa11ffad8..e820c0703df4f 100644 --- a/clang/test/DebugInfo/CXX/limited-ctor.cpp +++ b/clang/test/DebugInfo/CXX/limited-ctor.cpp @@ -27,12 +27,45 @@ struct E { constexpr E(){}; } TestE; -// Declared but not defined constexpr constructor should not emit full debug info.. -// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "DeclaredConstexpr"{{.*}}flags: DIFlagFwdDecl +// Restored by this revert: a constexpr constructor that is only declared keeps +// the class exempt from constructor homing. See Aliased below for a case where +// narrowing the exemption to defined constructors homes the type nowhere. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "DeclaredConstexpr"{{.*}}DIFlagTypePassByValue struct DeclaredConstexpr { constexpr DeclaredConstexpr(); } TestDeclaredConstexpr; +// A constructor of a class template specialization is only instantiated, and +// so only defined, where it is used. Nothing constructs Aliased<const int, int> +// here - it is only read, through the common initial sequence it shares with +// Aliased<int, int> - so its constructor is defined in no translation unit and +// nothing anywhere homes the type, even though the type must be complete to +// read the member. Constructing through the mutable alternative and reading +// through the const-qualified one is what +// absl::container_internal::map_slot_type does with std::pair. +// +// See https://timsong-cpp.github.io/cppwp/n3337/class.mem#19 for +// the rule that allows an object to be constructed by a constructor +// of one type and read via another type. +// +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "Aliased<int, int>"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "Aliased<const int, int>"{{.*}}DIFlagTypePassByValue +template <class A, class B> struct Aliased { + A first; + B second; + constexpr Aliased(const A &a, const B &b) : first(a), second(b) {} +}; +union AliasedSlot { + Aliased<const int, int> value; + Aliased<int, int> mutable_value; + AliasedSlot() {} + ~AliasedSlot() {} +} TestAliasedSlot; +int ReadAliasedSlot() { + TestAliasedSlot.mutable_value = Aliased<int, int>(1, 2); + return TestAliasedSlot.value.first; +} + // Defined out-of-line constexpr constructor should emit full debug info. // CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "OutOfLineConstexpr"{{.*}}DIFlagTypePassByValue struct OutOfLineConstexpr { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
