llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-codegen Author: Zahira Ammarguellat (zahiraam) <details> <summary>Changes</summary> This patch adds support for capturing structured bindings (C++20) in `OpenMP` parallel regions, which previously resulted in an error: `error: capturing a structured binding is not yet supported in OpenMP` --- Full diff: https://github.com/llvm/llvm-project/pull/190832.diff 7 Files Affected: - (modified) clang/lib/CodeGen/CGExpr.cpp (+18-2) - (modified) clang/lib/Sema/SemaExpr.cpp (+8-8) - (modified) clang/lib/Sema/SemaStmt.cpp (+8-5) - (added) clang/test/OpenMP/structured-binding-capture.cpp (+141) - (modified) clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp (-8) - (modified) clang/test/SemaCXX/cxx1z-decomposition.cpp (+6-8) - (modified) clang/test/SemaCXX/decomposition-openmp.cpp (+4-3) ``````````diff diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 23802cdeb4811..1e74e49702932 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -3737,8 +3737,24 @@ 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())); + } + auto *FD = LambdaCaptureFields.lookup(BD); + 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 c9642ed298bf3..d7a4f00fd36d7 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() && !IsBlock) + 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..60d4214bb3c8a 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); - Captures.push_back(CapturedStmt::Capture( - Cap.getLocation(), - Cap.isReferenceCapture() ? CapturedStmt::VCK_ByRef - : CapturedStmt::VCK_ByCopy, - cast<VarDecl>(Cap.getVariable()))); + 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))); } 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} +//. diff --git a/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp b/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp index aa8d055e44971..6d54ae5dc4a30 100644 --- a/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp +++ b/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp @@ -385,10 +385,6 @@ namespace GH145956 { Pair p = {1, 2}; auto const& [key, value] = p; return [&] { return key; }(); -#if __cpp_constexpr < 202002L - // expected-warning@-2 {{captured structured bindings are a C++20 extension}} - // expected-note@-4 {{'key' declared here}} -#endif } static_assert(f() == 1); constexpr auto retlambda() { @@ -396,10 +392,6 @@ namespace GH145956 { Pair p = {1, 2}; auto const& [key, value] = p; return [=] { return key; }; -#if __cpp_constexpr < 202002L - // expected-warning@-2 {{captured structured bindings are a C++20 extension}} - // expected-note@-4 {{'key' declared here}} -#endif } constexpr auto lambda = retlambda(); static_assert(lambda() == 1); diff --git a/clang/test/SemaCXX/cxx1z-decomposition.cpp b/clang/test/SemaCXX/cxx1z-decomposition.cpp index 6425f1ee7796e..b022fc9222417 100644 --- a/clang/test/SemaCXX/cxx1z-decomposition.cpp +++ b/clang/test/SemaCXX/cxx1z-decomposition.cpp @@ -67,14 +67,14 @@ auto [outerbit1, outerbit2] = S1{1, 2}; // expected-note {{declared here}} void enclosing() { struct S { int a = outer1; }; - auto [n] = S(); // expected-note 3{{'n' declared here}} + auto [n] = S(); // expected-note {{'n' declared here}} struct Q { int f() { return n; } // expected-error {{reference to local binding 'n' declared in enclosing function 'enclosing'}} }; - (void)[&] { return n; }; // expected-warning {{C++20}} - (void)[n] { return n; }; // expected-warning {{C++20}} + (void)[&] { return n; }; + (void)[n] { return n; }; static auto [m] = S(); // expected-note {{'m' declared here}} \ // expected-warning {{C++20}} @@ -85,10 +85,9 @@ void enclosing() { (void)[outerbit1]{}; // expected-error {{'outerbit1' cannot be captured because it does not have automatic storage duration}} - auto [bit, var] = S2{-1, 1}; // expected-note 2{{'bit' declared here}} + auto [bit, var] = S2{-1, 1}; - (void)[&bit] { // expected-error {{non-const reference cannot bind to bit-field 'a'}} \ - // expected-warning {{C++20}} + (void)[&bit] { // expected-error {{non-const reference cannot bind to bit-field 'a'}} return bit; }; @@ -97,8 +96,7 @@ void enclosing() { }; (void)[&] { return bit + u; } // expected-error {{unnamed variable cannot be implicitly captured in a lambda expression}} \ - // expected-error {{non-const reference cannot bind to bit-field 'a'}} \ - // expected-warning {{C++20}} + // expected-error {{non-const reference cannot bind to bit-field 'a'}} (); } diff --git a/clang/test/SemaCXX/decomposition-openmp.cpp b/clang/test/SemaCXX/decomposition-openmp.cpp index 2185f3db83d4e..7a379c2d3b1ce 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] = {}; @@ -20,13 +22,12 @@ void g() { } } -// FIXME: OpenMP should support capturing structured bindings +// Okay. void h() { int i[2] = {}; - auto [a, b] = i; // expected-note 2{{declared here}} + auto [a, b] = i; #pragma omp parallel { - // expected-error@+1 2{{capturing a structured binding is not yet supported in OpenMP}} foo(a + b); } } `````````` </details> https://github.com/llvm/llvm-project/pull/190832 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
