[clang] 08fb771 - Revert "[DebugInfo] Ignore undefined constexpr constructors in constructor homing." (#221566)

via cfe-commits cfe-commits at lists.llvm.org
Sun Sep 6 13:36:53 PDT 2026


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) <noreply at anthropic.com>

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 {


        


More information about the cfe-commits mailing list