https://github.com/zahiraam updated https://github.com/llvm/llvm-project/pull/190832
>From ca43027b4156c15d34c7b6b75ed8420ccc0c1f2e Mon Sep 17 00:00:00 2001 From: Ammarguellat <[email protected]> Date: Tue, 7 Apr 2026 12:10:39 -0700 Subject: [PATCH 1/2] [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 cc2b511f39d5f96318960733aa92cecdeacd7b62 Mon Sep 17 00:00:00 2001 From: Ammarguellat <[email protected]> Date: Tue, 7 Apr 2026 12:31:59 -0700 Subject: [PATCH 2/2] Fix format --- clang/lib/Sema/SemaExpr.cpp | 6 +++--- clang/lib/Sema/SemaStmt.cpp | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 46a93b8e53d3e..2fb31c3a8c861 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -19374,8 +19374,8 @@ static bool isVariableCapturable(CapturingScopeInfo *CSI, ValueDecl *Var, } if (isa<BindingDecl>(Var)) { - if (Var->getDeclName() && !Var->isImplicit()) - return true; + if (Var->getDeclName() && !Var->isImplicit()) + return true; if (!IsLambda || !S.getLangOpts().CPlusPlus) { if (Diagnose) diagnoseUncapturableValueReferenceOrBinding(S, Loc, Var); @@ -19516,7 +19516,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..b3521741ddebc 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()); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
