[clang] [OpenMP] Support capturing structured bindings in OpenMP regions. (PR #190832)

Zahira Ammarguellat via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 29 07:51:23 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 1/6] [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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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;



More information about the cfe-commits mailing list