[clang] [OpenMP] Support capturing structured bindings in OpenMP regions. (PR #190832)
Zahira Ammarguellat via cfe-commits
cfe-commits at lists.llvm.org
Fri May 1 13:13:50 PDT 2026
https://github.com/zahiraam updated https://github.com/llvm/llvm-project/pull/190832
>From ca43027b4156c15d34c7b6b75ed8420ccc0c1f2e Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Tue, 7 Apr 2026 12:10:39 -0700
Subject: [PATCH 01/13] [OpenMP] Support capturing structured bindings in
OpenMP regions.
---
clang/lib/CodeGen/CGExpr.cpp | 18 ++-
clang/lib/Sema/SemaExpr.cpp | 16 +-
clang/lib/Sema/SemaStmt.cpp | 5 +-
.../OpenMP/structured-binding-capture.cpp | 141 ++++++++++++++++++
4 files changed, 169 insertions(+), 11 deletions(-)
create mode 100644 clang/test/OpenMP/structured-binding-capture.cpp
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 23802cdeb4811..b2feb5d339a8e 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3737,8 +3737,22 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
// an enclosing scope.
if (const auto *BD = dyn_cast<BindingDecl>(ND)) {
if (E->refersToEnclosingVariableOrCapture()) {
- auto *FD = LambdaCaptureFields.lookup(BD);
- return EmitCapturedFieldLValue(*this, FD, CXXABIThisValue);
+ if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
+ auto I = LocalDeclMap.find(DD);
+ if (I != LocalDeclMap.end()) {
+ Address DDAddr = I->second;
+ llvm::Type *StructTy = CGM.getTypes().ConvertTypeForMem(
+ DD->getType().getCanonicalType());
+ if (DDAddr.getElementType() != StructTy)
+ DDAddr = DDAddr.withElementType(StructTy);
+ LValue BaseLV =
+ MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
+ return EmitLValueForField(
+ BaseLV, cast<FieldDecl>(
+ cast<MemberExpr>(BD->getBinding()->IgnoreImplicit())
+ ->getMemberDecl()));
+ }
+ }
}
// Suppress debug location updates when visiting the binding, since the
// binding may emit instructions that would otherwise be associated with the
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index c9642ed298bf3..46a93b8e53d3e 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19374,6 +19374,8 @@ static bool isVariableCapturable(CapturingScopeInfo *CSI, ValueDecl *Var,
}
if (isa<BindingDecl>(Var)) {
+ if (Var->getDeclName() && !Var->isImplicit())
+ return true;
if (!IsLambda || !S.getLangOpts().CPlusPlus) {
if (Diagnose)
diagnoseUncapturableValueReferenceOrBinding(S, Loc, Var);
@@ -19514,6 +19516,12 @@ static bool captureInLambda(LambdaScopeInfo *LSI, ValueDecl *Var,
ByRef = (LSI->ImpCaptureStyle == LambdaScopeInfo::ImpCap_LambdaByref);
}
+ if (auto* BD = dyn_cast<BindingDecl>(Var)) {
+ // For structured bindings, capture the individual element type,
+ // not the full decomposed type.
+ CaptureType = BD->getType();
+ DeclRefType = BD->getType();
+ }
if (BuildAndDiagnose && S.Context.getTargetInfo().getTriple().isWasm() &&
CaptureType.getNonReferenceType().isWebAssemblyReferenceType()) {
S.Diag(Loc, diag::err_wasm_ca_reference) << 0;
@@ -19880,14 +19888,6 @@ bool Sema::tryCaptureVariable(
// just break here. Similarly, global variables that are captured in a
// target region should not be captured outside the scope of the region.
if (RSI->CapRegionKind == CR_OpenMP) {
- // FIXME: We should support capturing structured bindings in OpenMP.
- if (isa<BindingDecl>(Var)) {
- if (BuildAndDiagnose) {
- Diag(ExprLoc, diag::err_capture_binding_openmp) << Var;
- Diag(Var->getLocation(), diag::note_entity_declared_at) << Var;
- }
- return true;
- }
OpenMPClauseKind IsOpenMPPrivateDecl = OpenMP().isOpenMPPrivateDecl(
Var, RSI->OpenMPLevel, RSI->OpenMPCaptureLevel);
// If the variable is private (i.e. not captured) and has variably
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 531147ef35b08..21c799b89a64a 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -4700,11 +4700,14 @@ buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
S.OpenMP().setOpenMPCaptureKind(Field, Cap.getVariable(),
RSI->OpenMPLevel);
+ ValueDecl* CapVar = Cap.getVariable();
+ if (auto* BD = dyn_cast<BindingDecl>(CapVar))
+ CapVar = cast<VarDecl>(BD->getDecomposedDecl());
Captures.push_back(CapturedStmt::Capture(
Cap.getLocation(),
Cap.isReferenceCapture() ? CapturedStmt::VCK_ByRef
: CapturedStmt::VCK_ByCopy,
- cast<VarDecl>(Cap.getVariable())));
+ cast<VarDecl>(CapVar)));
}
CaptureInits.push_back(Init.get());
}
diff --git a/clang/test/OpenMP/structured-binding-capture.cpp b/clang/test/OpenMP/structured-binding-capture.cpp
new file mode 100644
index 0000000000000..5d3fae741958b
--- /dev/null
+++ b/clang/test/OpenMP/structured-binding-capture.cpp
@@ -0,0 +1,141 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --include-generated-funcs --replace-value-regex "__omp_offloading_[0-9a-z]+_[0-9a-z]+" "reduction_size[.].+[.]" "pl_cond[.].+[.|,]" --prefix-filecheck-ir-name _ --version 4
+// RUN: %clang_cc1 -verify -std=c++20 -triple x86_64-pc-linux-gnu -fopenmp \
+// RUN: -emit-llvm %s -o - | FileCheck %s
+
+// expected-no-diagnostics
+
+struct Point {
+ int first, second;
+};
+
+Point twoints() {
+ return {37, 24};
+}
+
+int main() {
+ auto [m, n] = twoints();
+#pragma omp parallel for collapse(2)
+ for (int i = 0; i < 10; i++)
+ for (int j = 0; j < 10; j++)
+ [m, n](int i, int j) -> void { return; }(i, j);
+ return 0;
+}
+
+// CHECK-LABEL: define dso_local i64 @_Z7twointsv(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca [[STRUCT_POINT:%.*]], align 4
+// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[RETVAL]], i32 0, i32 0
+// CHECK-NEXT: store i32 37, ptr [[FIRST]], align 4
+// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[RETVAL]], i32 0, i32 1
+// CHECK-NEXT: store i32 24, ptr [[SECOND]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i64, ptr [[RETVAL]], align 4
+// CHECK-NEXT: ret i64 [[TMP0]]
+//
+//
+// CHECK-LABEL: define dso_local noundef i32 @main(
+// CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[TMP0:%.*]] = alloca [[STRUCT_POINT:%.*]], align 4
+// CHECK-NEXT: store i32 0, ptr [[RETVAL]], align 4
+// CHECK-NEXT: [[CALL:%.*]] = call i64 @_Z7twointsv()
+// CHECK-NEXT: store i64 [[CALL]], ptr [[TMP0]], align 4
+// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
+// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB2:[0-9]+]], i32 2, ptr @main.omp_outlined, ptr [[FIRST]], ptr [[SECOND]])
+// CHECK-NEXT: ret i32 0
+//
+//
+// CHECK-LABEL: define internal void @main.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP1:%.*]]) #[[ATTR2:[0-9]+]] {
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[DOTGLOBAL_TID__ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[DOTBOUND_TID__ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[DOTADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[DOTADDR1:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[DOTOMP_IV:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[TMP:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[_TMP2:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DOTOMP_LB:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DOTOMP_UB:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DOTOMP_STRIDE:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[DOTOMP_IS_LAST:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[I:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[J:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[CLASS_ANON:%.*]], align 4
+// CHECK-NEXT: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK-NEXT: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR]], align 8
+// CHECK-NEXT: store ptr [[TMP0]], ptr [[DOTADDR]], align 8
+// CHECK-NEXT: store ptr [[TMP1]], ptr [[DOTADDR1]], align 8
+// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2:![0-9]+]], !align [[META3:![0-9]+]]
+// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK-NEXT: store i32 0, ptr [[DOTOMP_LB]], align 4
+// CHECK-NEXT: store i32 99, ptr [[DOTOMP_UB]], align 4
+// CHECK-NEXT: store i32 1, ptr [[DOTOMP_STRIDE]], align 4
+// CHECK-NEXT: store i32 0, ptr [[DOTOMP_IS_LAST]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[TMP4]], align 4
+// CHECK-NEXT: call void @__kmpc_for_static_init_4(ptr @[[GLOB1:[0-9]+]], i32 [[TMP5]], i32 34, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP6]], 99
+// CHECK-NEXT: br i1 [[CMP]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]]
+// CHECK: cond.true:
+// CHECK-NEXT: br label [[COND_END:%.*]]
+// CHECK: cond.false:
+// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK-NEXT: br label [[COND_END]]
+// CHECK: cond.end:
+// CHECK-NEXT: [[COND:%.*]] = phi i32 [ 99, [[COND_TRUE]] ], [ [[TMP7]], [[COND_FALSE]] ]
+// CHECK-NEXT: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
+// CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
+// CHECK-NEXT: store i32 [[TMP8]], ptr [[DOTOMP_IV]], align 4
+// CHECK-NEXT: br label [[OMP_INNER_FOR_COND:%.*]]
+// CHECK: omp.inner.for.cond:
+// CHECK-NEXT: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK-NEXT: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK-NEXT: [[CMP3:%.*]] = icmp sle i32 [[TMP9]], [[TMP10]]
+// CHECK-NEXT: br i1 [[CMP3]], label [[OMP_INNER_FOR_BODY:%.*]], label [[OMP_INNER_FOR_END:%.*]]
+// CHECK: omp.inner.for.body:
+// CHECK-NEXT: [[TMP11:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK-NEXT: [[DIV:%.*]] = sdiv i32 [[TMP11]], 10
+// CHECK-NEXT: [[MUL:%.*]] = mul nsw i32 [[DIV]], 1
+// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK-NEXT: store i32 [[ADD]], ptr [[I]], align 4
+// CHECK-NEXT: [[TMP12:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK-NEXT: [[TMP13:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK-NEXT: [[DIV4:%.*]] = sdiv i32 [[TMP13]], 10
+// CHECK-NEXT: [[MUL5:%.*]] = mul nsw i32 [[DIV4]], 10
+// CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 [[TMP12]], [[MUL5]]
+// CHECK-NEXT: [[MUL6:%.*]] = mul nsw i32 [[SUB]], 1
+// CHECK-NEXT: [[ADD7:%.*]] = add nsw i32 0, [[MUL6]]
+// CHECK-NEXT: store i32 [[ADD7]], ptr [[J]], align 4
+// CHECK-NEXT: [[TMP14:%.*]] = getelementptr inbounds nuw [[CLASS_ANON]], ptr [[REF_TMP]], i32 0, i32 0
+// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK-NEXT: [[TMP15:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK-NEXT: store i32 [[TMP15]], ptr [[TMP14]], align 4
+// CHECK-NEXT: [[TMP16:%.*]] = getelementptr inbounds nuw [[CLASS_ANON]], ptr [[REF_TMP]], i32 0, i32 1
+// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK-NEXT: [[TMP17:%.*]] = load i32, ptr [[SECOND]], align 4
+// CHECK-NEXT: store i32 [[TMP17]], ptr [[TMP16]], align 4
+// CHECK-NEXT: [[TMP18:%.*]] = load i32, ptr [[I]], align 4
+// CHECK-NEXT: [[TMP19:%.*]] = load i32, ptr [[J]], align 4
+// CHECK-NEXT: call void @"_ZZ4mainENK3$_0clEii"(ptr noundef nonnull align 4 dereferenceable(8) [[REF_TMP]], i32 noundef [[TMP18]], i32 noundef [[TMP19]])
+// CHECK-NEXT: br label [[OMP_BODY_CONTINUE:%.*]]
+// CHECK: omp.body.continue:
+// CHECK-NEXT: br label [[OMP_INNER_FOR_INC:%.*]]
+// CHECK: omp.inner.for.inc:
+// CHECK-NEXT: [[TMP20:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK-NEXT: [[ADD8:%.*]] = add nsw i32 [[TMP20]], 1
+// CHECK-NEXT: store i32 [[ADD8]], ptr [[DOTOMP_IV]], align 4
+// CHECK-NEXT: br label [[OMP_INNER_FOR_COND]]
+// CHECK: omp.inner.for.end:
+// CHECK-NEXT: br label [[OMP_LOOP_EXIT:%.*]]
+// CHECK: omp.loop.exit:
+// CHECK-NEXT: call void @__kmpc_for_static_fini(ptr @[[GLOB1]], i32 [[TMP5]])
+// CHECK-NEXT: ret void
+//
+//.
+// CHECK: [[META2]] = !{}
+// CHECK: [[META3]] = !{i64 4}
+//.
>From 5518969a479ceeb370ff6130545cd8b3d9b5ebd3 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Fri, 17 Apr 2026 11:59:31 -0700
Subject: [PATCH 02/13] Addressed review comments
---
clang/lib/CodeGen/CGExpr.cpp | 33 ++++++++++++---------
clang/lib/Sema/SemaExpr.cpp | 5 +++-
clang/test/SemaCXX/decomposition-openmp.cpp | 5 ++--
3 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index b2feb5d339a8e..4366853389ed8 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3737,22 +3737,27 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
// an enclosing scope.
if (const auto *BD = dyn_cast<BindingDecl>(ND)) {
if (E->refersToEnclosingVariableOrCapture()) {
- if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
- auto I = LocalDeclMap.find(DD);
- if (I != LocalDeclMap.end()) {
- Address DDAddr = I->second;
- llvm::Type *StructTy = CGM.getTypes().ConvertTypeForMem(
- DD->getType().getCanonicalType());
- if (DDAddr.getElementType() != StructTy)
- DDAddr = DDAddr.withElementType(StructTy);
- LValue BaseLV =
- MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
- return EmitLValueForField(
- BaseLV, cast<FieldDecl>(
- cast<MemberExpr>(BD->getBinding()->IgnoreImplicit())
- ->getMemberDecl()));
+ auto *FD = LambdaCaptureFields.lookup(BD);
+ if (!FD) {
+ // OpenMP case: binding was captured via its decomposed decl.
+ if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
+ auto I = LocalDeclMap.find(DD);
+ if (I != LocalDeclMap.end()) {
+ Address DDAddr = I->second;
+ llvm::Type *StructTy = CGM.getTypes().ConvertTypeForMem(
+ DD->getType().getCanonicalType());
+ if (DDAddr.getElementType() != StructTy)
+ DDAddr = DDAddr.withElementType(StructTy);
+ LValue BaseLV =
+ MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
+ return EmitLValueForField(
+ BaseLV, cast<FieldDecl>(
+ cast<MemberExpr>(BD->getBinding()->IgnoreImplicit())
+ ->getMemberDecl()));
+ }
}
}
+ return EmitCapturedFieldLValue(*this, FD, CXXABIThisValue);
}
// Suppress debug location updates when visiting the binding, since the
// binding may emit instructions that would otherwise be associated with the
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 46a93b8e53d3e..fe059f427b767 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19374,8 +19374,11 @@ static bool isVariableCapturable(CapturingScopeInfo *CSI, ValueDecl *Var,
}
if (isa<BindingDecl>(Var)) {
- if (Var->getDeclName() && !Var->isImplicit())
+ if (Var->getDeclName() && !Var->isImplicit()) {
+ if (auto *RSI = dyn_cast<CapturedRegionScopeInfo>(CSI))
+ if (RSI->CapRegionKind == CR_OpenMP)
return true;
+ }
if (!IsLambda || !S.getLangOpts().CPlusPlus) {
if (Diagnose)
diagnoseUncapturableValueReferenceOrBinding(S, Loc, Var);
diff --git a/clang/test/SemaCXX/decomposition-openmp.cpp b/clang/test/SemaCXX/decomposition-openmp.cpp
index 2185f3db83d4e..70f1d40a87661 100644
--- a/clang/test/SemaCXX/decomposition-openmp.cpp
+++ b/clang/test/SemaCXX/decomposition-openmp.cpp
@@ -1,5 +1,7 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -fopenmp %s
+// expected-no-diagnostics
+
// Okay, not an OpenMP capture.
auto f() {
int i[2] = {};
@@ -23,10 +25,9 @@ void g() {
// FIXME: OpenMP should support capturing structured bindings
void h() {
int i[2] = {};
- auto [a, b] = i; // expected-note 2{{declared here}}
+ auto [a, b] = i;
#pragma omp parallel
{
- // expected-error at +1 2{{capturing a structured binding is not yet supported in OpenMP}}
foo(a + b);
}
}
>From 4dc3499c7b11b267ecdfc4f20f1adef16e69b04f Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Fri, 17 Apr 2026 12:05:48 -0700
Subject: [PATCH 03/13] Fix format
---
clang/lib/Sema/SemaExpr.cpp | 2 +-
clang/lib/Sema/SemaStmt.cpp | 14 +++++++-------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index fe059f427b767..300a20133f49a 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19519,7 +19519,7 @@ static bool captureInLambda(LambdaScopeInfo *LSI, ValueDecl *Var,
ByRef = (LSI->ImpCaptureStyle == LambdaScopeInfo::ImpCap_LambdaByref);
}
- if (auto* BD = dyn_cast<BindingDecl>(Var)) {
+ if (auto *BD = dyn_cast<BindingDecl>(Var)) {
// For structured bindings, capture the individual element type,
// not the full decomposed type.
CaptureType = BD->getType();
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 21c799b89a64a..60d4214bb3c8a 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -4700,14 +4700,14 @@ buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
S.OpenMP().setOpenMPCaptureKind(Field, Cap.getVariable(),
RSI->OpenMPLevel);
- ValueDecl* CapVar = Cap.getVariable();
- if (auto* BD = dyn_cast<BindingDecl>(CapVar))
+ ValueDecl *CapVar = Cap.getVariable();
+ if (auto *BD = dyn_cast<BindingDecl>(CapVar))
CapVar = cast<VarDecl>(BD->getDecomposedDecl());
- Captures.push_back(CapturedStmt::Capture(
- Cap.getLocation(),
- Cap.isReferenceCapture() ? CapturedStmt::VCK_ByRef
- : CapturedStmt::VCK_ByCopy,
- cast<VarDecl>(CapVar)));
+ Captures.push_back(CapturedStmt::Capture(Cap.getLocation(),
+ Cap.isReferenceCapture()
+ ? CapturedStmt::VCK_ByRef
+ : CapturedStmt::VCK_ByCopy,
+ cast<VarDecl>(CapVar)));
}
CaptureInits.push_back(Init.get());
}
>From da55057a51bd0bdfb0f845b03d583600de7622ea Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Mon, 20 Apr 2026 05:40:43 -0700
Subject: [PATCH 04/13] Addressed review comments
---
clang/lib/CodeGen/CGExpr.cpp | 35 +++++++++++++++++------------------
1 file changed, 17 insertions(+), 18 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 4366853389ed8..f1a67b8334702 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3737,26 +3737,25 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
// an enclosing scope.
if (const auto *BD = dyn_cast<BindingDecl>(ND)) {
if (E->refersToEnclosingVariableOrCapture()) {
- auto *FD = LambdaCaptureFields.lookup(BD);
- if (!FD) {
- // OpenMP case: binding was captured via its decomposed decl.
- if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
- auto I = LocalDeclMap.find(DD);
- if (I != LocalDeclMap.end()) {
- Address DDAddr = I->second;
- llvm::Type *StructTy = CGM.getTypes().ConvertTypeForMem(
- DD->getType().getCanonicalType());
- if (DDAddr.getElementType() != StructTy)
- DDAddr = DDAddr.withElementType(StructTy);
- LValue BaseLV =
- MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
- return EmitLValueForField(
- BaseLV, cast<FieldDecl>(
- cast<MemberExpr>(BD->getBinding()->IgnoreImplicit())
- ->getMemberDecl()));
- }
+ // OpenMP case: binding was captured via its decomposed decl.
+ if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
+ auto I = LocalDeclMap.find(DD);
+ if (I != LocalDeclMap.end()) {
+ Address DDAddr = I->second;
+ llvm::Type *StructTy = CGM.getTypes().ConvertTypeForMem(
+ DD->getType().getCanonicalType());
+ if (DDAddr.getElementType() != StructTy)
+ DDAddr = DDAddr.withElementType(StructTy);
+ LValue BaseLV =
+ MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
+ return EmitLValueForField(
+ BaseLV, cast<FieldDecl>(
+ cast<MemberExpr>(BD->getBinding()->IgnoreImplicit())
+ ->getMemberDecl()));
}
}
+ // Non-OpenMP case: binding was captured as a lambda field directly.
+ auto *FD = LambdaCaptureFields.lookup(BD);
return EmitCapturedFieldLValue(*this, FD, CXXABIThisValue);
}
// Suppress debug location updates when visiting the binding, since the
>From 5261f6f55d6fabdf83ba69d1bb47568140dbf345 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Tue, 21 Apr 2026 14:47:59 -0700
Subject: [PATCH 05/13] Addressed review comments
---
clang/docs/ReleaseNotes.rst | 3 +++
clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 --
clang/lib/CodeGen/CGExpr.cpp | 4 ++++
clang/lib/Sema/SemaStmt.cpp | 9 ++++-----
4 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 688f0a2c2bb75..dafb83a3fa2af 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -615,6 +615,9 @@ OpenMP Support
- Added support for ``transparent`` clause in task and taskloop directives.
- Added support for ``use_device_ptr`` clause to accept an optional
``fallback`` modifier (``fb_nullify`` or ``fb_preserve``) with OpenMP >= 61.
+- Added support for capturing structured bindings. Variables introduced by
+ decomposition declarations are now handled correctly when captured inside
+ OpenMP constructs.
Improvements
^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index eddf9c50033e1..62b15bca04196 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10260,8 +10260,6 @@ def ext_ms_anonymous_record : ExtWarn<
def err_reference_to_local_in_enclosing_context : Error<
"reference to local %select{variable|binding}1 %0 declared in enclosing "
"%select{%3|block literal|lambda expression|context}2">;
-def err_capture_binding_openmp : Error<
- "capturing a structured binding is not yet supported in OpenMP">;
def ext_capture_binding : ExtWarn<
"captured structured bindings are a C++20 extension">, InGroup<CXX20>;
def warn_cxx17_compat_capture_binding : Warning<
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index f1a67b8334702..d6573d94be470 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3739,6 +3739,10 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
if (E->refersToEnclosingVariableOrCapture()) {
// OpenMP case: binding was captured via its decomposed decl.
if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
+ assert(CapturedStmtInfo && "Expected to be in a captured statement");
+ assert(CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
+ "Expected OpenMP captured region");
+ assert(CGM.getLangOpts().OpenMP && "OpenMP not enabled");
auto I = LocalDeclMap.find(DD);
if (I != LocalDeclMap.end()) {
Address DDAddr = I->second;
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 60d4214bb3c8a..b4e041841feee 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -4703,11 +4703,10 @@ buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
ValueDecl *CapVar = Cap.getVariable();
if (auto *BD = dyn_cast<BindingDecl>(CapVar))
CapVar = cast<VarDecl>(BD->getDecomposedDecl());
- Captures.push_back(CapturedStmt::Capture(Cap.getLocation(),
- Cap.isReferenceCapture()
- ? CapturedStmt::VCK_ByRef
- : CapturedStmt::VCK_ByCopy,
- cast<VarDecl>(CapVar)));
+ Captures.emplace_back(Cap.getLocation(),
+ Cap.isReferenceCapture() ? CapturedStmt::VCK_ByRef
+ : CapturedStmt::VCK_ByCopy,
+ cast<VarDecl>(CapVar));
}
CaptureInits.push_back(Init.get());
}
>From 1c1da353d5ca3d3ae8a75b0970bf415adb10d8d0 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Wed, 29 Apr 2026 07:51:04 -0700
Subject: [PATCH 06/13] Addressed review comments
---
clang/docs/ReleaseNotes.rst | 7 +-
clang/lib/CodeGen/CGExpr.cpp | 107 ++++++++--
clang/lib/Sema/SemaExpr.cpp | 6 -
clang/lib/Sema/SemaLambda.cpp | 7 +-
clang/lib/Sema/SemaStmt.cpp | 24 ++-
.../OpenMP/structured-binding-capture.cpp | 141 -------------
.../OpenMP/structured-bindings-codegen.cpp | 195 ++++++++++++++++++
clang/test/SemaCXX/decomposition-openmp.cpp | 1 -
8 files changed, 310 insertions(+), 178 deletions(-)
delete mode 100644 clang/test/OpenMP/structured-binding-capture.cpp
create mode 100644 clang/test/OpenMP/structured-bindings-codegen.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index dafb83a3fa2af..bc094aeaa33b1 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -615,9 +615,10 @@ OpenMP Support
- Added support for ``transparent`` clause in task and taskloop directives.
- Added support for ``use_device_ptr`` clause to accept an optional
``fallback`` modifier (``fb_nullify`` or ``fb_preserve``) with OpenMP >= 61.
-- Added support for capturing structured bindings. Variables introduced by
- decomposition declarations are now handled correctly when captured inside
- OpenMP constructs.
+- Added support for C++17 structured bindings in OpenMP regions. Structured
+ bindings from structs, classes, and arrays can now be used inside
+ OpenMP directives. Note: Tuple-like bindings (types using the tuple protocol
+ with ``get<N>()``) are not yet supported and will produce a compilation error.
Improvements
^^^^^^^^^^^^
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index d6573d94be470..ed9b8580b63ea 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3739,26 +3739,97 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
if (E->refersToEnclosingVariableOrCapture()) {
// OpenMP case: binding was captured via its decomposed decl.
if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
- assert(CapturedStmtInfo && "Expected to be in a captured statement");
- assert(CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
- "Expected OpenMP captured region");
- assert(CGM.getLangOpts().OpenMP && "OpenMP not enabled");
- auto I = LocalDeclMap.find(DD);
- if (I != LocalDeclMap.end()) {
- Address DDAddr = I->second;
- llvm::Type *StructTy = CGM.getTypes().ConvertTypeForMem(
- DD->getType().getCanonicalType());
- if (DDAddr.getElementType() != StructTy)
- DDAddr = DDAddr.withElementType(StructTy);
- LValue BaseLV =
- MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
- return EmitLValueForField(
- BaseLV, cast<FieldDecl>(
- cast<MemberExpr>(BD->getBinding()->IgnoreImplicit())
- ->getMemberDecl()));
+ if (CapturedStmtInfo &&
+ CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
+ CGM.getLangOpts().OpenMP) {
+ auto I = LocalDeclMap.find(DD);
+ if (I != LocalDeclMap.end()) {
+ Address DDAddr = I->second;
+ llvm::Type *ExpectedTy = CGM.getTypes().ConvertTypeForMem(
+ DD->getType().getCanonicalType());
+ if (DDAddr.getElementType() != ExpectedTy)
+ DDAddr = DDAddr.withElementType(ExpectedTy);
+ LValue CapLVal;
+ if (DD->getType()->isReferenceType())
+ CapLVal = EmitLoadOfReferenceLValue(DDAddr, DD->getType(),
+ AlignmentSource::Decl);
+ else
+ CapLVal =
+ MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
+ if (getLangOpts().OpenMP &&
+ CGM.getOpenMPRuntime().isNontemporalDecl(DD))
+ CapLVal.setNontemporal(/*Value=*/true);
+ // Extract the specific binding from the decomposed object.
+ Expr *BindingExpr = BD->getBinding()->IgnoreImplicit();
+ if (auto *ME = dyn_cast<MemberExpr>(BindingExpr)) {
+ // Struct/union: access field.
+ return EmitLValueForField(CapLVal,
+ cast<FieldDecl>(ME->getMemberDecl()));
+ }
+ if (auto *ASE = dyn_cast<ArraySubscriptExpr>(BindingExpr)) {
+ Address Base = CapLVal.getAddress();
+ llvm::Value *Idx = EmitScalarExpr(ASE->getIdx());
+ llvm::Value *Indices[] = {llvm::ConstantInt::get(Int32Ty, 0),
+ Idx};
+ llvm::Type *ElemTy =
+ CGM.getTypes().ConvertTypeForMem(ASE->getType());
+ llvm::Value *EltPtr = Builder.CreateInBoundsGEP(
+ Base.getElementType(), Base.emitRawPointer(*this), Indices,
+ "arrayidx");
+ CharUnits Align = Base.getAlignment().alignmentOfArrayElement(
+ getContext().getTypeSizeInChars(ASE->getType()));
+ Address EltAddr(EltPtr, ElemTy, Align);
+ return MakeAddrLValue(EltAddr, ASE->getType());
+ }
+ // Fallback for complex binding types.
+ // TODO: Tuple bindings (std::tuple, std::pair via tuple protocol)
+ // use hidden temporary variables that aren't captured in OpenMP
+ // regions. Need to re-emit the get<N>() call on the captured tuple
+ // base object. For now, this will fail.
+ if (isa<DeclRefExpr>(BindingExpr))
+ llvm_unreachable(
+ "tuple-like structured bindings not yet supported in OpenMP");
+ return EmitLValue(BindingExpr);
+ }
+ // DD not in LocalDeclMap, check capture struct
+ if (auto *FD = CapturedStmtInfo->lookup(DD)) {
+ LValue CapLVal = EmitCapturedFieldLValue(
+ *this, FD, CapturedStmtInfo->getContextValue());
+ Address Addr = CapLVal.getAddress();
+ llvm::Type *ExpectedTy = CGM.getTypes().ConvertTypeForMem(
+ DD->getType().getCanonicalType());
+ if (Addr.getElementType() != ExpectedTy)
+ Addr = Addr.withElementType(ExpectedTy);
+ CapLVal = MakeAddrLValue(Addr, DD->getType().getCanonicalType());
+ if (DD->getType()->isReferenceType())
+ CapLVal = EmitLoadOfReferenceLValue(
+ CapLVal.getAddress(), DD->getType(), AlignmentSource::Decl);
+ if (getLangOpts().OpenMP &&
+ CGM.getOpenMPRuntime().isNontemporalDecl(DD))
+ CapLVal.setNontemporal(/*Value=*/true);
+
+ // Extract the specific binding.
+ Expr *BindingExpr = BD->getBinding()->IgnoreImplicit();
+ if (auto *ME = dyn_cast<MemberExpr>(BindingExpr)) {
+ return EmitLValueForField(CapLVal,
+ cast<FieldDecl>(ME->getMemberDecl()));
+ }
+ if (auto *ASE = dyn_cast<ArraySubscriptExpr>(BindingExpr)) {
+ Address Base = CapLVal.getAddress();
+ llvm::Value *Idx = EmitScalarExpr(ASE->getIdx());
+ llvm::Value *EltPtr = Builder.CreateInBoundsGEP(
+ Base.getElementType(), Base.emitRawPointer(*this), Idx,
+ "arrayidx");
+ CharUnits Align = Base.getAlignment().alignmentOfArrayElement(
+ getContext().getTypeSizeInChars(ASE->getType()));
+ Address EltAddr(EltPtr, Base.getElementType(), Align);
+ return MakeAddrLValue(EltAddr, ASE->getType());
+ }
+ return EmitLValue(BindingExpr);
+ }
}
}
- // Non-OpenMP case: binding was captured as a lambda field directly.
+ // Non-OpenMP case: lambda capture.
auto *FD = LambdaCaptureFields.lookup(BD);
return EmitCapturedFieldLValue(*this, FD, CXXABIThisValue);
}
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 300a20133f49a..e59a6c861aefd 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19519,12 +19519,6 @@ static bool captureInLambda(LambdaScopeInfo *LSI, ValueDecl *Var,
ByRef = (LSI->ImpCaptureStyle == LambdaScopeInfo::ImpCap_LambdaByref);
}
- if (auto *BD = dyn_cast<BindingDecl>(Var)) {
- // For structured bindings, capture the individual element type,
- // not the full decomposed type.
- CaptureType = BD->getType();
- DeclRefType = BD->getType();
- }
if (BuildAndDiagnose && S.Context.getTargetInfo().getTriple().isWasm() &&
CaptureType.getNonReferenceType().isWebAssemblyReferenceType()) {
S.Diag(Loc, diag::err_wasm_ca_reference) << 0;
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 8572e3a742a6c..754d0918f79da 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1970,9 +1970,14 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
} else {
assert(Cap.isVariableCapture() && "unknown kind of capture");
ValueDecl *Var = Cap.getVariable();
+ // For OpenMP structured bindings, capture the decomposed decl, not the
+ // binding.
+ if (IsOpenMPMapping && isa<BindingDecl>(Var)) {
+ Var = cast<BindingDecl>(Var)->getDecomposedDecl();
+ }
Name = Var->getIdentifier();
Init = BuildDeclarationNameExpr(
- CXXScopeSpec(), DeclarationNameInfo(Var->getDeclName(), Loc), Var);
+ CXXScopeSpec(), DeclarationNameInfo(Var->getDeclName(), Loc), Var);
}
// In OpenMP, the capture kind doesn't actually describe how to capture:
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index b4e041841feee..9f90dc1fda665 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -4672,10 +4672,23 @@ static bool
buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
SmallVectorImpl<CapturedStmt::Capture> &Captures,
SmallVectorImpl<Expr *> &CaptureInits) {
+ llvm::SmallPtrSet<VarDecl *, 4> CapturedDecomposed;
for (const sema::Capture &Cap : RSI->Captures) {
if (Cap.isInvalid())
continue;
+ ValueDecl *CapVar = nullptr;
+ if (Cap.isVariableCapture()) {
+ CapVar = Cap.getVariable();
+ if (auto *BD = dyn_cast<BindingDecl>(CapVar)) {
+ VarDecl *DD = cast<VarDecl>(BD->getDecomposedDecl());
+ if (!CapturedDecomposed.insert(DD).second) {
+ continue; // Skip duplicate.
+ }
+ CapVar = DD;
+ }
+ }
+
// Form the initializer for the capture.
ExprResult Init = S.BuildCaptureInit(Cap, Cap.getLocation(),
RSI->CapRegionKind == CR_OpenMP);
@@ -4688,8 +4701,8 @@ buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
// Add the capture to our list of captures.
if (Cap.isThisCapture()) {
- Captures.push_back(CapturedStmt::Capture(Cap.getLocation(),
- CapturedStmt::VCK_This));
+ Captures.push_back(
+ CapturedStmt::Capture(Cap.getLocation(), CapturedStmt::VCK_This));
} else if (Cap.isVLATypeCapture()) {
Captures.push_back(
CapturedStmt::Capture(Cap.getLocation(), CapturedStmt::VCK_VLAType));
@@ -4697,12 +4710,7 @@ buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
assert(Cap.isVariableCapture() && "unknown kind of capture");
if (S.getLangOpts().OpenMP && RSI->CapRegionKind == CR_OpenMP)
- S.OpenMP().setOpenMPCaptureKind(Field, Cap.getVariable(),
- RSI->OpenMPLevel);
-
- ValueDecl *CapVar = Cap.getVariable();
- if (auto *BD = dyn_cast<BindingDecl>(CapVar))
- CapVar = cast<VarDecl>(BD->getDecomposedDecl());
+ S.OpenMP().setOpenMPCaptureKind(Field, CapVar, RSI->OpenMPLevel);
Captures.emplace_back(Cap.getLocation(),
Cap.isReferenceCapture() ? CapturedStmt::VCK_ByRef
: CapturedStmt::VCK_ByCopy,
diff --git a/clang/test/OpenMP/structured-binding-capture.cpp b/clang/test/OpenMP/structured-binding-capture.cpp
deleted file mode 100644
index 5d3fae741958b..0000000000000
--- a/clang/test/OpenMP/structured-binding-capture.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --include-generated-funcs --replace-value-regex "__omp_offloading_[0-9a-z]+_[0-9a-z]+" "reduction_size[.].+[.]" "pl_cond[.].+[.|,]" --prefix-filecheck-ir-name _ --version 4
-// RUN: %clang_cc1 -verify -std=c++20 -triple x86_64-pc-linux-gnu -fopenmp \
-// RUN: -emit-llvm %s -o - | FileCheck %s
-
-// expected-no-diagnostics
-
-struct Point {
- int first, second;
-};
-
-Point twoints() {
- return {37, 24};
-}
-
-int main() {
- auto [m, n] = twoints();
-#pragma omp parallel for collapse(2)
- for (int i = 0; i < 10; i++)
- for (int j = 0; j < 10; j++)
- [m, n](int i, int j) -> void { return; }(i, j);
- return 0;
-}
-
-// CHECK-LABEL: define dso_local i64 @_Z7twointsv(
-// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
-// CHECK-NEXT: entry:
-// CHECK-NEXT: [[RETVAL:%.*]] = alloca [[STRUCT_POINT:%.*]], align 4
-// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[RETVAL]], i32 0, i32 0
-// CHECK-NEXT: store i32 37, ptr [[FIRST]], align 4
-// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[RETVAL]], i32 0, i32 1
-// CHECK-NEXT: store i32 24, ptr [[SECOND]], align 4
-// CHECK-NEXT: [[TMP0:%.*]] = load i64, ptr [[RETVAL]], align 4
-// CHECK-NEXT: ret i64 [[TMP0]]
-//
-//
-// CHECK-LABEL: define dso_local noundef i32 @main(
-// CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
-// CHECK-NEXT: entry:
-// CHECK-NEXT: [[RETVAL:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[TMP0:%.*]] = alloca [[STRUCT_POINT:%.*]], align 4
-// CHECK-NEXT: store i32 0, ptr [[RETVAL]], align 4
-// CHECK-NEXT: [[CALL:%.*]] = call i64 @_Z7twointsv()
-// CHECK-NEXT: store i64 [[CALL]], ptr [[TMP0]], align 4
-// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
-// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
-// CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB2:[0-9]+]], i32 2, ptr @main.omp_outlined, ptr [[FIRST]], ptr [[SECOND]])
-// CHECK-NEXT: ret i32 0
-//
-//
-// CHECK-LABEL: define internal void @main.omp_outlined(
-// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP1:%.*]]) #[[ATTR2:[0-9]+]] {
-// CHECK-NEXT: entry:
-// CHECK-NEXT: [[DOTGLOBAL_TID__ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT: [[DOTBOUND_TID__ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT: [[DOTADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT: [[DOTADDR1:%.*]] = alloca ptr, align 8
-// CHECK-NEXT: [[DOTOMP_IV:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[TMP:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[_TMP2:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[DOTOMP_LB:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[DOTOMP_UB:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[DOTOMP_STRIDE:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[DOTOMP_IS_LAST:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[I:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[J:%.*]] = alloca i32, align 4
-// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[CLASS_ANON:%.*]], align 4
-// CHECK-NEXT: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR]], align 8
-// CHECK-NEXT: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR]], align 8
-// CHECK-NEXT: store ptr [[TMP0]], ptr [[DOTADDR]], align 8
-// CHECK-NEXT: store ptr [[TMP1]], ptr [[DOTADDR1]], align 8
-// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2:![0-9]+]], !align [[META3:![0-9]+]]
-// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8, !nonnull [[META2]], !align [[META3]]
-// CHECK-NEXT: store i32 0, ptr [[DOTOMP_LB]], align 4
-// CHECK-NEXT: store i32 99, ptr [[DOTOMP_UB]], align 4
-// CHECK-NEXT: store i32 1, ptr [[DOTOMP_STRIDE]], align 4
-// CHECK-NEXT: store i32 0, ptr [[DOTOMP_IS_LAST]], align 4
-// CHECK-NEXT: [[TMP4:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
-// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[TMP4]], align 4
-// CHECK-NEXT: call void @__kmpc_for_static_init_4(ptr @[[GLOB1:[0-9]+]], i32 [[TMP5]], i32 34, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
-// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
-// CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP6]], 99
-// CHECK-NEXT: br i1 [[CMP]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]]
-// CHECK: cond.true:
-// CHECK-NEXT: br label [[COND_END:%.*]]
-// CHECK: cond.false:
-// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
-// CHECK-NEXT: br label [[COND_END]]
-// CHECK: cond.end:
-// CHECK-NEXT: [[COND:%.*]] = phi i32 [ 99, [[COND_TRUE]] ], [ [[TMP7]], [[COND_FALSE]] ]
-// CHECK-NEXT: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
-// CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
-// CHECK-NEXT: store i32 [[TMP8]], ptr [[DOTOMP_IV]], align 4
-// CHECK-NEXT: br label [[OMP_INNER_FOR_COND:%.*]]
-// CHECK: omp.inner.for.cond:
-// CHECK-NEXT: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
-// CHECK-NEXT: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
-// CHECK-NEXT: [[CMP3:%.*]] = icmp sle i32 [[TMP9]], [[TMP10]]
-// CHECK-NEXT: br i1 [[CMP3]], label [[OMP_INNER_FOR_BODY:%.*]], label [[OMP_INNER_FOR_END:%.*]]
-// CHECK: omp.inner.for.body:
-// CHECK-NEXT: [[TMP11:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
-// CHECK-NEXT: [[DIV:%.*]] = sdiv i32 [[TMP11]], 10
-// CHECK-NEXT: [[MUL:%.*]] = mul nsw i32 [[DIV]], 1
-// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
-// CHECK-NEXT: store i32 [[ADD]], ptr [[I]], align 4
-// CHECK-NEXT: [[TMP12:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
-// CHECK-NEXT: [[TMP13:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
-// CHECK-NEXT: [[DIV4:%.*]] = sdiv i32 [[TMP13]], 10
-// CHECK-NEXT: [[MUL5:%.*]] = mul nsw i32 [[DIV4]], 10
-// CHECK-NEXT: [[SUB:%.*]] = sub nsw i32 [[TMP12]], [[MUL5]]
-// CHECK-NEXT: [[MUL6:%.*]] = mul nsw i32 [[SUB]], 1
-// CHECK-NEXT: [[ADD7:%.*]] = add nsw i32 0, [[MUL6]]
-// CHECK-NEXT: store i32 [[ADD7]], ptr [[J]], align 4
-// CHECK-NEXT: [[TMP14:%.*]] = getelementptr inbounds nuw [[CLASS_ANON]], ptr [[REF_TMP]], i32 0, i32 0
-// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
-// CHECK-NEXT: [[TMP15:%.*]] = load i32, ptr [[FIRST]], align 4
-// CHECK-NEXT: store i32 [[TMP15]], ptr [[TMP14]], align 4
-// CHECK-NEXT: [[TMP16:%.*]] = getelementptr inbounds nuw [[CLASS_ANON]], ptr [[REF_TMP]], i32 0, i32 1
-// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
-// CHECK-NEXT: [[TMP17:%.*]] = load i32, ptr [[SECOND]], align 4
-// CHECK-NEXT: store i32 [[TMP17]], ptr [[TMP16]], align 4
-// CHECK-NEXT: [[TMP18:%.*]] = load i32, ptr [[I]], align 4
-// CHECK-NEXT: [[TMP19:%.*]] = load i32, ptr [[J]], align 4
-// CHECK-NEXT: call void @"_ZZ4mainENK3$_0clEii"(ptr noundef nonnull align 4 dereferenceable(8) [[REF_TMP]], i32 noundef [[TMP18]], i32 noundef [[TMP19]])
-// CHECK-NEXT: br label [[OMP_BODY_CONTINUE:%.*]]
-// CHECK: omp.body.continue:
-// CHECK-NEXT: br label [[OMP_INNER_FOR_INC:%.*]]
-// CHECK: omp.inner.for.inc:
-// CHECK-NEXT: [[TMP20:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
-// CHECK-NEXT: [[ADD8:%.*]] = add nsw i32 [[TMP20]], 1
-// CHECK-NEXT: store i32 [[ADD8]], ptr [[DOTOMP_IV]], align 4
-// CHECK-NEXT: br label [[OMP_INNER_FOR_COND]]
-// CHECK: omp.inner.for.end:
-// CHECK-NEXT: br label [[OMP_LOOP_EXIT:%.*]]
-// CHECK: omp.loop.exit:
-// CHECK-NEXT: call void @__kmpc_for_static_fini(ptr @[[GLOB1]], i32 [[TMP5]])
-// CHECK-NEXT: ret void
-//
-//.
-// CHECK: [[META2]] = !{}
-// CHECK: [[META3]] = !{i64 4}
-//.
diff --git a/clang/test/OpenMP/structured-bindings-codegen.cpp b/clang/test/OpenMP/structured-bindings-codegen.cpp
new file mode 100644
index 0000000000000..b829a4ea93274
--- /dev/null
+++ b/clang/test/OpenMP/structured-bindings-codegen.cpp
@@ -0,0 +1,195 @@
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 -x c++ -std=c++20 -emit-llvm %s -o - | FileCheck %s
+
+// expected-no-diagnostics
+
+void use(int);
+
+// Struct binding.
+struct Point {
+ int x, y;
+};
+Point make_point() { return {1, 2}; }
+void test_struct() {
+ auto [m, n] = make_point();
+#pragma omp parallel
+ {
+ use(m + n);
+ }
+}
+// CHECK-LABEL: @{{.*}}test_struct{{.*}}()
+// CHECK: call void {{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_struct{{.*}}.omp_outlined", ptr {{.*}})
+
+// CHECK-LABEL: @{{.*}}test_struct{{.*}}.omp_outlined"(
+// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK-NEXT: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+//
+
+// Pair binding.
+struct pair {
+ int first;
+ int second;
+};
+pair make_pair(int a, int b) {
+ return {a, b};
+}
+void test_pair() {
+ auto [a, b] = make_pair(1, 2);
+#pragma omp parallel
+ {
+ use(a);
+ }
+}
+// CHECK-LABEL: @{{.*}}test_pair{{.*}}()
+// CHECK: call void {{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_pair{{.*}}.omp_outlined", ptr {{.*}})
+
+// CHECK-LABEL: @{{.*}}test_pair{{.*}}.omp_outlined"(
+// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK-NEXT: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
+//
+
+// Array binding.
+void test_array() {
+ int arr[2] = {1, 2};
+ auto [x, y] = arr;
+#pragma omp parallel
+ {
+ use(x + y);
+ }
+}
+// CHECK-LABEL: @{{.*}}test_array{{.*}}()
+// CHECK: call void {{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_array{{.*}}.omp_outlined", ptr {{.*}})
+
+// CHECK-LABEL: @{{.*}}test_array{{.*}}.omp_outlined"(
+// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK-NEXT: [[ARRAYIDX1:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
+//
+
+// Binding with bitfields.
+struct S {
+ int x : 4;
+ int y : 4;
+};
+void test_bitfields() {
+ S s{1, 2};
+ auto [a, b] = s;
+#pragma omp parallel
+ {
+ use(a + b);
+ }
+}
+// CHECK-LABEL: @{{.*}}test_bitfields{{.*}}()
+// CHECK: call void{{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_bitfields{{.*}}.omp_outlined", ptr {{.*}})
+
+// CHECK-LABEL: @{{.*}}test_bitfields{{.*}}.omp_outlined"(
+// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[BF_LOAD:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK-NEXT: [[BF_SHL:%.*]] = shl i32 [[BF_LOAD]], 28
+// CHECK-NEXT: [[BF_ASHR:%.*]] = ashr i32 [[BF_SHL]], 28
+// CHECK-NEXT: [[BF_LOAD1:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK-NEXT: [[BF_SHL2:%.*]] = shl i32 [[BF_LOAD1]], 24
+// CHECK-NEXT: [[BF_ASHR3:%.*]] = ashr i32 [[BF_SHL2]], 28
+//
+
+// Lambda inside OpenMP with captured bindings.
+void test_with_lambda() {
+ auto [m, n] = make_point();
+#pragma omp parallel for collapse(2)
+ for (int i = 0; i < 10; i++)
+ for (int j = 0; j < 10; j++)
+ [m, n](int i, int j) -> void { return; }(i, j);
+}
+// CHECK-LABEL: @{{.*}}test_with_lambda{{.*}}()
+// CHECK: call void{{.*}} @__kmpc_fork_call(ptr {{.*}}, i32 1, ptr @{{.*}}test_with_lambda{{.*}}.omp_outlined", ptr {{.*}})
+
+// CHECK-LABEL: @{{.*}}test_with_lambda{{.*}}.omp_outlined"(
+// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT: [[TMP13:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: [[TMP15:%.*]] = load i32, ptr [[Y]], align 4
+//
+
+// Only one binding used.
+void test_partial_capture() {
+ auto [a, b] = make_pair(1, 2);
+#pragma omp parallel
+ {
+ use(a);
+ }
+}
+// CHECK-LABEL: @{{.*}}test_partial_capture{{.*}}()
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 1, ptr @{{.*}}test_partial_capture{{.*}}.omp_outlined", ptr {{.*}})
+
+// CHECK-LABEL: @{{.*}}test_partial_capture{{.*}}.omp_outlined"(
+// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK-NEXT: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
+//
+
+// Nested parallel regions.
+void test_nested() {
+ auto [x, y] = make_point();
+#pragma omp parallel
+ {
+ use(x);
+#pragma omp parallel
+ {
+ use(y);
+ }
+ }
+}
+// CHECK-LABEL: @{{.*}}test_nested{{.*}}()
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr @{{.*}}test_nested{{.*}}.omp_outlined", ptr {{.*}}, ptr {{.*}})
+
+// CHECK-LABEL: @{{.*}}test_nested{{.*}}.omp_outlined"(
+// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]], ptr noundef nonnull{{.*}}[[TMP1:%.*]])
+// CHECK: [[TMP2:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK-NEXT: call void @{{.*}}use{{.*}}"(i32 noundef [[TMP4]])
+//
+
+// Multiple bindings in same region.
+void test_multiple() {
+ auto [a, b] = make_point();
+ auto [c, d] = make_pair(3, 4);
+#pragma omp parallel
+ {
+ use(a + b + c + d);
+ }
+}
+// CHECK-LABEL: define dso_local void @"?test_multiple@@YAXXZ"()
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @1, i32 2, ptr @"?test_multiple@@YAXXZ.omp_outlined", ptr %0, ptr %1)
+
+// CHECK-LABEL: define internal void @"?test_multiple@@YAXXZ.omp_outlined"(ptr noalias noundef %.global_tid., ptr noalias noundef %.bound_tid., ptr noundef nonnull align 4 dereferenceable(4) %0, ptr noundef nonnull align 4 dereferenceable(4) %1)
+// CHECK: [[TMP2:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK-NEXT: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
+// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK-NEXT: [[ADD2:%.*]] = add nsw i32 [[ADD]], [[TMP6]]
+// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP3]], i32 0, i32 1
+// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[SECOND]], align 4
+// CHECK-NEXT: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP7]]
+// CHECK-NEXT: call void {{.*}}use{{.*}}(i32 noundef [[ADD3]])
+
diff --git a/clang/test/SemaCXX/decomposition-openmp.cpp b/clang/test/SemaCXX/decomposition-openmp.cpp
index 70f1d40a87661..e3f04305f0961 100644
--- a/clang/test/SemaCXX/decomposition-openmp.cpp
+++ b/clang/test/SemaCXX/decomposition-openmp.cpp
@@ -22,7 +22,6 @@ void g() {
}
}
-// FIXME: OpenMP should support capturing structured bindings
void h() {
int i[2] = {};
auto [a, b] = i;
>From acb2aa66d5a1a9f1655520503acf89af8eb9bc5d Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Wed, 29 Apr 2026 09:23:50 -0700
Subject: [PATCH 07/13] Added warning requested in review
---
clang/lib/Sema/SemaExpr.cpp | 13 +++++++++++--
clang/test/SemaCXX/decomposition-openmp.cpp | 13 +++++++++++++
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index e59a6c861aefd..41c00199a5a9d 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19375,9 +19375,18 @@ static bool isVariableCapturable(CapturingScopeInfo *CSI, ValueDecl *Var,
if (isa<BindingDecl>(Var)) {
if (Var->getDeclName() && !Var->isImplicit()) {
- if (auto *RSI = dyn_cast<CapturedRegionScopeInfo>(CSI))
- if (RSI->CapRegionKind == CR_OpenMP)
+ if (auto *RSI = dyn_cast<CapturedRegionScopeInfo>(CSI)) {
+ if (RSI->CapRegionKind == CR_OpenMP) {
+ if (Diagnose && S.getLangOpts().CPlusPlus) {
+ S.Diag(Loc, S.LangOpts.CPlusPlus20
+ ? diag::warn_cxx17_compat_capture_binding
+ : diag::ext_capture_binding)
+ << Var;
+ S.Diag(Var->getLocation(), diag::note_entity_declared_at) << Var;
+ }
return true;
+ }
+ }
}
if (!IsLambda || !S.getLangOpts().CPlusPlus) {
if (Diagnose)
diff --git a/clang/test/SemaCXX/decomposition-openmp.cpp b/clang/test/SemaCXX/decomposition-openmp.cpp
index e3f04305f0961..78135d90a561b 100644
--- a/clang/test/SemaCXX/decomposition-openmp.cpp
+++ b/clang/test/SemaCXX/decomposition-openmp.cpp
@@ -1,3 +1,4 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx17 -std=c++17 -fopenmp %s
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -fopenmp %s
// expected-no-diagnostics
@@ -5,8 +6,12 @@
// Okay, not an OpenMP capture.
auto f() {
int i[2] = {};
+ // cxx17-note at +2{{'a' declared here}}
+ // cxx17-note at +1{{'b' declared here}}
auto [a, b] = i;
+ // cxx17-warning at +1{{captured structured bindings are a C++20 extension}}
return [=, &a] {
+ // cxx17-warning at +1{{captured structured bindings are a C++20 extension}}
return a + b;
};
}
@@ -17,16 +22,24 @@ void g() {
#pragma omp parallel
{
int i[2] = {};
+ // cxx17-note at +2{{'a' declared here}}
+ // cxx17-note at +1{{'b' declared here}}
auto [a, b] = i;
+ // cxx17-warning at +2{{captured structured bindings are a C++20 extension}}
+ // cxx17-warning at +1{{captured structured bindings are a C++20 extension}}
auto L = [&] { foo(a+b); };
}
}
void h() {
int i[2] = {};
+ // cxx17-note at +2{{'a' declared here}}
+ // cxx17-note at +1{{'b' declared here}}
auto [a, b] = i;
#pragma omp parallel
{
+ // cxx17-warning at +2{{captured structured bindings are a C++20 extension}}
+ // cxx17-warning at +1{{captured structured bindings are a C++20 extension}}
foo(a + b);
}
}
>From 4b266f3575780caac74e78e5f1559d599db34396 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Wed, 29 Apr 2026 12:05:50 -0700
Subject: [PATCH 08/13] Made check lines less strict for failing LIT test
---
.../OpenMP/structured-bindings-codegen.cpp | 125 +++++++-----------
1 file changed, 45 insertions(+), 80 deletions(-)
diff --git a/clang/test/OpenMP/structured-bindings-codegen.cpp b/clang/test/OpenMP/structured-bindings-codegen.cpp
index b829a4ea93274..81d5c9298ede2 100644
--- a/clang/test/OpenMP/structured-bindings-codegen.cpp
+++ b/clang/test/OpenMP/structured-bindings-codegen.cpp
@@ -16,17 +16,9 @@ void test_struct() {
use(m + n);
}
}
-// CHECK-LABEL: @{{.*}}test_struct{{.*}}()
-// CHECK: call void {{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_struct{{.*}}.omp_outlined", ptr {{.*}})
-
-// CHECK-LABEL: @{{.*}}test_struct{{.*}}.omp_outlined"(
-// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
-// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
-// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
-// CHECK-NEXT: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
-//
+// CHECK-LABEL: @{{.*}}test_struct{{.*}}.omp_outlined{{.*}}(
+// CHECK: getelementptr inbounds{{.*}}i32 0, i32 0
+// CHECK: getelementptr inbounds{{.*}}i32 0, i32 1
// Pair binding.
struct pair {
@@ -43,15 +35,11 @@ void test_pair() {
use(a);
}
}
-// CHECK-LABEL: @{{.*}}test_pair{{.*}}()
-// CHECK: call void {{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_pair{{.*}}.omp_outlined", ptr {{.*}})
-
-// CHECK-LABEL: @{{.*}}test_pair{{.*}}.omp_outlined"(
-// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK-LABEL: @{{.*}}test_pair{{.*}}.omp_outlined{{.*}}(
// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
-// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
-// CHECK-NEXT: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
+// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
//
// Array binding.
@@ -63,16 +51,12 @@ void test_array() {
use(x + y);
}
}
-// CHECK-LABEL: @{{.*}}test_array{{.*}}()
-// CHECK: call void {{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_array{{.*}}.omp_outlined", ptr {{.*}})
-
-// CHECK-LABEL: @{{.*}}test_array{{.*}}.omp_outlined"(
-// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK-LABEL: @{{.*}}test_array{{.*}}.omp_outlined{{.*}}(
// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 0
-// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
-// CHECK-NEXT: [[ARRAYIDX1:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
+// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK: [[ARRAYIDX1:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
//
// Binding with bitfields.
@@ -88,18 +72,14 @@ void test_bitfields() {
use(a + b);
}
}
-// CHECK-LABEL: @{{.*}}test_bitfields{{.*}}()
-// CHECK: call void{{.*}}@__kmpc_fork_call({{.*}}, i32 1, ptr @{{.*}}test_bitfields{{.*}}.omp_outlined", ptr {{.*}})
-
-// CHECK-LABEL: @{{.*}}test_bitfields{{.*}}.omp_outlined"(
-// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK-LABEL: @{{.*}}test_bitfields{{.*}}.omp_outlined{{.*}}(
// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[BF_LOAD:%.*]] = load i32, ptr [[TMP1]], align 4
-// CHECK-NEXT: [[BF_SHL:%.*]] = shl i32 [[BF_LOAD]], 28
-// CHECK-NEXT: [[BF_ASHR:%.*]] = ashr i32 [[BF_SHL]], 28
-// CHECK-NEXT: [[BF_LOAD1:%.*]] = load i32, ptr [[TMP1]], align 4
-// CHECK-NEXT: [[BF_SHL2:%.*]] = shl i32 [[BF_LOAD1]], 24
-// CHECK-NEXT: [[BF_ASHR3:%.*]] = ashr i32 [[BF_SHL2]], 28
+// CHECK: [[BF_LOAD:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK: [[BF_SHL:%.*]] = shl i32 [[BF_LOAD]], 28
+// CHECK: [[BF_ASHR:%.*]] = ashr i32 [[BF_SHL]], 28
+// CHECK: [[BF_LOAD1:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK: [[BF_SHL2:%.*]] = shl i32 [[BF_LOAD1]], 24
+// CHECK: [[BF_ASHR3:%.*]] = ashr i32 [[BF_SHL2]], 28
//
// Lambda inside OpenMP with captured bindings.
@@ -110,16 +90,12 @@ void test_with_lambda() {
for (int j = 0; j < 10; j++)
[m, n](int i, int j) -> void { return; }(i, j);
}
-// CHECK-LABEL: @{{.*}}test_with_lambda{{.*}}()
-// CHECK: call void{{.*}} @__kmpc_fork_call(ptr {{.*}}, i32 1, ptr @{{.*}}test_with_lambda{{.*}}.omp_outlined", ptr {{.*}})
-
-// CHECK-LABEL: @{{.*}}test_with_lambda{{.*}}.omp_outlined"(
-// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK-LABEL: @{{.*}}test_with_lambda{{.*}}.omp_outlined{{.*}}(
// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
-// CHECK-NEXT: [[TMP13:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[X]], align 4
// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT: [[TMP15:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[Y]], align 4
//
// Only one binding used.
@@ -130,15 +106,11 @@ void test_partial_capture() {
use(a);
}
}
-// CHECK-LABEL: @{{.*}}test_partial_capture{{.*}}()
-// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 1, ptr @{{.*}}test_partial_capture{{.*}}.omp_outlined", ptr {{.*}})
-
-// CHECK-LABEL: @{{.*}}test_partial_capture{{.*}}.omp_outlined"(
-// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]])
+// CHECK-LABEL: @{{.*}}test_partial_capture{{.*}}.omp_outlined{{.*}}(
// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
-// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
-// CHECK-NEXT: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
+// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
//
// Nested parallel regions.
@@ -153,16 +125,12 @@ void test_nested() {
}
}
}
-// CHECK-LABEL: @{{.*}}test_nested{{.*}}()
-// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr @{{.*}}test_nested{{.*}}.omp_outlined", ptr {{.*}}, ptr {{.*}})
-
-// CHECK-LABEL: @{{.*}}test_nested{{.*}}.omp_outlined"(
-// CHECK-SAME: ptr {{.*}}, ptr {{.*}}, ptr noundef nonnull{{.*}}[[TMP0:%.*]], ptr noundef nonnull{{.*}}[[TMP1:%.*]])
+// CHECK-LABEL: @{{.*}}test_nested{{.*}}.omp_outlined{{.*}}(
// CHECK: [[TMP2:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
-// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
-// CHECK-NEXT: call void @{{.*}}use{{.*}}"(i32 noundef [[TMP4]])
+// CHECK: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: call void @{{.*}}use{{.*}}"(i32 noundef [[TMP4]])
//
// Multiple bindings in same region.
@@ -174,22 +142,19 @@ void test_multiple() {
use(a + b + c + d);
}
}
-// CHECK-LABEL: define dso_local void @"?test_multiple@@YAXXZ"()
-// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @1, i32 2, ptr @"?test_multiple@@YAXXZ.omp_outlined", ptr %0, ptr %1)
-
-// CHECK-LABEL: define internal void @"?test_multiple@@YAXXZ.omp_outlined"(ptr noalias noundef %.global_tid., ptr noalias noundef %.bound_tid., ptr noundef nonnull align 4 dereferenceable(4) %0, ptr noundef nonnull align 4 dereferenceable(4) %1)
+// CHECK-LABEL: @{{.*}}test_multiple{{.*}}.omp_outlined{{.*}}(
// CHECK: [[TMP2:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
-// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
-// CHECK-NEXT: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
-// CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[Y]], align 4
-// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
-// CHECK-NEXT: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
-// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[FIRST]], align 4
-// CHECK-NEXT: [[ADD2:%.*]] = add nsw i32 [[ADD]], [[TMP6]]
-// CHECK-NEXT: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP3]], i32 0, i32 1
-// CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[SECOND]], align 4
-// CHECK-NEXT: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP7]]
-// CHECK-NEXT: call void {{.*}}use{{.*}}(i32 noundef [[ADD3]])
+// CHECK: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
+// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[ADD]], [[TMP6]]
+// CHECK: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[SECOND]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP7]]
+// CHECK: call void {{.*}}use{{.*}}(i32 noundef [[ADD3]])
>From f840cd350d45eece92750730492e450164e8f8b9 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Thu, 30 Apr 2026 05:34:59 -0700
Subject: [PATCH 09/13] Fixed LIT test.
---
clang/test/OpenMP/structured-bindings-codegen.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/test/OpenMP/structured-bindings-codegen.cpp b/clang/test/OpenMP/structured-bindings-codegen.cpp
index 81d5c9298ede2..6e895491ea5bd 100644
--- a/clang/test/OpenMP/structured-bindings-codegen.cpp
+++ b/clang/test/OpenMP/structured-bindings-codegen.cpp
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 -x c++ -std=c++20 -emit-llvm %s -o - | FileCheck %s
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 -x c++ -std=c++20 \
+// RUN: -emit-llvm %s -o - | FileCheck %s
// expected-no-diagnostics
>From b39bad61c5232eb0f8d102ab66d6009aa8a28218 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Thu, 30 Apr 2026 13:31:33 -0700
Subject: [PATCH 10/13] Addressed review comments
---
clang/lib/CodeGen/CGExpr.cpp | 150 +++----
clang/lib/CodeGen/CodeGenFunction.h | 1 +
.../OpenMP/structured-bindings-codegen.cpp | 409 +++++++++++++++---
3 files changed, 409 insertions(+), 151 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index d640b8e698def..9a0dc0a88c6be 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3589,6 +3589,61 @@ static bool canEmitSpuriousReferenceToVariable(CodeGenFunction &CGF,
}
}
+/// Emit an LValue for a structured binding captured in an OpenMP region.
+/// Handles extracting individual bindings from the captured decomposed
+/// declaration (struct fields, array elements, etc.).
+LValue CodeGenFunction::EmitOMPCapturedBindingLValue(const BindingDecl *BD) {
+ assert(CapturedStmtInfo &&
+ CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
+ CGM.getLangOpts().OpenMP);
+ auto *DD = cast<VarDecl>(BD->getDecomposedDecl());
+ auto I = LocalDeclMap.find(DD);
+ assert(I != LocalDeclMap.end() && "Decomposed decl not in LocalDeclMap");
+
+ Address ParamAddr = I->second;
+ QualType AggregType = DD->getType();
+ if (AggregType->isReferenceType())
+ AggregType = AggregType->getPointeeType();
+
+ LValue CapLVal;
+ llvm::Type *ParamLLVMType = ParamAddr.getElementType();
+ if (ParamLLVMType->isPointerTy()) {
+ llvm::Value *Ptr = Builder.CreateLoad(ParamAddr, "captured.val");
+ Address AggregAddr(Ptr, ConvertTypeForMem(AggregType),
+ getContext().getDeclAlign(DD));
+ CapLVal = MakeAddrLValue(AggregAddr, AggregType);
+ } else {
+ Address AggregAddr(ParamAddr.emitRawPointer(*this),
+ ConvertTypeForMem(AggregType), ParamAddr.getAlignment());
+ CapLVal = MakeAddrLValue(AggregAddr, AggregType);
+ }
+ // Extract the specific binding from the decomposed object.
+ Expr *BindingExpr = BD->getBinding()->IgnoreImplicit();
+ if (auto *ME = dyn_cast<MemberExpr>(BindingExpr)) {
+ // Struct/union: access field.
+ FieldDecl *Field = cast<FieldDecl>(ME->getMemberDecl());
+ return EmitLValueForField(CapLVal, Field);
+ } else if (auto *ASE = dyn_cast<ArraySubscriptExpr>(BindingExpr)) {
+ // Array binding - access element.
+ Address Base = CapLVal.getAddress();
+ llvm::Value *Idx = EmitScalarExpr(ASE->getIdx());
+ llvm::Value *Indices[] = {llvm::ConstantInt::get(Int32Ty, 0), Idx};
+ llvm::Type *ElemTy = CGM.getTypes().ConvertTypeForMem(ASE->getType());
+ llvm::Value *EltPtr = Builder.CreateInBoundsGEP(
+ Base.getElementType(), Base.emitRawPointer(*this), Indices, "arrayidx");
+ CharUnits Align = Base.getAlignment().alignmentOfArrayElement(
+ getContext().getTypeSizeInChars(ASE->getType()));
+ Address EltAddr(EltPtr, ElemTy, Align);
+ return MakeAddrLValue(EltAddr, ASE->getType());
+ }
+
+ // TODO: Tuple bindings (std::tuple, std::pair via tuple protocol)
+ // use hidden temporary variables that aren't captured in OpenMP
+ // regions. Need to re-emit the get<N>() call on the captured tuple
+ // base object.
+ llvm_unreachable("Unexpected structured binding type in OpenMP");
+}
+
LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
const NamedDecl *ND = E->getDecl();
QualType T = E->getType();
@@ -3772,96 +3827,11 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
if (const auto *BD = dyn_cast<BindingDecl>(ND)) {
if (E->refersToEnclosingVariableOrCapture()) {
// OpenMP case: binding was captured via its decomposed decl.
- if (auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
- if (CapturedStmtInfo &&
- CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
- CGM.getLangOpts().OpenMP) {
- auto I = LocalDeclMap.find(DD);
- if (I != LocalDeclMap.end()) {
- Address DDAddr = I->second;
- llvm::Type *ExpectedTy = CGM.getTypes().ConvertTypeForMem(
- DD->getType().getCanonicalType());
- if (DDAddr.getElementType() != ExpectedTy)
- DDAddr = DDAddr.withElementType(ExpectedTy);
- LValue CapLVal;
- if (DD->getType()->isReferenceType())
- CapLVal = EmitLoadOfReferenceLValue(DDAddr, DD->getType(),
- AlignmentSource::Decl);
- else
- CapLVal =
- MakeAddrLValue(DDAddr, DD->getType().getCanonicalType());
- if (getLangOpts().OpenMP &&
- CGM.getOpenMPRuntime().isNontemporalDecl(DD))
- CapLVal.setNontemporal(/*Value=*/true);
- // Extract the specific binding from the decomposed object.
- Expr *BindingExpr = BD->getBinding()->IgnoreImplicit();
- if (auto *ME = dyn_cast<MemberExpr>(BindingExpr)) {
- // Struct/union: access field.
- return EmitLValueForField(CapLVal,
- cast<FieldDecl>(ME->getMemberDecl()));
- }
- if (auto *ASE = dyn_cast<ArraySubscriptExpr>(BindingExpr)) {
- Address Base = CapLVal.getAddress();
- llvm::Value *Idx = EmitScalarExpr(ASE->getIdx());
- llvm::Value *Indices[] = {llvm::ConstantInt::get(Int32Ty, 0),
- Idx};
- llvm::Type *ElemTy =
- CGM.getTypes().ConvertTypeForMem(ASE->getType());
- llvm::Value *EltPtr = Builder.CreateInBoundsGEP(
- Base.getElementType(), Base.emitRawPointer(*this), Indices,
- "arrayidx");
- CharUnits Align = Base.getAlignment().alignmentOfArrayElement(
- getContext().getTypeSizeInChars(ASE->getType()));
- Address EltAddr(EltPtr, ElemTy, Align);
- return MakeAddrLValue(EltAddr, ASE->getType());
- }
- // Fallback for complex binding types.
- // TODO: Tuple bindings (std::tuple, std::pair via tuple protocol)
- // use hidden temporary variables that aren't captured in OpenMP
- // regions. Need to re-emit the get<N>() call on the captured tuple
- // base object. For now, this will fail.
- if (isa<DeclRefExpr>(BindingExpr))
- llvm_unreachable(
- "tuple-like structured bindings not yet supported in OpenMP");
- return EmitLValue(BindingExpr);
- }
- // DD not in LocalDeclMap, check capture struct
- if (auto *FD = CapturedStmtInfo->lookup(DD)) {
- LValue CapLVal = EmitCapturedFieldLValue(
- *this, FD, CapturedStmtInfo->getContextValue());
- Address Addr = CapLVal.getAddress();
- llvm::Type *ExpectedTy = CGM.getTypes().ConvertTypeForMem(
- DD->getType().getCanonicalType());
- if (Addr.getElementType() != ExpectedTy)
- Addr = Addr.withElementType(ExpectedTy);
- CapLVal = MakeAddrLValue(Addr, DD->getType().getCanonicalType());
- if (DD->getType()->isReferenceType())
- CapLVal = EmitLoadOfReferenceLValue(
- CapLVal.getAddress(), DD->getType(), AlignmentSource::Decl);
- if (getLangOpts().OpenMP &&
- CGM.getOpenMPRuntime().isNontemporalDecl(DD))
- CapLVal.setNontemporal(/*Value=*/true);
-
- // Extract the specific binding.
- Expr *BindingExpr = BD->getBinding()->IgnoreImplicit();
- if (auto *ME = dyn_cast<MemberExpr>(BindingExpr)) {
- return EmitLValueForField(CapLVal,
- cast<FieldDecl>(ME->getMemberDecl()));
- }
- if (auto *ASE = dyn_cast<ArraySubscriptExpr>(BindingExpr)) {
- Address Base = CapLVal.getAddress();
- llvm::Value *Idx = EmitScalarExpr(ASE->getIdx());
- llvm::Value *EltPtr = Builder.CreateInBoundsGEP(
- Base.getElementType(), Base.emitRawPointer(*this), Idx,
- "arrayidx");
- CharUnits Align = Base.getAlignment().alignmentOfArrayElement(
- getContext().getTypeSizeInChars(ASE->getType()));
- Address EltAddr(EltPtr, Base.getElementType(), Align);
- return MakeAddrLValue(EltAddr, ASE->getType());
- }
- return EmitLValue(BindingExpr);
- }
- }
+ if (CapturedStmtInfo &&
+ CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
+ CGM.getLangOpts().OpenMP) {
+ // OpenMP case: binding was captured via its decomposed decl.
+ return EmitOMPCapturedBindingLValue(BD);
}
// Non-OpenMP case: lambda capture.
auto *FD = LambdaCaptureFields.lookup(BD);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 29b87a0616992..b01f92c1b6a92 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4461,6 +4461,7 @@ class CodeGenFunction : public CodeGenTypeCache {
// Note: only available for agg return types
LValue EmitVAArgExprLValue(const VAArgExpr *E);
LValue EmitDeclRefLValue(const DeclRefExpr *E);
+ LValue EmitOMPCapturedBindingLValue(const BindingDecl *BD);
LValue EmitStringLiteralLValue(const StringLiteral *E);
LValue EmitObjCEncodeExprLValue(const ObjCEncodeExpr *E);
LValue EmitPredefinedLValue(const PredefinedExpr *E);
diff --git a/clang/test/OpenMP/structured-bindings-codegen.cpp b/clang/test/OpenMP/structured-bindings-codegen.cpp
index 6e895491ea5bd..f1ae96a273443 100644
--- a/clang/test/OpenMP/structured-bindings-codegen.cpp
+++ b/clang/test/OpenMP/structured-bindings-codegen.cpp
@@ -1,5 +1,6 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals all --filter-out-after "getelem.*kernel" --filter-out "= alloca.*" --include-generated-funcs --replace-value-regex "__omp_offloading_[0-9a-z]+_[0-9a-z]+" "reduction_size[.].+[.]" "pl_cond[.].+[.|,]" --prefix-filecheck-ir-name _ --global-value-regex "\.offload_.*" --global-hex-value-regex ".offload_maptypes.*" --version 6
// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 -x c++ -std=c++20 \
-// RUN: -emit-llvm %s -o - | FileCheck %s
+// RUN: -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s
// expected-no-diagnostics
@@ -17,9 +18,6 @@ void test_struct() {
use(m + n);
}
}
-// CHECK-LABEL: @{{.*}}test_struct{{.*}}.omp_outlined{{.*}}(
-// CHECK: getelementptr inbounds{{.*}}i32 0, i32 0
-// CHECK: getelementptr inbounds{{.*}}i32 0, i32 1
// Pair binding.
struct pair {
@@ -36,12 +34,6 @@ void test_pair() {
use(a);
}
}
-// CHECK-LABEL: @{{.*}}test_pair{{.*}}.omp_outlined{{.*}}(
-// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
-// CHECK: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
-// CHECK: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
-//
// Array binding.
void test_array() {
@@ -52,13 +44,6 @@ void test_array() {
use(x + y);
}
}
-// CHECK-LABEL: @{{.*}}test_array{{.*}}.omp_outlined{{.*}}(
-// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 0
-// CHECK: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
-// CHECK: [[ARRAYIDX1:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 1
-// CHECK: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
-//
// Binding with bitfields.
struct S {
@@ -73,15 +58,6 @@ void test_bitfields() {
use(a + b);
}
}
-// CHECK-LABEL: @{{.*}}test_bitfields{{.*}}.omp_outlined{{.*}}(
-// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[BF_LOAD:%.*]] = load i32, ptr [[TMP1]], align 4
-// CHECK: [[BF_SHL:%.*]] = shl i32 [[BF_LOAD]], 28
-// CHECK: [[BF_ASHR:%.*]] = ashr i32 [[BF_SHL]], 28
-// CHECK: [[BF_LOAD1:%.*]] = load i32, ptr [[TMP1]], align 4
-// CHECK: [[BF_SHL2:%.*]] = shl i32 [[BF_LOAD1]], 24
-// CHECK: [[BF_ASHR3:%.*]] = ashr i32 [[BF_SHL2]], 28
-//
// Lambda inside OpenMP with captured bindings.
void test_with_lambda() {
@@ -91,13 +67,6 @@ void test_with_lambda() {
for (int j = 0; j < 10; j++)
[m, n](int i, int j) -> void { return; }(i, j);
}
-// CHECK-LABEL: @{{.*}}test_with_lambda{{.*}}.omp_outlined{{.*}}(
-// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
-// CHECK: [[TMP13:%.*]] = load i32, ptr [[X]], align 4
-// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
-// CHECK: [[TMP15:%.*]] = load i32, ptr [[Y]], align 4
-//
// Only one binding used.
void test_partial_capture() {
@@ -107,12 +76,6 @@ void test_partial_capture() {
use(a);
}
}
-// CHECK-LABEL: @{{.*}}test_partial_capture{{.*}}.omp_outlined{{.*}}(
-// CHECK: [[TMP1:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
-// CHECK: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
-// CHECK: call void {{.*}}use{{.*}}"(i32 noundef [[TMP2]])
-//
// Nested parallel regions.
void test_nested() {
@@ -126,13 +89,6 @@ void test_nested() {
}
}
}
-// CHECK-LABEL: @{{.*}}test_nested{{.*}}.omp_outlined{{.*}}(
-// CHECK: [[TMP2:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
-// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
-// CHECK: call void @{{.*}}use{{.*}}"(i32 noundef [[TMP4]])
-//
// Multiple bindings in same region.
void test_multiple() {
@@ -143,19 +99,350 @@ void test_multiple() {
use(a + b + c + d);
}
}
-// CHECK-LABEL: @{{.*}}test_multiple{{.*}}.omp_outlined{{.*}}(
-// CHECK: [[TMP2:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[TMP3:%.*]] = load ptr, ptr {{.*}}, align 8
-// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
-// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
-// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
-// CHECK: [[TMP5:%.*]] = load i32, ptr [[Y]], align 4
-// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
-// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
-// CHECK: [[TMP6:%.*]] = load i32, ptr [[FIRST]], align 4
-// CHECK: [[ADD2:%.*]] = add nsw i32 [[ADD]], [[TMP6]]
-// CHECK: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP3]], i32 0, i32 1
-// CHECK: [[TMP7:%.*]] = load i32, ptr [[SECOND]], align 4
-// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP7]]
-// CHECK: call void {{.*}}use{{.*}}(i32 noundef [[ADD3]])
+// Reference structured binding.
+void test_reference_binding() {
+ Point p = make_point();
+ auto& [m, n] = p;
+#pragma omp parallel
+ { use(m); }
+}
+// CHECK-LABEL: define dso_local i64 @_Z10make_pointv(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[RETVAL:%.*]], i32 0, i32 0
+// CHECK: store i32 1, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[RETVAL]], i32 0, i32 1
+// CHECK: store i32 2, ptr [[Y]], align 4
+// CHECK: [[TMP0:%.*]] = load i64, ptr [[RETVAL]], align 4
+// CHECK: ret i64 [[TMP0]]
+//
+//
+// CHECK-LABEL: define dso_local void @_Z11test_structv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[CALL:%.*]] = call i64 @_Z10make_pointv()
+// CHECK: store i64 [[CALL]], ptr [[TMP0:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1:[0-9]+]], i32 1, ptr @_Z11test_structv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z11test_structv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1:[0-9]+]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2:![0-9]+]], !align [[META3:![0-9]+]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], [[TMP3]]
+// CHECK: call void @_Z3usei(i32 noundef [[ADD]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local i64 @_Z9make_pairii(
+// CHECK-SAME: i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[A]], ptr [[A_ADDR:%.*]], align 4
+// CHECK: store i32 [[B]], ptr [[B_ADDR:%.*]], align 4
+// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[RETVAL:%.*]], i32 0, i32 0
+// CHECK: [[TMP0:%.*]] = load i32, ptr [[A_ADDR]], align 4
+// CHECK: store i32 [[TMP0]], ptr [[FIRST]], align 4
+// CHECK: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[RETVAL]], i32 0, i32 1
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[B_ADDR]], align 4
+// CHECK: store i32 [[TMP1]], ptr [[SECOND]], align 4
+// CHECK: [[TMP2:%.*]] = load i64, ptr [[RETVAL]], align 4
+// CHECK: ret i64 [[TMP2]]
+//
+//
+// CHECK-LABEL: define dso_local void @_Z9test_pairv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[CALL:%.*]] = call i64 @_Z9make_pairii(i32 noundef 1, i32 noundef 2)
+// CHECK: store i64 [[CALL]], ptr [[TMP0:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z9test_pairv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z9test_pairv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z10test_arrayv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*]]:
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[ARR:%.*]], ptr align 4 @__const._Z10test_arrayv.arr, i64 8, i1 false)
+// CHECK: [[ARRAYINIT_BEGIN:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP0:%.*]], i64 0, i64 0
+// CHECK: br label %[[ARRAYINIT_BODY:.*]]
+// CHECK: [[ARRAYINIT_BODY]]:
+// CHECK: [[ARRAYINIT_INDEX:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[ARRAYINIT_NEXT:%.*]], %[[ARRAYINIT_BODY]] ]
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds i32, ptr [[ARRAYINIT_BEGIN]], i64 [[ARRAYINIT_INDEX]]
+// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw [2 x i32], ptr [[ARR]], i64 0, i64 [[ARRAYINIT_INDEX]]
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[TMP1]], align 4
+// CHECK: [[ARRAYINIT_NEXT]] = add nuw i64 [[ARRAYINIT_INDEX]], 1
+// CHECK: [[ARRAYINIT_DONE:%.*]] = icmp eq i64 [[ARRAYINIT_NEXT]], 2
+// CHECK: br i1 [[ARRAYINIT_DONE]], label %[[ARRAYINIT_END:.*]], label %[[ARRAYINIT_BODY]]
+// CHECK: [[ARRAYINIT_END]]:
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z10test_arrayv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z10test_arrayv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK: [[ARRAYIDX1:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], [[TMP3]]
+// CHECK: call void @_Z3usei(i32 noundef [[ADD]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z14test_bitfieldsv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[S:%.*]], ptr align 4 @__const._Z14test_bitfieldsv.s, i64 4, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[S]], i64 4, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z14test_bitfieldsv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z14test_bitfieldsv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[BF_LOAD:%.*]] = load i8, ptr [[TMP1]], align 4
+// CHECK: [[BF_SHL:%.*]] = shl i8 [[BF_LOAD]], 4
+// CHECK: [[BF_ASHR:%.*]] = ashr i8 [[BF_SHL]], 4
+// CHECK: [[BF_CAST:%.*]] = sext i8 [[BF_ASHR]] to i32
+// CHECK: [[BF_LOAD1:%.*]] = load i8, ptr [[TMP1]], align 4
+// CHECK: [[BF_ASHR2:%.*]] = ashr i8 [[BF_LOAD1]], 4
+// CHECK: [[BF_CAST3:%.*]] = sext i8 [[BF_ASHR2]] to i32
+// CHECK: [[ADD:%.*]] = add nsw i32 [[BF_CAST]], [[BF_CAST3]]
+// CHECK: call void @_Z3usei(i32 noundef [[ADD]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z16test_with_lambdav(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[CALL:%.*]] = call i64 @_Z10make_pointv()
+// CHECK: store i64 [[CALL]], ptr [[TMP0:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z16test_with_lambdav.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z16test_with_lambdav.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 99, ptr [[DOTOMP_UB:%.*]], align 4
+// CHECK: store i32 1, ptr [[DOTOMP_STRIDE:%.*]], align 4
+// CHECK: store i32 0, ptr [[DOTOMP_IS_LAST:%.*]], align 4
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[TMP2]], align 4
+// CHECK: call void @__kmpc_for_static_init_4(ptr @[[GLOB2:[0-9]+]], i32 [[TMP3]], i32 34, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP:%.*]] = icmp sgt i32 [[TMP4]], 99
+// CHECK: br i1 [[CMP]], label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
+// CHECK: [[COND_TRUE]]:
+// CHECK: br label %[[COND_END:.*]]
+// CHECK: [[COND_FALSE]]:
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: br label %[[COND_END]]
+// CHECK: [[COND_END]]:
+// CHECK: [[COND:%.*]] = phi i32 [ 99, %[[COND_TRUE]] ], [ [[TMP5]], %[[COND_FALSE]] ]
+// CHECK: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
+// CHECK: store i32 [[TMP6]], ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP2:%.*]] = icmp sle i32 [[TMP7]], [[TMP8]]
+// CHECK: br i1 [[CMP2]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[DIV:%.*]] = sdiv i32 [[TMP9]], 10
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[DIV]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[DIV3:%.*]] = sdiv i32 [[TMP11]], 10
+// CHECK: [[MUL4:%.*]] = mul nsw i32 [[DIV3]], 10
+// CHECK: [[SUB:%.*]] = sub nsw i32 [[TMP10]], [[MUL4]]
+// CHECK: [[MUL5:%.*]] = mul nsw i32 [[SUB]], 1
+// CHECK: [[ADD6:%.*]] = add nsw i32 0, [[MUL5]]
+// CHECK: store i32 [[ADD6]], ptr [[J:%.*]], align 4
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[CLASS_ANON:%.*]], ptr [[REF_TMP:%.*]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP13]], ptr [[TMP12]], align 4
+// CHECK: [[TMP14:%.*]] = getelementptr inbounds nuw [[CLASS_ANON]], ptr [[REF_TMP]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP15]], ptr [[TMP14]], align 4
+// CHECK: [[TMP16:%.*]] = load i32, ptr [[I]], align 4
+// CHECK: [[TMP17:%.*]] = load i32, ptr [[J]], align 4
+// CHECK: call void @"_ZZ16test_with_lambdavENK3$_0clEii"(ptr noundef nonnull align 4 dereferenceable(8) [[REF_TMP]], i32 noundef [[TMP16]], i32 noundef [[TMP17]])
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP18:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD7:%.*]] = add nsw i32 [[TMP18]], 1
+// CHECK: store i32 [[ADD7]], ptr [[DOTOMP_IV]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND]]
+// CHECK: [[OMP_INNER_FOR_END]]:
+// CHECK: br label %[[OMP_LOOP_EXIT:.*]]
+// CHECK: [[OMP_LOOP_EXIT]]:
+// CHECK: call void @__kmpc_for_static_fini(ptr @[[GLOB2]], i32 [[TMP3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z20test_partial_capturev(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[CALL:%.*]] = call i64 @_Z9make_pairii(i32 noundef 1, i32 noundef 2)
+// CHECK: store i64 [[CALL]], ptr [[TMP0:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z20test_partial_capturev.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z20test_partial_capturev.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z11test_nestedv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[CALL:%.*]] = call i64 @_Z10make_pointv()
+// CHECK: store i64 [[CALL]], ptr [[TMP0:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 2, ptr @_Z11test_nestedv.omp_outlined, ptr [[TMP0]], ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z11test_nestedv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP1:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP4]])
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z11test_nestedv.omp_outlined.omp_outlined, ptr [[TMP2]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z11test_nestedv.omp_outlined.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z13test_multiplev(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[CALL:%.*]] = call i64 @_Z10make_pointv()
+// CHECK: store i64 [[CALL]], ptr [[TMP0:%.*]], align 4
+// CHECK: [[CALL1:%.*]] = call i64 @_Z9make_pairii(i32 noundef 3, i32 noundef 4)
+// CHECK: store i64 [[CALL1]], ptr [[TMP1:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 2, ptr @_Z13test_multiplev.omp_outlined, ptr [[TMP0]], ptr [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z13test_multiplev.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP1:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
+// CHECK: [[FIRST:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[FIRST]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[ADD]], [[TMP6]]
+// CHECK: [[SECOND:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[SECOND]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP7]]
+// CHECK: call void @_Z3usei(i32 noundef [[ADD3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z22test_reference_bindingv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[CALL:%.*]] = call i64 @_Z10make_pointv()
+// CHECK: store i64 [[CALL]], ptr [[P:%.*]], align 4
+// CHECK: store ptr [[P]], ptr [[TMP0:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[TMP0]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z22test_reference_bindingv.omp_outlined, ptr [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z22test_reference_bindingv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0:%.*]]) #[[ATTR1]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[DOTGLOBAL_TID_]], ptr [[DOTGLOBAL_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[DOTBOUND_TID_]], ptr [[DOTBOUND_TID__ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
+// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
+// CHECK: [[CAPTURED_VAL:%.*]] = load ptr, ptr [[TMP]], align 8
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[CAPTURED_VAL]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 8
+// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: ret void
+//
>From 0aea9f294d8ecb54017a1fbbc5abb142d708ba34 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Thu, 30 Apr 2026 13:53:23 -0700
Subject: [PATCH 11/13] Fix format
---
clang/lib/CodeGen/CGExpr.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 9a0dc0a88c6be..38147ced873f8 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3595,7 +3595,7 @@ static bool canEmitSpuriousReferenceToVariable(CodeGenFunction &CGF,
LValue CodeGenFunction::EmitOMPCapturedBindingLValue(const BindingDecl *BD) {
assert(CapturedStmtInfo &&
CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
- CGM.getLangOpts().OpenMP);
+ CGM.getLangOpts().OpenMP);
auto *DD = cast<VarDecl>(BD->getDecomposedDecl());
auto I = LocalDeclMap.find(DD);
assert(I != LocalDeclMap.end() && "Decomposed decl not in LocalDeclMap");
>From 340302fc9bca1e83f4a1eceb3f16f3e7dbc8d194 Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Fri, 1 May 2026 09:06:41 -0700
Subject: [PATCH 12/13] Addressed review comments
---
clang/lib/CodeGen/CGExpr.cpp | 22 +++++--------------
.../OpenMP/structured-bindings-codegen.cpp | 5 +++--
2 files changed, 9 insertions(+), 18 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 38147ced873f8..cabde70c31a9b 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3597,26 +3597,16 @@ LValue CodeGenFunction::EmitOMPCapturedBindingLValue(const BindingDecl *BD) {
CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
CGM.getLangOpts().OpenMP);
auto *DD = cast<VarDecl>(BD->getDecomposedDecl());
- auto I = LocalDeclMap.find(DD);
- assert(I != LocalDeclMap.end() && "Decomposed decl not in LocalDeclMap");
-
- Address ParamAddr = I->second;
QualType AggregType = DD->getType();
if (AggregType->isReferenceType())
AggregType = AggregType->getPointeeType();
+ DeclarationNameInfo NameInfo(DD->getDeclName(), SourceLocation());
+ DeclRefExpr *DRE = DeclRefExpr::Create(
+ getContext(), NestedNameSpecifierLoc(), SourceLocation(), DD,
+ /*RefersToEnclosingVariableOrCapture=*/false, NameInfo, AggregType,
+ VK_LValue);
+ LValue CapLVal = EmitLValue(DRE);
- LValue CapLVal;
- llvm::Type *ParamLLVMType = ParamAddr.getElementType();
- if (ParamLLVMType->isPointerTy()) {
- llvm::Value *Ptr = Builder.CreateLoad(ParamAddr, "captured.val");
- Address AggregAddr(Ptr, ConvertTypeForMem(AggregType),
- getContext().getDeclAlign(DD));
- CapLVal = MakeAddrLValue(AggregAddr, AggregType);
- } else {
- Address AggregAddr(ParamAddr.emitRawPointer(*this),
- ConvertTypeForMem(AggregType), ParamAddr.getAlignment());
- CapLVal = MakeAddrLValue(AggregAddr, AggregType);
- }
// Extract the specific binding from the decomposed object.
Expr *BindingExpr = BD->getBinding()->IgnoreImplicit();
if (auto *ME = dyn_cast<MemberExpr>(BindingExpr)) {
diff --git a/clang/test/OpenMP/structured-bindings-codegen.cpp b/clang/test/OpenMP/structured-bindings-codegen.cpp
index f1ae96a273443..3c6cfdd35dd16 100644
--- a/clang/test/OpenMP/structured-bindings-codegen.cpp
+++ b/clang/test/OpenMP/structured-bindings-codegen.cpp
@@ -440,9 +440,10 @@ void test_reference_binding() {
// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META2]], !align [[META3]]
// CHECK: [[CAPTURED_VAL:%.*]] = load ptr, ptr [[TMP]], align 8
// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[CAPTURED_VAL]], i32 0, i32 0
-// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 8
-// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 8
+// CHECK: call void @_Z3usei(i32 noundef [[TMP3]])
// CHECK: ret void
//
>From 61d5d1a3894f6b46bb5d4a8d707a87b2aeeff8fb Mon Sep 17 00:00:00 2001
From: Ammarguellat <zahira.ammarguellat at intel.com>
Date: Fri, 1 May 2026 13:13:31 -0700
Subject: [PATCH 13/13] Fix LIT fail
---
clang/lib/CodeGen/CGExpr.cpp | 14 +++++++++++---
clang/test/OpenMP/structured-bindings-codegen.cpp | 5 ++---
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index cabde70c31a9b..bac486a557721 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3603,17 +3603,25 @@ LValue CodeGenFunction::EmitOMPCapturedBindingLValue(const BindingDecl *BD) {
DeclarationNameInfo NameInfo(DD->getDeclName(), SourceLocation());
DeclRefExpr *DRE = DeclRefExpr::Create(
getContext(), NestedNameSpecifierLoc(), SourceLocation(), DD,
- /*RefersToEnclosingVariableOrCapture=*/false, NameInfo, AggregType,
+ /*RefersToEnclosingVariableOrCapture=*/true, NameInfo, AggregType,
VK_LValue);
LValue CapLVal = EmitLValue(DRE);
-
+ QualType CanonType = AggregType.getCanonicalType();
+ llvm::Type *StructTy = CGM.getTypes().ConvertTypeForMem(CanonType);
+ Address Addr = CapLVal.getAddress();
+ if (Addr.getElementType() != StructTy) {
+ Addr = Addr.withElementType(StructTy);
+ CapLVal = MakeAddrLValue(Addr, CanonType, CapLVal.getBaseInfo(),
+ CapLVal.getTBAAInfo());
+ }
// Extract the specific binding from the decomposed object.
Expr *BindingExpr = BD->getBinding()->IgnoreImplicit();
if (auto *ME = dyn_cast<MemberExpr>(BindingExpr)) {
// Struct/union: access field.
FieldDecl *Field = cast<FieldDecl>(ME->getMemberDecl());
return EmitLValueForField(CapLVal, Field);
- } else if (auto *ASE = dyn_cast<ArraySubscriptExpr>(BindingExpr)) {
+ }
+ if (auto *ASE = dyn_cast<ArraySubscriptExpr>(BindingExpr)) {
// Array binding - access element.
Address Base = CapLVal.getAddress();
llvm::Value *Idx = EmitScalarExpr(ASE->getIdx());
diff --git a/clang/test/OpenMP/structured-bindings-codegen.cpp b/clang/test/OpenMP/structured-bindings-codegen.cpp
index 3c6cfdd35dd16..bd1f463f28615 100644
--- a/clang/test/OpenMP/structured-bindings-codegen.cpp
+++ b/clang/test/OpenMP/structured-bindings-codegen.cpp
@@ -441,9 +441,8 @@ void test_reference_binding() {
// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META2]], !align [[META3]]
// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
// CHECK: [[TMP2:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META2]], !align [[META3]]
-// CHECK: [[CAPTURED_VAL:%.*]] = load ptr, ptr [[TMP]], align 8
-// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[CAPTURED_VAL]], i32 0, i32 0
-// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 8
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 4
// CHECK: call void @_Z3usei(i32 noundef [[TMP3]])
// CHECK: ret void
//
More information about the cfe-commits
mailing list