[clang] [Clang] fix bad codegen from constexpr structured bindings (PR #186594)
Takashi Idobe via cfe-commits
cfe-commits at lists.llvm.org
Sat Mar 14 06:48:56 PDT 2026
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 <idobetakashi at gmail.com>
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 <idobetakashi at gmail.com>
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
More information about the cfe-commits
mailing list