[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 09:35:39 PDT 2026
https://github.com/Takashiidobe updated https://github.com/llvm/llvm-project/pull/186594
>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 eb2c2c1e517cd3fd421680eda83d697a6185613a 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 | 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
More information about the cfe-commits
mailing list