[clang] cbe9891 - [Clang] fix bad codegen from constexpr structured bindings (#186594)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 26 21:07:27 PDT 2026
Author: Takashi Idobe
Date: 2026-03-27T12:07:23+08:00
New Revision: cbe9891b44d3d1c15bd8f632d6d84e486751e530
URL: https://github.com/llvm/llvm-project/commit/cbe9891b44d3d1c15bd8f632d6d84e486751e530
DIFF: https://github.com/llvm/llvm-project/commit/cbe9891b44d3d1c15bd8f632d6d84e486751e530.diff
LOG: [Clang] fix bad codegen from constexpr structured bindings (#186594)
Resolves: https://github.com/llvm/llvm-project/issues/164150
C++26 allows for constexpr packs in structured bindings. This is a new
feature (the code doesn't compile on lower the -std=c++26) and so was
previously unhandled in clang.
This makes clang aware of packs and handle them as one constant unit
instead of materializing them as separate mutable reference temporaries
allowing llvm to optimize them.
This turns the example code from the issue into this as you would expect
without compiling for zen 5 (the good codegen described).
```asm
movq %rdi, %rax
movups (%rsi), %xmm0
movups %xmm0, (%rdi)
movups (%rdx), %xmm0
movups %xmm0, 16(%rdi)
retq
```
Added:
clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp
clang/test/CodeGenCXX/reference-temporary-mutable.cpp
clang/test/CodeGenCXX/reference-temporary-subobject.cpp
Modified:
clang/lib/CodeGen/CodeGenModule.cpp
clang/test/CodeGenCXX/const-init-cxx11.cpp
clang/test/CodeGenCXX/reference-temporary-ms.cpp
clang/test/CodeGenCXX/temporaries.cpp
Removed:
################################################################################
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index b4a24bcf03d77..94a52f51118a7 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7391,10 +7391,15 @@ ConstantAddress CodeGenModule::GetAddrOfGlobalTemporary(
E->getStorageDuration() == SD_Thread) && "not a global temporary");
const auto *VD = cast<VarDecl>(E->getExtendingDecl());
- // If we're not materializing a subobject of the temporary, keep the
- // cv-qualifiers from the type of the MaterializeTemporaryExpr.
+ // Use the MaterializeTemporaryExpr's type if it has the same unqualified
+ // base type as Init. This preserves cv-qualifiers (e.g. const from a
+ // constexpr or const-ref binding) that skipRValueSubobjectAdjustments may
+ // have dropped via NoOp casts, while correctly falling back to Init's type
+ // when a real subobject adjustment changed the type (e.g. member access or
+ // base-class cast in C++98), where E->getType() reflects the reference type,
+ // not the actual storage type.
QualType MaterializedType = Init->getType();
- if (Init == E->getSubExpr())
+ if (getContext().hasSameUnqualifiedType(E->getType(), MaterializedType))
MaterializedType = E->getType();
CharUnits Align = getContext().getTypeAlignInChars(MaterializedType);
diff --git a/clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp b/clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp
new file mode 100644
index 0000000000000..ce81f7d8d026e
--- /dev/null
+++ b/clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+namespace std {
+using size_t = decltype(sizeof(0));
+template <typename T> struct tuple_size;
+template <size_t I, typename T> struct tuple_element;
+
+template <typename T> struct tuple_size<const T> : tuple_size<T> {};
+
+template <size_t I, typename T> struct tuple_element<I, const T> {
+ using type = const typename tuple_element<I, T>::type;
+};
+} // namespace std
+
+using u8 = unsigned char;
+
+template <u8 N> struct Range {
+ template <std::size_t I>
+ constexpr friend u8 get(Range) noexcept {
+ return I;
+ }
+};
+
+namespace std {
+template <u8 N> struct tuple_size<Range<N>> {
+ static constexpr std::size_t value = N;
+};
+template <std::size_t I, u8 N> struct tuple_element<I, Range<N>> {
+ using type = u8;
+};
+} // namespace std
+
+const u8 &f() {
+ static constexpr auto [I] = Range<1>();
+ return I;
+}
+
+// CHECK: @[[TMP:_ZGR.*]] = internal constant i8 0, align 1
+// CHECK-LABEL: define {{.*}} @_Z1fv(
+// CHECK: ret ptr @[[TMP]]
diff --git a/clang/test/CodeGenCXX/const-init-cxx11.cpp b/clang/test/CodeGenCXX/const-init-cxx11.cpp
index 0795fb534af4b..5dfe3488ca7bb 100644
--- a/clang/test/CodeGenCXX/const-init-cxx11.cpp
+++ b/clang/test/CodeGenCXX/const-init-cxx11.cpp
@@ -226,8 +226,7 @@ namespace LiteralReference {
int n;
};
- // This creates a non-const temporary and binds a reference to it.
- // CHECK: @[[TEMP:.*]] = internal global {{.*}} { i32 5 }, align 4
+ // CHECK: @[[TEMP:.*]] = internal constant {{.*}} { i32 5 }, align 4
// CHECK: @_ZN16LiteralReference3litE ={{.*}} constant {{.*}} @[[TEMP]], align 8
const Lit &lit = Lit();
diff --git a/clang/test/CodeGenCXX/reference-temporary-ms.cpp b/clang/test/CodeGenCXX/reference-temporary-ms.cpp
index 6c4101634920d..34145cdbd96a7 100644
--- a/clang/test/CodeGenCXX/reference-temporary-ms.cpp
+++ b/clang/test/CodeGenCXX/reference-temporary-ms.cpp
@@ -5,6 +5,6 @@ const int __declspec(dllexport) &Exported = 42;
// The reference temporary shouldn't be dllexport, even if the reference is.
// PRE17: @"?$RT1 at Exported@@3ABHB" = internal constant i32 42
-// CXX17: @"?$RT1 at Exported@@3ABHB" = internal global i32 42
+// CXX17: @"?$RT1 at Exported@@3ABHB" = internal constant i32 42
// CHECK: @"?Exported@@3ABHB" = dso_local dllexport constant ptr @"?$RT1 at Exported@@3ABHB"
diff --git a/clang/test/CodeGenCXX/reference-temporary-mutable.cpp b/clang/test/CodeGenCXX/reference-temporary-mutable.cpp
new file mode 100644
index 0000000000000..992970a2bf34e
--- /dev/null
+++ b/clang/test/CodeGenCXX/reference-temporary-mutable.cpp
@@ -0,0 +1,29 @@
+// Tests lifetime-extended temporaries with mutable subobjects are always
+// emitted as `internal global`, never `internal constant` even for:
+// - `const` references with an initializer to a const expression,
+// - or binding to a mutable member of an otherwise const object.
+//
+// Mutable members can be modified through a const object, so they can't be
+// placed in read-only memory.
+//
+// RUN: %clang_cc1 -std=c++11 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+struct WithMutable {
+ int x;
+ mutable int m;
+ constexpr WithMutable(int X, int M) : x(X), m(M) {}
+};
+const WithMutable &direct_ref = WithMutable(1, 2);
+void touch_direct() { direct_ref.m = 7; }
+
+// CHECK: @_ZGR10direct_ref_ = internal global %struct.WithMutable
+
+struct MutableMember {
+ mutable int m;
+ constexpr MutableMember(int v) : m(v) {}
+};
+const int &member_ref = MutableMember(5).m;
+void write_member() { const_cast<int &>(member_ref) = 9; }
+
+// CHECK: @_ZGR10member_ref_ = internal global %struct.MutableMember
diff --git a/clang/test/CodeGenCXX/reference-temporary-subobject.cpp b/clang/test/CodeGenCXX/reference-temporary-subobject.cpp
new file mode 100644
index 0000000000000..828bd53692448
--- /dev/null
+++ b/clang/test/CodeGenCXX/reference-temporary-subobject.cpp
@@ -0,0 +1,50 @@
+// Tests that lifetime-extended temporaries whose backing storage is a
+// subobject (member or base) are always emitted as `internal global`, never
+// 'internal constant', regardless of the cv-qualification on the reference.
+//
+// In C++98, skipRValueSubobjectAdjustments is used for rvalue subobject
+// adjustments. The MaterializeTemporaryExpr ends up with the type of the
+// reference (e.g. `const int`), not the type of the backing store (e.g. `S`).
+// hasSameUnqualifiedType detects this mismatch and correctly falls back to
+// Init->getType(), preventing the backing store from being marked constant.
+//
+// RUN: %clang_cc1 -std=c++98 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -std=c++11 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+// `const int &` bound to a member of an S temporary.
+// The backing store is the whole S object, which can't be stored const.
+
+struct MemberS { int x, y; };
+const int &member_ref = MemberS().x;
+
+// CHECK: @_ZGR10member_ref_ = internal global %struct.MemberS
+
+// Binding a `const int &` to a mutable member.
+// The backing store is the whole object (with a mutable member), so it
+// must remain writable.
+
+struct MutableS {
+ mutable int m;
+ MutableS() : m(1) {}
+};
+const int &mutable_member_ref = MutableS().m;
+void write_mutable() { const_cast<int &>(mutable_member_ref) = 5; }
+
+// CHECK: @_ZGR18mutable_member_ref_ = internal global %struct.MutableS
+
+// `const Base &` bound to a Derived temporary.
+// Non-constexpr constructors mean no constant initializer is possible, and
+// the backing store is the full Derived object.
+
+struct Base { int b; Base() : b(11) {} };
+struct Derived : Base { int d; Derived() : Base(), d(22) {} };
+const Base &base_ref = Derived();
+
+// CHECK: @_ZGR8base_ref_ = internal global %struct.Derived
+
+// Same as above but using a plain member instead of a base class.
+
+struct Pair { int a, c; Pair() : a(33), c(44) {} };
+const int &pair_member_ref = Pair().a;
+
+// CHECK: @_ZGR15pair_member_ref_ = internal global %struct.Pair
diff --git a/clang/test/CodeGenCXX/temporaries.cpp b/clang/test/CodeGenCXX/temporaries.cpp
index 146ebc7dd090e..c3842776e0c5e 100644
--- a/clang/test/CodeGenCXX/temporaries.cpp
+++ b/clang/test/CodeGenCXX/temporaries.cpp
@@ -48,8 +48,7 @@ namespace BraceInit {
typedef const int &CIR;
CIR x = CIR{3};
// CHECK-CXX11: @_ZGRN9BraceInit1xE_ = internal constant i32 3
- // FIXME: This should still be emitted as 'constant' in C++17.
- // CHECK-CXX17: @_ZGRN9BraceInit1xE_ = internal global i32 3
+ // CHECK-CXX17: @_ZGRN9BraceInit1xE_ = internal constant i32 3
// CHECK: @_ZN9BraceInit1xE ={{.*}} constant ptr @_ZGRN9BraceInit1xE_
}
@@ -59,7 +58,7 @@ namespace RefTempSubobject {
int ints[3] = {1, 2, 3};
};
- // CHECK: @_ZGRN16RefTempSubobject2srE_ = internal global { ptr, [3 x i32] } { {{.*}} getelementptr {{.*}} @_ZGRN16RefTempSubobject2srE_, {{.*}}, [3 x i32] [i32 1, i32 2, i32 3] }
+ // CHECK: @_ZGRN16RefTempSubobject2srE_ = internal constant { ptr, [3 x i32] } { {{.*}} getelementptr {{.*}} @_ZGRN16RefTempSubobject2srE_, {{.*}}, [3 x i32] [i32 1, i32 2, i32 3] }
// CHECK: @_ZN16RefTempSubobject2srE = constant {{.*}} @_ZGRN16RefTempSubobject2srE_
constexpr const SelfReferential &sr = SelfReferential();
}
More information about the cfe-commits
mailing list