https://github.com/Takashiidobe updated https://github.com/llvm/llvm-project/pull/186594
>From 4db794fd703f969eaecff6f49dc4adc4d523fcaa Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Sat, 14 Mar 2026 09:43:44 -0400 Subject: [PATCH 1/6] allow C++26 constexpr structured pack bindings to be emitted as constants --- clang/lib/CodeGen/CGExpr.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 23802cdeb4811..aa9411cff654e 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -1942,6 +1942,17 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) { CEK = checkVarTypeForConstantEmission(var->getType()); } else if (isa<EnumConstantDecl>(Value)) { CEK = CEK_AsValueOnly; + } else if (const auto *BD = dyn_cast<BindingDecl>(Value)) { + // For structured binding elements from tuple-like decompositions, the + // binding is backed by a hidden holding variable (the "reference + // temporary"). Use the holding variable's type to decide whether we + // can constant-emit. Without this, static constexpr pack bindings used + // as array indices always materialise as loads from their reference- + // temporary globals, blocking constant folding and vectorisation. + if (VarDecl *HV = BD->getHoldingVar()) + CEK = checkVarTypeForConstantEmission(HV->getType()); + else + CEK = CEK_None; } else { CEK = CEK_None; } @@ -2003,6 +2014,16 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) { if (isa<VarDecl>(Value)) { if (!getContext().DeclMustBeEmitted(cast<VarDecl>(Value))) EmitDeclRefExprDbgValue(RefExpr, result.Val); + } else if (const auto *BD = dyn_cast<BindingDecl>(Value)) { + // For tuple-like structured binding elements, the holding variable is + // always emitted (static storage), so only emit a debug reference if + // it is not otherwise required to be emitted. + if (VarDecl *HV = BD->getHoldingVar()) { + if (!getContext().DeclMustBeEmitted(HV)) + EmitDeclRefExprDbgValue(RefExpr, result.Val); + } else { + EmitDeclRefExprDbgValue(RefExpr, result.Val); + } } else { assert(isa<EnumConstantDecl>(Value)); EmitDeclRefExprDbgValue(RefExpr, result.Val); >From d96c0acf83785ab5165daf077b20b84c9c123552 Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Sat, 14 Mar 2026 09:43:54 -0400 Subject: [PATCH 2/6] add test from issue --- ...cpp26-constexpr-binding-pack-subscript.cpp | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp diff --git a/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp new file mode 100644 index 0000000000000..3f7a02a78717e --- /dev/null +++ b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp @@ -0,0 +1,52 @@ +// RUN: %clang -std=c++26 -O3 -emit-llvm -S -target x86_64-unknown-linux-gnu -o - %s | FileCheck %s +// RUN: %clang -std=c++26 -emit-llvm -S -target x86_64-unknown-linux-gnu -o - %s | FileCheck %s --check-prefix=IR +// UNSUPPORTED: system-windows + +// static constexpr structured binding pack elements used as array +// subscript indices must be constant-folded at the Clang codegen level. +// Without the fix, each element is emitted as a load from a "reference +// temporary" static variable, preventing vectorisation at -O3. + +#include <array> +#include <cstdint> + +using u8 = std::uint8_t; + +template <u8 N> +struct Range { + template <std::size_t I> + consteval 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 + +template <std::size_t L, std::size_t R> +__attribute__((always_inline)) inline constexpr std::array<u8, L + R> +concat(const std::array<u8, L> &l, const std::array<u8, R> &r) { + static constexpr auto [...I] = Range<L>{}; + static constexpr auto [...J] = Range<R>{}; + return {l[I]..., r[J]...}; +} + +auto test(const std::array<u8, 16> &l, const std::array<u8, 16> &r) { + return concat(l, r); +} + +// At -O3 the two 16-byte arrays should be copied with a pair of vector +// loads/stores; no scalar byte loop and no reference-temporary indirection. +// CHECK-LABEL: define {{.*}} @{{.*test.*}} +// CHECK-NOT: reference temporary +// CHECK: load <16 x i8> +// CHECK: store <16 x i8> +// CHECK: load <16 x i8> +// CHECK: store <16 x i8> +// CHECK: ret void + +// At any optimisation level the binding-pack indices must not be materialised +// as "reference temporary" static variables. +// IR-LABEL: define {{.*}} @{{.*test.*}} +// IR-NOT: reference temporary >From 21f7cc0bbea880459639a6f2fa476b640a7a78b5 Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Mon, 16 Mar 2026 21:45:52 -0400 Subject: [PATCH 3/6] code review feedback --- clang/lib/CodeGen/CGExpr.cpp | 24 ++++----- ...cpp26-constexpr-binding-pack-subscript.cpp | 52 +++++++++---------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index aa9411cff654e..a82b30f65cc32 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -1935,11 +1935,15 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) { const ValueDecl *Value = RefExpr->getDecl(); // The value needs to be an enum constant or a constant variable. + // For BindingDecls backed by a holding variable, UnderlyingVar points to + // that holding variable and is used below for debug-value emission. ConstantEmissionKind CEK; + const VarDecl *UnderlyingVar = nullptr; if (isa<ParmVarDecl>(Value)) { CEK = CEK_None; } else if (const auto *var = dyn_cast<VarDecl>(Value)) { CEK = checkVarTypeForConstantEmission(var->getType()); + UnderlyingVar = var; } else if (isa<EnumConstantDecl>(Value)) { CEK = CEK_AsValueOnly; } else if (const auto *BD = dyn_cast<BindingDecl>(Value)) { @@ -1949,10 +1953,12 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) { // can constant-emit. Without this, static constexpr pack bindings used // as array indices always materialise as loads from their reference- // temporary globals, blocking constant folding and vectorisation. - if (VarDecl *HV = BD->getHoldingVar()) + if (VarDecl *HV = BD->getHoldingVar()) { CEK = checkVarTypeForConstantEmission(HV->getType()); - else + UnderlyingVar = HV; + } else { CEK = CEK_None; + } } else { CEK = CEK_None; } @@ -2011,19 +2017,9 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) { // Make sure we emit a debug reference to the global variable. // This should probably fire even for - if (isa<VarDecl>(Value)) { - if (!getContext().DeclMustBeEmitted(cast<VarDecl>(Value))) + if (UnderlyingVar) { + if (!getContext().DeclMustBeEmitted(UnderlyingVar)) EmitDeclRefExprDbgValue(RefExpr, result.Val); - } else if (const auto *BD = dyn_cast<BindingDecl>(Value)) { - // For tuple-like structured binding elements, the holding variable is - // always emitted (static storage), so only emit a debug reference if - // it is not otherwise required to be emitted. - if (VarDecl *HV = BD->getHoldingVar()) { - if (!getContext().DeclMustBeEmitted(HV)) - EmitDeclRefExprDbgValue(RefExpr, result.Val); - } else { - EmitDeclRefExprDbgValue(RefExpr, result.Val); - } } else { assert(isa<EnumConstantDecl>(Value)); EmitDeclRefExprDbgValue(RefExpr, result.Val); diff --git a/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp index 3f7a02a78717e..cd47a03dc3483 100644 --- a/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp +++ b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp @@ -1,52 +1,50 @@ -// RUN: %clang -std=c++26 -O3 -emit-llvm -S -target x86_64-unknown-linux-gnu -o - %s | FileCheck %s -// RUN: %clang -std=c++26 -emit-llvm -S -target x86_64-unknown-linux-gnu -o - %s | FileCheck %s --check-prefix=IR -// UNSUPPORTED: system-windows +// RUN: %clang_cc1 -std=c++26 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s // static constexpr structured binding pack elements used as array // subscript indices must be constant-folded at the Clang codegen level. // Without the fix, each element is emitted as a load from a "reference -// temporary" static variable, preventing vectorisation at -O3. +// temporary" static variable. -#include <array> -#include <cstdint> +namespace std { + using size_t = decltype(sizeof(0)); + template <typename T> struct tuple_size; + template <size_t I, typename T> struct tuple_element; +} // namespace std -using u8 = std::uint8_t; +using u8 = unsigned char; template <u8 N> struct Range { template <std::size_t I> consteval 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; }; + 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 +template <std::size_t N> +struct Array { + u8 data[N]; + constexpr const u8 &operator[](std::size_t i) const { return data[i]; } +}; + template <std::size_t L, std::size_t R> -__attribute__((always_inline)) inline constexpr std::array<u8, L + R> -concat(const std::array<u8, L> &l, const std::array<u8, R> &r) { +__attribute__((always_inline)) inline constexpr Array<L + R> +concat(const Array<L> &l, const Array<R> &r) { static constexpr auto [...I] = Range<L>{}; static constexpr auto [...J] = Range<R>{}; - return {l[I]..., r[J]...}; + return {{l[I]..., r[J]...}}; } -auto test(const std::array<u8, 16> &l, const std::array<u8, 16> &r) { +Array<32> test(const Array<16> &l, const Array<16> &r) { return concat(l, r); } -// At -O3 the two 16-byte arrays should be copied with a pair of vector -// loads/stores; no scalar byte loop and no reference-temporary indirection. +// The binding-pack indices must not be materialised as "reference temporary" +// static variables at any optimisation level. // CHECK-LABEL: define {{.*}} @{{.*test.*}} // CHECK-NOT: reference temporary -// CHECK: load <16 x i8> -// CHECK: store <16 x i8> -// CHECK: load <16 x i8> -// CHECK: store <16 x i8> -// CHECK: ret void - -// At any optimisation level the binding-pack indices must not be materialised -// as "reference temporary" static variables. -// IR-LABEL: define {{.*}} @{{.*test.*}} -// IR-NOT: reference temporary >From e6227b8fff625f7a795b10323a976216e82b055d Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Tue, 17 Mar 2026 22:04:09 -0400 Subject: [PATCH 4/6] fix test by adding cv-qualified tuple protocol forwarding to trigger constexpr pack decomposition --- ...cpp26-constexpr-binding-pack-subscript.cpp | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp index cd47a03dc3483..fdc6e9fb36e93 100644 --- a/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp +++ b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp @@ -1,50 +1,55 @@ -// RUN: %clang_cc1 -std=c++26 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -std=c++26 -O2 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s -// static constexpr structured binding pack elements used as array -// subscript indices must be constant-folded at the Clang codegen level. -// Without the fix, each element is emitted as a load from a "reference -// temporary" static variable. +// static constexpr structured binding pack elements used as array subscript +// indices must be constant-folded at the Clang codegen level. Without the fix, +// each element is emitted as a load from a reference temporary static variable. namespace std { - using size_t = decltype(sizeof(0)); - template <typename T> struct tuple_size; - template <size_t I, typename T> struct tuple_element; + 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> - consteval friend u8 get(Range) noexcept { return I; } + template <std::size_t I> + consteval 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; }; + 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 template <std::size_t N> struct Array { - u8 data[N]; - constexpr const u8 &operator[](std::size_t i) const { return data[i]; } + u8 data[N]; + const u8 &operator[](std::size_t i) const { return data[i]; } }; template <std::size_t L, std::size_t R> -__attribute__((always_inline)) inline constexpr Array<L + R> -concat(const Array<L> &l, const Array<R> &r) { - static constexpr auto [...I] = Range<L>{}; - static constexpr auto [...J] = Range<R>{}; - return {{l[I]..., r[J]...}}; +Array<L + R> concat(const Array<L> &l, const Array<R> &r) { + static constexpr auto [...I] = Range<L>{}; + static constexpr auto [...J] = Range<R>{}; + return {{l[I]..., r[J]...}}; } Array<32> test(const Array<16> &l, const Array<16> &r) { - return concat(l, r); + return concat(l, r); } -// The binding-pack indices must not be materialised as "reference temporary" +// The binding-pack indices must not be materialised as reference-temporary // static variables at any optimisation level. // CHECK-LABEL: define {{.*}} @{{.*test.*}} -// CHECK-NOT: reference temporary +// CHECK-NOT: @_ZGR >From 5defcea6a9a7d0f70d0b09237f65ae692809cbfa Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Wed, 18 Mar 2026 19:36:35 -0400 Subject: [PATCH 5/6] fix constexpr codegen problem in CodeGenModule instead. Keep the CV-qualifiers from the MaterializeTemporaryExpr which can be used to generate better code in a const context --- clang/lib/CodeGen/CGExpr.cpp | 21 +------ clang/lib/CodeGen/CodeGenModule.cpp | 11 ++-- ...egen-for-constexpr-structured-bindings.cpp | 40 ++++++++++++++ clang/test/CodeGenCXX/const-init-cxx11.cpp | 3 +- ...cpp26-constexpr-binding-pack-subscript.cpp | 55 ------------------- .../CodeGenCXX/reference-temporary-ms.cpp | 2 +- clang/test/CodeGenCXX/temporaries.cpp | 5 +- 7 files changed, 52 insertions(+), 85 deletions(-) create mode 100644 clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp delete mode 100644 clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index a82b30f65cc32..23802cdeb4811 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -1935,30 +1935,13 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) { const ValueDecl *Value = RefExpr->getDecl(); // The value needs to be an enum constant or a constant variable. - // For BindingDecls backed by a holding variable, UnderlyingVar points to - // that holding variable and is used below for debug-value emission. ConstantEmissionKind CEK; - const VarDecl *UnderlyingVar = nullptr; if (isa<ParmVarDecl>(Value)) { CEK = CEK_None; } else if (const auto *var = dyn_cast<VarDecl>(Value)) { CEK = checkVarTypeForConstantEmission(var->getType()); - UnderlyingVar = var; } else if (isa<EnumConstantDecl>(Value)) { CEK = CEK_AsValueOnly; - } else if (const auto *BD = dyn_cast<BindingDecl>(Value)) { - // For structured binding elements from tuple-like decompositions, the - // binding is backed by a hidden holding variable (the "reference - // temporary"). Use the holding variable's type to decide whether we - // can constant-emit. Without this, static constexpr pack bindings used - // as array indices always materialise as loads from their reference- - // temporary globals, blocking constant folding and vectorisation. - if (VarDecl *HV = BD->getHoldingVar()) { - CEK = checkVarTypeForConstantEmission(HV->getType()); - UnderlyingVar = HV; - } else { - CEK = CEK_None; - } } else { CEK = CEK_None; } @@ -2017,8 +2000,8 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) { // Make sure we emit a debug reference to the global variable. // This should probably fire even for - if (UnderlyingVar) { - if (!getContext().DeclMustBeEmitted(UnderlyingVar)) + if (isa<VarDecl>(Value)) { + if (!getContext().DeclMustBeEmitted(cast<VarDecl>(Value))) EmitDeclRefExprDbgValue(RefExpr, result.Val); } else { assert(isa<EnumConstantDecl>(Value)); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index daaa846bf42bc..256db38233c0e 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -7357,11 +7357,12 @@ 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. - QualType MaterializedType = Init->getType(); - if (Init == E->getSubExpr()) - MaterializedType = E->getType(); + // Keep cv-qualifiers from the MaterializeTemporaryExpr on the storage type. + // The initializer expression may have had rvalue subobject adjustments + // stripped, which can drop top-level qualifiers that are still part of the + // materialized temporary's type. + QualType MaterializedType = getContext().getQualifiedType( + Init->getType(), E->getType().getQualifiers()); 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/cpp26-constexpr-binding-pack-subscript.cpp b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp deleted file mode 100644 index fdc6e9fb36e93..0000000000000 --- a/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// RUN: %clang_cc1 -std=c++26 -O2 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s - -// static constexpr structured binding pack elements used as array subscript -// indices must be constant-folded at the Clang codegen level. Without the fix, -// each element is emitted as a load from a reference temporary static variable. - -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> - consteval 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 - -template <std::size_t N> -struct Array { - u8 data[N]; - const u8 &operator[](std::size_t i) const { return data[i]; } -}; - -template <std::size_t L, std::size_t R> -Array<L + R> concat(const Array<L> &l, const Array<R> &r) { - static constexpr auto [...I] = Range<L>{}; - static constexpr auto [...J] = Range<R>{}; - return {{l[I]..., r[J]...}}; -} - -Array<32> test(const Array<16> &l, const Array<16> &r) { - return concat(l, r); -} - -// The binding-pack indices must not be materialised as reference-temporary -// static variables at any optimisation level. -// CHECK-LABEL: define {{.*}} @{{.*test.*}} -// CHECK-NOT: @_ZGR 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@Exported@@3ABHB" = internal constant i32 42 -// CXX17: @"?$RT1@Exported@@3ABHB" = internal global i32 42 +// CXX17: @"?$RT1@Exported@@3ABHB" = internal constant i32 42 // CHECK: @"?Exported@@3ABHB" = dso_local dllexport constant ptr @"?$RT1@Exported@@3ABHB" 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(); } >From 8eca176d263b9ac5075e4694525185edb15232d4 Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Thu, 19 Mar 2026 21:59:31 -0400 Subject: [PATCH 6/6] Fix cv-qualification of lifetime-extended global temporaries: This fixes static constexpr structured binding pack elements being incorrectly emitted as internal global rather than internal constant, blocking constant folding of their uses. GetAddrOfGlobalTemporary determines storage type by taking the initializer from skipRValueSubobjectAdjustments, which strips Noop casts. This dropped cv-qualifiers on a type, causing constexpr lifetime-extended temporaries to be emitted as internal global instead of internal constant. The fix is to use the expression's type (which has cv-qualifiers from the reference binding) only when E and Init have the same unqualified base type (meaning only casts were stripped). When these types differ, like in this case: ``` struct S { int x, y; }; const int &xx = S().x; ``` Under C++98 rules, E has type const int but the initializer needs to be in writable memory. We need to fall back to the initializer's type in order to avoid labeling the backing object as const. For C++11 and later, the Expr is not const-qualified as the backing object is not, so there's no need to handle this case. --- clang/lib/CodeGen/CodeGenModule.cpp | 16 +++--- .../reference-temporary-mutable.cpp | 29 +++++++++++ .../reference-temporary-subobject.cpp | 50 +++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 clang/test/CodeGenCXX/reference-temporary-mutable.cpp create mode 100644 clang/test/CodeGenCXX/reference-temporary-subobject.cpp diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 256db38233c0e..ea2f4aa790752 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -7357,12 +7357,16 @@ ConstantAddress CodeGenModule::GetAddrOfGlobalTemporary( E->getStorageDuration() == SD_Thread) && "not a global temporary"); const auto *VD = cast<VarDecl>(E->getExtendingDecl()); - // Keep cv-qualifiers from the MaterializeTemporaryExpr on the storage type. - // The initializer expression may have had rvalue subobject adjustments - // stripped, which can drop top-level qualifiers that are still part of the - // materialized temporary's type. - QualType MaterializedType = getContext().getQualifiedType( - Init->getType(), E->getType().getQualifiers()); + // 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 (getContext().hasSameUnqualifiedType(E->getType(), MaterializedType)) + MaterializedType = E->getType(); CharUnits Align = getContext().getTypeAlignInChars(MaterializedType); 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
