https://github.com/Takashiidobe created https://github.com/llvm/llvm-project/pull/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. >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/2] 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 f43a6bae31f7916505079b25353d79e0162718f8 Mon Sep 17 00:00:00 2001 From: Takashiidobe <[email protected]> Date: Sat, 14 Mar 2026 09:43:54 -0400 Subject: [PATCH 2/2] add test from issue --- ...cpp26-constexpr-binding-pack-subscript.cpp | 51 +++++++++++++++++++ 1 file changed, 51 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..551f53ddcd74c --- /dev/null +++ b/clang/test/CodeGenCXX/cpp26-constexpr-binding-pack-subscript.cpp @@ -0,0 +1,51 @@ +// 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 + +// 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
