https://github.com/Takashiidobe updated https://github.com/llvm/llvm-project/pull/186594
>From bccd1fed6e25b57bcbb9990c6c4a6305d3af3288 Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Sat, 14 Mar 2026 09:43:44 -0400 Subject: [PATCH 1/4] 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 069846b854a87..b2ce2a4f7e24e 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 eb2c2c1e517cd3fd421680eda83d697a6185613a Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Sat, 14 Mar 2026 09:43:54 -0400 Subject: [PATCH 2/4] 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 a4547b4ec4fe3369e21aa5696a119e786650a126 Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Mon, 16 Mar 2026 21:45:52 -0400 Subject: [PATCH 3/4] 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 b2ce2a4f7e24e..bd94e0ea3ec5f 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 255aad97ac3fb1a71d18d3a1f31ce6fbe588458d Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Tue, 17 Mar 2026 22:04:09 -0400 Subject: [PATCH 4/4] fix test by adding cv-qualified tuple protocol forwarding to trigger constexpr pack decomposition --- ...cpp26-constexpr-binding-pack-subscript.cpp | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 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..3f08c017b8380 100644 --- a/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp +++ b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp @@ -1,15 +1,21 @@ // 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. +// 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; -} // namespace std + 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; @@ -29,12 +35,11 @@ namespace std { template <std::size_t N> struct Array { u8 data[N]; - constexpr const u8 &operator[](std::size_t i) const { return data[i]; } + 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) { +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]...}}; @@ -44,7 +49,7 @@ 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" +// 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
