[clang] [Clang] fix bad codegen from constexpr structured bindings (PR #186594)
Takashi Idobe via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 17 19:05:35 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/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 <idobetakashi at gmail.com>
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 <idobetakashi at gmail.com>
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 976d8886fc642e143eb658c4b98dd6b687940865 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
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 | 51 ++++++++++---------
1 file changed, 28 insertions(+), 23 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..931469d207c49 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
-// 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
More information about the cfe-commits
mailing list