[clang] 93e1bed - [OpenMP] Support capturing structured bindings in OpenMP regions. (#190832)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 22 06:22:10 PDT 2026
Author: Zahira Ammarguellat
Date: 2026-09-22T06:21:59-07:00
New Revision: 93e1bed81a47c0b3c3df6de2d5f7d7e4f450ae87
URL: https://github.com/llvm/llvm-project/commit/93e1bed81a47c0b3c3df6de2d5f7d7e4f450ae87
DIFF: https://github.com/llvm/llvm-project/commit/93e1bed81a47c0b3c3df6de2d5f7d7e4f450ae87.diff
LOG: [OpenMP] Support capturing structured bindings in OpenMP regions. (#190832)
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`
Added:
clang/test/OpenMP/structured-bindings-ast.cpp
clang/test/OpenMP/structured-bindings-codegen.cpp
clang/test/OpenMP/structured-bindings-messages.cpp
clang/test/OpenMP/structured-bindings-nontemporal.cpp
clang/test/OpenMP/structured-bindings-target-map-different-types.cpp
clang/test/OpenMP/structured-bindings-template-instantiation.cpp
Modified:
clang/docs/OpenMPSupport.md
clang/docs/ReleaseNotes.md
clang/include/clang/AST/DeclCXX.h
clang/include/clang/AST/TextNodeDumper.h
clang/include/clang/Basic/Attr.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/AST/DeclCXX.cpp
clang/lib/AST/TextNodeDumper.cpp
clang/lib/CodeGen/CGExpr.cpp
clang/lib/CodeGen/CGOpenMPRuntime.cpp
clang/lib/CodeGen/CGStmtOpenMP.cpp
clang/lib/CodeGen/CodeGenFunction.h
clang/lib/Sema/SemaExpr.cpp
clang/lib/Sema/SemaLambda.cpp
clang/lib/Sema/SemaOpenMP.cpp
clang/lib/Sema/SemaStmt.cpp
clang/test/SemaCXX/decomposition-openmp.cpp
clang/utils/TableGen/ClangAttrEmitter.cpp
Removed:
################################################################################
diff --git a/clang/docs/OpenMPSupport.md b/clang/docs/OpenMPSupport.md
index a04c1085a87ca..1cc39fabe948a 100644
--- a/clang/docs/OpenMPSupport.md
+++ b/clang/docs/OpenMPSupport.md
@@ -41,6 +41,19 @@ see {ref}`OpenMP implementation details <openmp-implementation-details>` and
known at compile time. To prevent this conservative choice and use
at most 32 bits, compile your program with the
`-fopenmp-optimistic-collapse`.
+- C++20 structured bindings are now supported in OpenMP constructs.
+ Bindings from structured binding declarations can be used in data-sharing
+ clauses (``private``, ``firstprivate``, ``lastprivate``, ``shared``,
+ ``linear``), and in ``map`` clauses for target directives.
+ Limitations: tuple-like bindings (using the tuple protocol with ``get<N>()``)
+ are not yet supported; conditional lastprivate and reductions are not yet
+ supported.
+ Important restriction for target regions: if the original variable is
+ explicitly mapped (e.g., ``map(tofrom: t)``) but only bindings from it,
+ and not the original variable itself, are used in the target region, the
+ compiler emits an error. Either use the original variable directly in the
+ target region, or map the bindings explicitly instead of the original
+ variable.
## GPU devices support
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ba944a62a1fff..68ab4fb414c56 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -985,6 +985,21 @@ The `alpha.cplusplus.UseAfterLifetimeEnd` checker was renamed to `alpha.core.Use
conditions to `llvm.assume`, enabling downstream optimizations. Previously
the clause was parsed but its condition was discarded without effect.
+- Added support for capturing structured bindings in OpenMP regions
+ (a C++20 extension; warned as an extension in C++17). Individual bindings
+ form aggregate decompositions(structs, classes, and arrays) can now be used
+ in data-sharing clauses (``private``, ``firstprivate``, ``lastprivate``,
+ ``shared``, ``linear``) and ``map`` clauses for target directives.
+ Tuple-like bindings (types using the tuple protocol with ``get<N>()``,
+ such as ``std::pair`` or ``std::tuple``) are not yet supported and
+ will produce a compilation error. Reduction clauses with structured bindings
+ are not yet supported.
+ When the original variable is explicitly mapped in a target region
+ but only bindings from it are used (not the original variable itself),
+ modifications to the bindings will not be reflected in the original variable.
+ To ensure correct behavior, either use the original variable directly in the
+ target region or map the bindings explicitly instead.
+
### SYCL Support
#### Improvements
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index ff2223070dc15..6941f0179647a 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -4344,6 +4344,24 @@ class DecompositionDecl final
void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override;
+ /// Result of attempting to extract the original variable from a
+ /// DecompositionDecl.
+ struct OriginalVarResult {
+ enum DiagnosticKind {
+ CallExpr = 0, // Function call.
+ InitListExpr = 1, // Initializer list.
+ Temporary = 2, // Temporary object.
+ MoveExpr = 3, // Move expression.
+ };
+ const VarDecl *Var = nullptr;
+ DiagnosticKind DiagKind = Temporary;
+ };
+
+ /// If this decomposition was initialized from a variable (e.g., auto [a,b] =
+ /// p), returns the variable. Otherwise returns nullptr with a diagnostic kind
+ /// indicating why extraction failed.
+ OriginalVarResult getOriginalVar() const;
+
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == Decomposition; }
};
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 7219a0d3f8e50..592aa2f1bc942 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -439,6 +439,7 @@ class TextNodeDumper
void VisitOpenACCDeclareDecl(const OpenACCDeclareDecl *D);
void VisitOpenACCRoutineDecl(const OpenACCRoutineDecl *D);
void VisitOpenACCRoutineDeclAttr(const OpenACCRoutineDeclAttr *A);
+ void VisitOMPCaptureKindAttr(const OMPCaptureKindAttr *A);
void VisitEmbedExpr(const EmbedExpr *S);
void VisitAtomicExpr(const AtomicExpr *AE);
void VisitConvertVectorExpr(const ConvertVectorExpr *S);
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 569475f4f74fe..b9eb41654a81b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -728,6 +728,10 @@ class Attr {
// typical attribute serialization. This will cause tablegen to emit a call to
// ASTRecordWriter::Add<Name>Attr and ASTRecordReader::read<Name>Attr.
bit HasCustomSerialization = 0;
+ // Set to true if this attribute has a custom text node dump implementation
+ // in TextNodeDumper.cpp. This will prevent the automatic generation of a
+ // Visit method in AttrTextNodeDump.inc.
+ bit HasCustomTextNodeDump = 0;
// Set to true if all of the attribute's arguments should be parsed in an
// unevaluated context.
bit ParseArgumentsAsUnevaluated = 0;
@@ -4782,6 +4786,7 @@ def OMPCaptureKind : Attr {
let SemaHandler = 0;
let Args = [UnsignedArgument<"CaptureKindVal">];
let Documentation = [InternalOnly];
+ let HasCustomTextNodeDump = 1;
let AdditionalMembers = [{
llvm::omp::Clause getCaptureKind() const {
return static_cast<llvm::omp::Clause>(getCaptureKindVal());
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 7190ba9d92685..071cb41fd6902 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10304,8 +10304,8 @@ def err_reference_to_local_in_enclosing_context : Error<
"%select{%3|block literal|lambda expression|context}2">;
def err_local_nested_class_invalid_scope : Error<
"nested local class %0 must be defined in the same block scope as %1">;
-def err_capture_binding_openmp : Error<
- "capturing a structured binding is not yet supported in OpenMP">;
+def err_capture_tuple_binding_openmp : Error<
+ "capturing tuple-like structured binding %0 is not yet supported in OpenMP">;
def err_static_data_member_not_allowed_in_local_class : Error<
"static data member %0 not allowed in local %sub{select_tag_type_kind}2 %1">;
@@ -12416,6 +12416,12 @@ def err_omp_parent_cancel_region_ordered : Error<
"parent region for 'omp %select{cancellation point|cancel}0' construct cannot be ordered">;
def err_omp_reduction_wrong_type : Error<"reduction type cannot be %select{qualified with 'const', 'volatile' or 'restrict'|a function|a reference|an array}0 type">;
def err_omp_wrong_var_in_declare_reduction : Error<"only %select{'omp_priv' or 'omp_orig'|'omp_in' or 'omp_out'}0 variables are allowed in %select{initializer|combiner}0 expression">;
+def err_omp_unsupported_on_binding : Error<
+ "%select{reductions|conditional lastprivate|linear clause with parallel constructs|capturing in a lambda or block}0 on structured bindings "
+ "%plural{0:are|:is}0 not yet supported">;
+def err_omp_loop_var_is_structured_binding : Error<
+ "structured binding %0 cannot be used as the loop control variable of an "
+ "OpenMP loop">;
def err_omp_declare_reduction_redefinition : Error<"redefinition of user-defined reduction for type %0">;
def err_omp_mapper_wrong_type : Error<
"mapper type must be of struct, union or class type">;
@@ -12708,6 +12714,18 @@ def note_omp_flush_order_clause_here : Note<
"memory order clause '%0' is specified here">;
def err_omp_non_lvalue_in_map_or_motion_clauses: Error<
"expected addressable lvalue in '%0' clause">;
+def err_omp_unsupported_structured_binding_init : Error<
+ "mapping of %select{structured binding initialized from function call|"
+ "structured binding initialized from initializer list|"
+ "structured binding initialized from temporary object|"
+ "structured binding initialized from move expression|"
+ "tuple-like structured binding}0 is not yet supported">;
+def err_omp_decomposition_bindings_
diff erent_capture_kinds : Error<
+ "bindings from structured binding %0 require conflicting capture kinds "
+ "(by-reference vs. by-copy)">;
+def err_omp_original_var_mapped_bindings_only_used : Error<
+ "original variable %0 is explicitly mapped but only bindings from it are used "
+ "in target region; either use %0 directly or map the bindings explicitly">;
def err_omp_var_expected : Error<
"expected variable of the '%0' type%select{|, not %2}1">;
def err_omp_non_pointer_type_array_shaping_base : Error<
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5becfc9fae152..5f24adbf9e9cf 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9254,7 +9254,8 @@ class Sema final : public SemaBase {
const sema::Capture &From);
/// Build a FieldDecl suitable to hold the given capture.
- FieldDecl *BuildCaptureField(RecordDecl *RD, const sema::Capture &Capture);
+ FieldDecl *BuildCaptureField(RecordDecl *RD, const sema::Capture &Capture,
+ bool IsOpenMP = false);
/// Initialize the given capture with a suitable expression.
ExprResult BuildCaptureInit(const sema::Capture &Capture,
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index f0da56542ae7e..ac468f6879f7b 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3775,6 +3775,65 @@ void DecompositionDecl::printName(llvm::raw_ostream &OS,
OS << ']';
}
+DecompositionDecl::OriginalVarResult DecompositionDecl::getOriginalVar() const {
+ OriginalVarResult Result;
+ const Expr *Init = getInit();
+ if (!Init)
+ return Result;
+
+ // Helper to determine diagnostic kind from a CallExpr based on value
+ // category.
+ auto GetDiagKindFromCall = [](const CallExpr *Call) {
+ return Call->isXValue() ? OriginalVarResult::MoveExpr
+ : OriginalVarResult::CallExpr;
+ };
+ const Expr *Stripped = Init->IgnoreParenImpCasts();
+ if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Stripped))
+ Stripped = BTE->getSubExpr()->IgnoreParenImpCasts();
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(Stripped)) {
+ Result.Var = dyn_cast<VarDecl>(DRE->getDecl());
+ return Result;
+ }
+ if (const auto *CE = dyn_cast<CXXConstructExpr>(Stripped)) {
+ if (CE->getNumArgs() == 1) {
+ const Expr *ArgStripped = CE->getArg(0)->IgnoreParenImpCasts();
+ if (const auto *ArgDRE = dyn_cast<DeclRefExpr>(ArgStripped)) {
+ Result.Var = dyn_cast<VarDecl>(ArgDRE->getDecl());
+ return Result;
+ }
+ if (const auto *Call = dyn_cast<CallExpr>(ArgStripped))
+ Result.DiagKind = GetDiagKindFromCall(Call);
+ else
+ Result.DiagKind = OriginalVarResult::Temporary;
+ return Result;
+ }
+ }
+ Result.DiagKind = OriginalVarResult::Temporary;
+ if (const auto *Call = dyn_cast<CallExpr>(Stripped)) {
+ Result.DiagKind = GetDiagKindFromCall(Call);
+ } else if (isa<InitListExpr, CXXStdInitializerListExpr>(Stripped)) {
+ Result.DiagKind = OriginalVarResult::InitListExpr;
+ } else if (const auto *FCE = dyn_cast<CXXFunctionalCastExpr>(Stripped)) {
+ const Expr *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
+ if (isa<InitListExpr>(SubExpr)) {
+ Result.DiagKind = OriginalVarResult::InitListExpr;
+ } else if (const auto *Call = dyn_cast<CallExpr>(SubExpr)) {
+ Result.DiagKind = GetDiagKindFromCall(Call);
+ } else if (const auto *CE = dyn_cast<CXXConstructExpr>(SubExpr)) {
+ if (CE->getNumArgs() == 1) {
+ if (const auto *ArgCall =
+ dyn_cast<CallExpr>(CE->getArg(0)->IgnoreParenImpCasts()))
+ Result.DiagKind = GetDiagKindFromCall(ArgCall);
+ }
+ }
+ } else if (isa<MaterializeTemporaryExpr, CXXBindTemporaryExpr>(Stripped)) {
+ Result.DiagKind = OriginalVarResult::Temporary;
+ } else if (Stripped->isXValue()) {
+ Result.DiagKind = OriginalVarResult::MoveExpr;
+ }
+ return Result;
+}
+
void MSPropertyDecl::anchor() {}
MSPropertyDecl *MSPropertyDecl::Create(ASTContext &C, DeclContext *DC,
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 70008d531177d..45981683ad171 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -3412,6 +3412,10 @@ void TextNodeDumper::VisitOpenACCRoutineDeclAttr(
});
}
+void TextNodeDumper::VisitOMPCaptureKindAttr(const OMPCaptureKindAttr *A) {
+ OS << " " << llvm::omp::getOpenMPClauseName(A->getCaptureKind());
+}
+
void TextNodeDumper::VisitEmbedExpr(const EmbedExpr *S) {
AddChild("begin", [=] { OS << S->getStartingElementPos(); });
AddChild("number of elements", [=] { OS << S->getDataElementCount(); });
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 2a28ae041cee0..aca0415d7f544 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3615,6 +3615,69 @@ 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 && "Expected to be inside a captured region");
+ assert(CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
+ "Expected OpenMP captured region");
+ assert(CGM.getLangOpts().OpenMP && "Expected OpenMP to be enabled");
+
+ if (auto It = LocalDeclMap.find(BD->getCanonicalDecl());
+ It != LocalDeclMap.end())
+ return MakeAddrLValue(It->second, BD->getType());
+
+ const auto *DD = cast<VarDecl>(BD->getDecomposedDecl());
+
+ // Use getNonReferenceType() because we need the actual object type, not the
+ // reference type. DeclRefExpr with VK_LValue requires a non-reference type
+ // (AST invariant). EmitDeclRefLValue will load any reference for us.
+ QualType DREType = DD->getType().getNonReferenceType();
+ DeclRefExpr DRE(getContext(), const_cast<VarDecl *>(DD),
+ /*RefersToEnclosingVariableOrCapture=*/true, DREType,
+ VK_LValue, SourceLocation());
+ LValue BaseLVal = EmitDeclRefLValue(&DRE);
+
+ // Ensure the Address has the correct element type for DD's type.
+ // EmitDeclRefLValue might return an address with a
diff erent element type
+ // if reference unwrapping occurred.
+ Address BaseAddr = BaseLVal.getAddress();
+ QualType DDType = DD->getType();
+ llvm::Type *ExpectedTy = CGM.getTypes().ConvertTypeForMem(DDType);
+ if (BaseAddr.getElementType() != ExpectedTy)
+ BaseAddr = BaseAddr.withElementType(ExpectedTy);
+
+ // Now emit the binding expression (array subscript, member access, etc.)
+ // by temporarily installing the decomposed storage address, then routing
+ // through EmitLValue for the binding expression.
+ Expr *BindingExpr = BD->getBinding();
+ auto It = LocalDeclMap.find(DD);
+ bool WasMapped = It != LocalDeclMap.end();
+ Address SavedAddr = WasMapped ? It->second : Address::invalid();
+ Address MapAddr = BaseAddr;
+ if (DD->getType()->isReferenceType()) {
+ RawAddress RefSlot = CreateMemTemp(DD->getType(), "omp.binding.ref");
+ Builder.CreateStore(BaseAddr.emitRawPointer(*this), RefSlot);
+ MapAddr = RefSlot;
+ }
+ if (WasMapped)
+ It->second = MapAddr;
+ else
+ LocalDeclMap.insert({DD, MapAddr});
+ llvm::scope_exit Guard([&] {
+ if (WasMapped) {
+ auto RestoreIt = LocalDeclMap.find(DD);
+ assert(RestoreIt != LocalDeclMap.end() && "DD should still be in map");
+ RestoreIt->second = SavedAddr;
+ } else {
+ LocalDeclMap.erase(DD);
+ }
+ });
+
+ return EmitLValue(BindingExpr);
+}
+
LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
const NamedDecl *ND = E->getDecl();
QualType T = E->getType();
@@ -3797,6 +3860,33 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
// an enclosing scope.
if (const auto *BD = dyn_cast<BindingDecl>(ND)) {
if (E->refersToEnclosingVariableOrCapture()) {
+ auto ApplyNontemporal = [&](LValue LV) {
+ if (getLangOpts().OpenMP &&
+ CGM.getOpenMPRuntime().isNontemporalDecl(BD))
+ LV.setNontemporal(/*Value=*/true);
+ return LV;
+ };
+
+ // Try direct lookup first.
+ auto It = LocalDeclMap.find(BD->getCanonicalDecl());
+ if (It != LocalDeclMap.end()) {
+ return ApplyNontemporal(
+ MakeAddrLValue(It->second, E->getType(), AlignmentSource::Decl));
+ }
+
+ // OpenMP case: binding was captured via its decomposed decl.
+ if (CapturedStmtInfo &&
+ CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP &&
+ CGM.getLangOpts().OpenMP) {
+ auto NameIt = OMPPrivatizedBindings.find(
+ cast<BindingDecl>(BD->getCanonicalDecl()));
+ if (NameIt != OMPPrivatizedBindings.end()) {
+ return ApplyNontemporal(MakeAddrLValue(NameIt->second, E->getType(),
+ AlignmentSource::Decl));
+ }
+ return ApplyNontemporal(EmitOMPCapturedBindingLValue(BD));
+ }
+ // Non-OpenMP case: lambda capture.
auto *FD = LambdaCaptureFields.lookup(BD);
return EmitCapturedFieldLValue(*this, FD, CXXABIThisValue);
}
diff --git a/clang/lib/CodeGen/CGOpenMPRuntime.cpp b/clang/lib/CodeGen/CGOpenMPRuntime.cpp
index 862274bf89355..6de9fea26954e 100644
--- a/clang/lib/CodeGen/CGOpenMPRuntime.cpp
+++ b/clang/lib/CodeGen/CGOpenMPRuntime.cpp
@@ -1895,7 +1895,11 @@ void CGOpenMPRuntime::registerVTable(const OMPExecutableDirective &D) {
const VarDecl *VD = nullptr;
if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
- VD = cast<VarDecl>(DRE->getDecl());
+ // Handle BindingDecls by redirecting to their DecompositionDecl.
+ if (auto *BD = dyn_cast<BindingDecl>(DRE->getDecl()))
+ VD = cast<VarDecl>(BD->getDecomposedDecl());
+ else
+ VD = cast<VarDecl>(DRE->getDecl());
} else if (auto *MRE = dyn_cast<MemberExpr>(E)) {
if (auto *BaseDRE = dyn_cast<DeclRefExpr>(MRE->getBase())) {
if (auto *BaseVD = dyn_cast<VarDecl>(BaseDRE->getDecl()))
@@ -3034,6 +3038,14 @@ struct PrivateHelpersTy {
typedef std::pair<CharUnits /*Align*/, PrivateHelpersTy> PrivateDataTy;
} // anonymous namespace
+/// For BindingDecls, returns the DecomposedDecl as the original VarDecl.
+/// For regular VarDecls, returns the VarDecl itself.
+static const VarDecl *getOriginalVarDecl(const ValueDecl *Decl) {
+ if (const auto *BD = dyn_cast<BindingDecl>(Decl))
+ return cast<VarDecl>(BD->getDecomposedDecl());
+ return cast<VarDecl>(Decl);
+}
+
static bool isAllocatableDecl(const VarDecl *VD) {
const VarDecl *CVD = VD->getCanonicalDecl();
if (!CVD->hasAttr<OMPAllocateDeclAttr>())
@@ -3055,7 +3067,18 @@ createPrivatesRecordDecl(CodeGenModule &CGM, ArrayRef<PrivateDataTy> Privates) {
RD->startDefinition();
for (const auto &Pair : Privates) {
const VarDecl *VD = Pair.second.Original;
- QualType Type = VD->getType().getNonReferenceType();
+ const VarDecl *PrivateCopy = Pair.second.PrivateCopy;
+ // For BindingDecls, use PrivateCopy type (binding's actual type).
+ // For regular variables, use Original type to preserve qualifiers.
+ // Check OriginalRef to detect BindingDecls since Original may be the
+ // DecompositionDecl.
+ bool IsBinding =
+ Pair.second.OriginalRef &&
+ isa<BindingDecl>(
+ cast<DeclRefExpr>(Pair.second.OriginalRef)->getDecl());
+ QualType Type = IsBinding ? PrivateCopy->getType().getNonReferenceType()
+ : VD->getType().getNonReferenceType();
+
// If the private variable is a local variable with lvalue ref type,
// allocate the pointer instead of the pointee type.
if (Pair.second.isLocalPrivate()) {
@@ -3327,7 +3350,9 @@ emitTaskPrivateMappingFunction(CodeGenModule &CGM, SourceLocation Loc,
C.getPointerType(PrivatesQTy).withConst().withRestrict(),
ImplicitParamKind::Other);
Args.push_back(TaskPrivatesArg);
- llvm::DenseMap<CanonicalDeclPtr<const VarDecl>, unsigned> PrivateVarsPos;
+ llvm::SmallDenseMap<CanonicalDeclPtr<const VarDecl>, unsigned> PrivateVarsPos;
+ // Track BindingDecl positions separately since BindingDecl is not a VarDecl.
+ llvm::SmallDenseMap<const BindingDecl *, unsigned> BindingDeclPos;
unsigned Counter = 1;
for (const Expr *E : Data.PrivateVars) {
Args.push_back(ImplicitParamDecl::Create(
@@ -3336,8 +3361,11 @@ emitTaskPrivateMappingFunction(CodeGenModule &CGM, SourceLocation Loc,
.withConst()
.withRestrict(),
ImplicitParamKind::Other));
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
- PrivateVarsPos[VD] = Counter;
+ const ValueDecl *VD = cast<DeclRefExpr>(E)->getDecl();
+ if (const auto *BD = dyn_cast<BindingDecl>(VD))
+ BindingDeclPos[cast<BindingDecl>(BD->getCanonicalDecl())] = Counter;
+ else
+ PrivateVarsPos[cast<VarDecl>(VD)] = Counter;
++Counter;
}
for (const Expr *E : Data.FirstprivateVars) {
@@ -3347,8 +3375,11 @@ emitTaskPrivateMappingFunction(CodeGenModule &CGM, SourceLocation Loc,
.withConst()
.withRestrict(),
ImplicitParamKind::Other));
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
- PrivateVarsPos[VD] = Counter;
+ const ValueDecl *VD = cast<DeclRefExpr>(E)->getDecl();
+ if (const auto *BD = dyn_cast<BindingDecl>(VD))
+ BindingDeclPos[cast<BindingDecl>(BD->getCanonicalDecl())] = Counter;
+ else
+ PrivateVarsPos[cast<VarDecl>(VD)] = Counter;
++Counter;
}
for (const Expr *E : Data.LastprivateVars) {
@@ -3358,8 +3389,11 @@ emitTaskPrivateMappingFunction(CodeGenModule &CGM, SourceLocation Loc,
.withConst()
.withRestrict(),
ImplicitParamKind::Other));
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
- PrivateVarsPos[VD] = Counter;
+ const ValueDecl *VD = cast<DeclRefExpr>(E)->getDecl();
+ if (const auto *BD = dyn_cast<BindingDecl>(VD))
+ BindingDeclPos[cast<BindingDecl>(BD->getCanonicalDecl())] = Counter;
+ else
+ PrivateVarsPos[cast<VarDecl>(VD)] = Counter;
++Counter;
}
for (const VarDecl *VD : Data.PrivateLocals) {
@@ -3406,7 +3440,28 @@ emitTaskPrivateMappingFunction(CodeGenModule &CGM, SourceLocation Loc,
Counter = 0;
for (const FieldDecl *Field : PrivatesQTyRD->fields()) {
LValue FieldLVal = CGF.EmitLValueForField(Base, Field);
- const VarDecl *VD = Args[PrivateVarsPos[Privates[Counter].second.Original]];
+ // Lookup by the original declaration (BindingDecl or VarDecl).
+ const ValueDecl *LookupVD;
+ if (Privates[Counter].second.OriginalRef) {
+ LookupVD =
+ cast<DeclRefExpr>(Privates[Counter].second.OriginalRef)->getDecl();
+ } else {
+ LookupVD = Privates[Counter].second.Original;
+ }
+
+ // For BindingDecls, the privates record now stores each binding's type
+ // directly (not the full DecompositionDecl), so FieldLVal is already
+ // correct.
+
+ unsigned Position;
+ if (const auto *BD = dyn_cast<BindingDecl>(LookupVD)) {
+ Position =
+ BindingDeclPos.lookup(cast<BindingDecl>(BD->getCanonicalDecl()));
+ assert(Position && "binding not in privates mapping");
+ } else {
+ Position = PrivateVarsPos[cast<VarDecl>(LookupVD)];
+ }
+ const VarDecl *VD = Args[Position];
LValue RefLVal =
CGF.MakeAddrLValue(CGF.GetAddrOfLocalVar(VD), VD->getType());
LValue RefLoadLVal = CGF.EmitLoadOfPointerLValue(
@@ -3481,9 +3536,41 @@ static void emitPrivatesInit(CodeGenFunction &CGF,
CGF.MakeAddrLValue(CGF.GetAddrOfLocalVar(OriginalVD), Type);
} else if (ForDup) {
SharedRefLValue = CGF.EmitLValueForField(SrcBase, SharedField);
+ // For BindingDecls, access the specific binding field within the
+ // captured DecompositionDecl.
+ if (Pair.second.OriginalRef) {
+ if (const auto *DRE =
+ dyn_cast<DeclRefExpr>(Pair.second.OriginalRef)) {
+ if (const auto *BD = dyn_cast<BindingDecl>(DRE->getDecl())) {
+ // Emit the binding subobject (member or array element) with
+ // the decomposed decl temporarily mapped to the capture.
+ const VarDecl *DD = cast<VarDecl>(BD->getDecomposedDecl());
+ auto It = CGF.findLocalDecl(DD);
+ bool WasMapped = It != CGF.localDeclMapEnd();
+ Address Saved = WasMapped ? It->second : Address::invalid();
+ if (WasMapped)
+ It->second = SharedRefLValue.getAddress();
+ else
+ CGF.insertLocalDecl(DD, SharedRefLValue.getAddress());
+ SharedRefLValue = CGF.EmitLValue(BD->getBinding());
+ if (WasMapped) {
+ auto RestoreIt = CGF.findLocalDecl(DD);
+ RestoreIt->second = Saved;
+ } else {
+ CGF.eraseLocalDecl(DD);
+ }
+ }
+ }
+ }
+ bool IsBinding =
+ Pair.second.OriginalRef &&
+ isa<BindingDecl>(
+ cast<DeclRefExpr>(Pair.second.OriginalRef)->getDecl());
SharedRefLValue = CGF.MakeAddrLValue(
SharedRefLValue.getAddress().withAlignment(
- C.getDeclAlign(OriginalVD)),
+ IsBinding ? C.toCharUnitsFromBits(
+ C.getTypeAlign(SharedRefLValue.getType()))
+ : C.getDeclAlign(OriginalVD)),
SharedRefLValue.getType(), LValueBaseInfo(AlignmentSource::Decl),
SharedRefLValue.getTBAAInfo());
} else if (CGF.LambdaCaptureFields.count(
@@ -3532,6 +3619,72 @@ static void emitPrivatesInit(CodeGenFunction &CGF,
} else {
CGF.EmitExprAsInit(Init, VD, PrivateLValue, /*capturedByInit=*/false);
}
+ } else if (const VarDecl *OriginalVD = Pair.second.Original) {
+ // Handle array bindings without initializers (firstprivate only).
+ // For private clause (PrivateElemInit is nullptr), skip initialization.
+ // Check if OriginalRef is a BindingDecl with array type.
+ const BindingDecl *BD = nullptr;
+ if (Pair.second.OriginalRef) {
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(Pair.second.OriginalRef)) {
+ BD = dyn_cast<BindingDecl>(DRE->getDecl());
+ }
+ }
+ if (BD && BD->getType()->isArrayType() && Pair.second.PrivateElemInit) {
+ LValue PrivateLValue = CGF.EmitLValueForField(PrivatesBase, *FI);
+ QualType Type = PrivateLValue.getType();
+ const FieldDecl *SharedField = CapturesInfo.lookup(OriginalVD);
+
+ // Lambda to temporarily map DecompositionDecl and emit the binding
+ // expression.
+ auto EmitBindingWithTempMap = [&CGF](const BindingDecl *BD,
+ Address DDAddr) -> LValue {
+ const VarDecl *DD = cast<VarDecl>(BD->getDecomposedDecl());
+ auto It = CGF.findLocalDecl(DD);
+ bool WasMapped = It != CGF.localDeclMapEnd();
+ Address Saved = WasMapped ? It->second : Address::invalid();
+ if (WasMapped)
+ It->second = DDAddr;
+ else
+ CGF.insertLocalDecl(DD, DDAddr);
+
+ LValue Result = CGF.EmitLValue(BD->getBinding());
+
+ // Restore the mapping
+ if (WasMapped) {
+ auto RestoreIt = CGF.findLocalDecl(DD);
+ RestoreIt->second = Saved;
+ } else {
+ CGF.eraseLocalDecl(DD);
+ }
+
+ return Result;
+ };
+
+ LValue SharedRefLValue;
+ if (ForDup) {
+ SharedRefLValue = CGF.EmitLValueForField(SrcBase, SharedField);
+ SharedRefLValue =
+ EmitBindingWithTempMap(BD, SharedRefLValue.getAddress());
+ SharedRefLValue = CGF.MakeAddrLValue(
+ SharedRefLValue.getAddress().withAlignment(
+ C.getDeclAlign(OriginalVD)),
+ SharedRefLValue.getType(), LValueBaseInfo(AlignmentSource::Decl),
+ SharedRefLValue.getTBAAInfo());
+ } else {
+ // For !ForDup (first task), emit binding from parent scope using
+ // InlinedOpenMPRegionRAII to access the correct scope
+ InlinedOpenMPRegionRAII Region(
+ CGF, [](CodeGenFunction &, PrePostActionTy &) {}, OMPD_unknown,
+ /*HasCancel=*/false, /*NoInheritance=*/true);
+
+ // Get the decomposed decl address from parent scope
+ const VarDecl *DD = cast<VarDecl>(BD->getDecomposedDecl());
+ Address DDAddr = CGF.GetAddrOfLocalVar(DD);
+ SharedRefLValue = EmitBindingWithTempMap(BD, DDAddr);
+ }
+ // Perform simple memcpy for array binding
+ CGF.EmitAggregateAssign(PrivateLValue, SharedRefLValue, Type);
+ }
}
++FI;
}
@@ -3784,32 +3937,35 @@ CGOpenMPRuntime::emitTaskInit(CodeGenFunction &CGF, SourceLocation Loc,
// Aggregate privates and sort them by the alignment.
const auto *I = Data.PrivateCopies.begin();
for (const Expr *E : Data.PrivateVars) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
- Privates.emplace_back(
- C.getDeclAlign(VD),
- PrivateHelpersTy(E, VD, cast<VarDecl>(cast<DeclRefExpr>(*I)->getDecl()),
- /*PrivateElemInit=*/nullptr));
+ const auto *Decl = cast<DeclRefExpr>(E)->getDecl();
+ const auto *VD = getOriginalVarDecl(Decl);
+ const auto *CopyVD = cast<VarDecl>(cast<DeclRefExpr>(*I)->getDecl());
+ Privates.emplace_back(C.getDeclAlign(VD),
+ PrivateHelpersTy(E, VD, CopyVD,
+ /*PrivateElemInit=*/nullptr));
++I;
}
I = Data.FirstprivateCopies.begin();
const auto *IElemInitRef = Data.FirstprivateInits.begin();
for (const Expr *E : Data.FirstprivateVars) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
- Privates.emplace_back(
- C.getDeclAlign(VD),
- PrivateHelpersTy(
- E, VD, cast<VarDecl>(cast<DeclRefExpr>(*I)->getDecl()),
- cast<VarDecl>(cast<DeclRefExpr>(*IElemInitRef)->getDecl())));
+ const auto *Decl = cast<DeclRefExpr>(E)->getDecl();
+ const auto *VD = getOriginalVarDecl(Decl);
+ const auto *CopyVD = cast<VarDecl>(cast<DeclRefExpr>(*I)->getDecl());
+ const auto *InitVD =
+ cast<VarDecl>(cast<DeclRefExpr>(*IElemInitRef)->getDecl());
+ Privates.emplace_back(C.getDeclAlign(VD),
+ PrivateHelpersTy(E, VD, CopyVD, InitVD));
++I;
++IElemInitRef;
}
I = Data.LastprivateCopies.begin();
for (const Expr *E : Data.LastprivateVars) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
- Privates.emplace_back(
- C.getDeclAlign(VD),
- PrivateHelpersTy(E, VD, cast<VarDecl>(cast<DeclRefExpr>(*I)->getDecl()),
- /*PrivateElemInit=*/nullptr));
+ const auto *Decl = cast<DeclRefExpr>(E)->getDecl();
+ const auto *VD = getOriginalVarDecl(Decl);
+ const auto *CopyVD = cast<VarDecl>(cast<DeclRefExpr>(*I)->getDecl());
+ Privates.emplace_back(C.getDeclAlign(VD),
+ PrivateHelpersTy(E, VD, CopyVD,
+ /*PrivateElemInit=*/nullptr));
++I;
}
for (const VarDecl *VD : Data.PrivateLocals) {
@@ -5685,8 +5841,15 @@ static std::string generateUniqueName(CodeGenModule &CGM, StringRef Prefix,
llvm::raw_svector_ostream Out(Buffer);
const clang::DeclRefExpr *DE;
const VarDecl *D = ::getBaseDecl(Ref, DE);
- if (!D)
- D = cast<VarDecl>(cast<DeclRefExpr>(Ref)->getDecl());
+ if (!D) {
+ auto *DRE = cast<DeclRefExpr>(Ref);
+ if (const auto *BD = dyn_cast<BindingDecl>(DRE->getDecl())) {
+ // For BindingDecls, use the decomposed declaration as the base.
+ D = cast<VarDecl>(BD->getDecomposedDecl());
+ } else {
+ D = cast<VarDecl>(DRE->getDecl());
+ }
+ }
D = D->getCanonicalDecl();
std::string Name = CGM.getOpenMPRuntime().getName(
{D->isLocalVarDeclOrParm() ? D->getName() : CGM.getMangledName(D)});
@@ -9391,9 +9554,12 @@ class MappableExprsHandler {
: CurDir(&Dir), CGF(CGF), AttachPtrComparator(*this) {
// Extract firstprivate clause information.
for (const auto *C : Dir.getClausesOfKind<OMPFirstprivateClause>())
- for (const auto *D : C->varlist())
- FirstPrivateDecls.try_emplace(
- cast<VarDecl>(cast<DeclRefExpr>(D)->getDecl()), C->isImplicit());
+ for (const auto *D : C->varlist()) {
+ const ValueDecl *VD = cast<DeclRefExpr>(D)->getDecl();
+ if (const auto *BD = dyn_cast<BindingDecl>(VD))
+ VD = cast<VarDecl>(BD->getDecomposedDecl());
+ FirstPrivateDecls.try_emplace(cast<VarDecl>(VD), C->isImplicit());
+ }
// Extract implicit firstprivates from uses_allocators clauses.
for (const auto *C : Dir.getClausesOfKind<OMPUsesAllocatorsClause>()) {
for (unsigned I = 0, E = C->getNumberOfAllocators(); I < E; ++I) {
diff --git a/clang/lib/CodeGen/CGStmtOpenMP.cpp b/clang/lib/CodeGen/CGStmtOpenMP.cpp
index 1a805b05b401e..cff5801fd097b 100644
--- a/clang/lib/CodeGen/CGStmtOpenMP.cpp
+++ b/clang/lib/CodeGen/CGStmtOpenMP.cpp
@@ -190,8 +190,10 @@ class OMPLoopScope : public CodeGenFunction::RunCleanupsScope {
// Mark private vars as undefs.
for (const auto *C : LD->getClausesOfKind<OMPPrivateClause>()) {
for (const Expr *IRef : C->varlist()) {
- const auto *OrigVD =
- cast<VarDecl>(cast<DeclRefExpr>(IRef)->getDecl());
+ const auto *OrigDecl = cast<DeclRefExpr>(IRef)->getDecl();
+ const auto *OrigVD = dyn_cast<VarDecl>(OrigDecl);
+ if (!OrigVD)
+ continue;
if (EmittedAsPrivate.insert(OrigVD->getCanonicalDecl()).second) {
QualType OrigVDTy = OrigVD->getType().getNonReferenceType();
(void)PreCondVars.setVarAddr(
@@ -385,6 +387,21 @@ static void emitCommonOMPTargetDirective(CodeGenFunction &CGF,
const OMPExecutableDirective &S,
const RegionCodeGenTy &CodeGen);
+Address CodeGenFunction::EmitOMPBindingOriginalAddr(const BindingDecl *BD,
+ SourceLocation Loc) {
+ if (CapturedStmtInfo &&
+ CapturedStmtInfo->getKind() == CapturedRegionKind::CR_OpenMP) {
+ if (const auto *DD = dyn_cast<VarDecl>(BD->getDecomposedDecl())) {
+ if (CapturedStmtInfo->lookup(DD))
+ return EmitOMPCapturedBindingLValue(BD).getAddress();
+ }
+ }
+ DeclRefExpr DRE(getContext(), const_cast<BindingDecl *>(BD),
+ /*RefersToEnclosingVariableOrCapture=*/false, BD->getType(),
+ VK_LValue, Loc);
+ return EmitLValue(&DRE).getAddress();
+}
+
LValue CodeGenFunction::EmitOMPSharedLValue(const Expr *E) {
if (const auto *OrigDRE = dyn_cast<DeclRefExpr>(E)) {
if (const auto *OrigVD = dyn_cast<VarDecl>(OrigDRE->getDecl())) {
@@ -397,6 +414,17 @@ LValue CodeGenFunction::EmitOMPSharedLValue(const Expr *E) {
OrigDRE->getType(), VK_LValue, OrigDRE->getExprLoc());
return EmitLValue(&DRE);
}
+ if (const auto *OrigBD = dyn_cast<BindingDecl>(OrigDRE->getDecl())) {
+ OrigBD = cast<BindingDecl>(OrigBD->getCanonicalDecl());
+ const auto *DD = cast<VarDecl>(OrigBD->getDecomposedDecl());
+ bool IsCaptured = LambdaCaptureFields.lookup(OrigBD) ||
+ (CapturedStmtInfo && CapturedStmtInfo->lookup(DD)) ||
+ isa_and_nonnull<BlockDecl>(CurCodeDecl);
+ DeclRefExpr DRE(getContext(), const_cast<BindingDecl *>(OrigBD),
+ IsCaptured, OrigDRE->getType(), VK_LValue,
+ OrigDRE->getExprLoc());
+ return EmitLValue(&DRE);
+ }
}
return EmitLValue(E);
}
@@ -1155,12 +1183,12 @@ bool CodeGenFunction::EmitOMPFirstprivateClause(const OMPExecutableDirective &D,
bool DeviceConstTarget = getLangOpts().OpenMPIsTargetDevice &&
isOpenMPTargetExecutionDirective(EKind);
bool FirstprivateIsLastprivate = false;
- llvm::DenseMap<const VarDecl *, OpenMPLastprivateModifier> Lastprivates;
+ llvm::SmallDenseMap<const Decl *, OpenMPLastprivateModifier> Lastprivates;
for (const auto *C : D.getClausesOfKind<OMPLastprivateClause>()) {
- for (const auto *D : C->varlist())
- Lastprivates.try_emplace(
- cast<VarDecl>(cast<DeclRefExpr>(D)->getDecl())->getCanonicalDecl(),
- C->getKind());
+ for (const auto *D : C->varlist()) {
+ const auto *VD = cast<DeclRefExpr>(D)->getDecl();
+ Lastprivates.try_emplace(VD->getCanonicalDecl(), C->getKind());
+ }
}
llvm::DenseSet<const VarDecl *> EmittedAsFirstprivate;
llvm::SmallVector<OpenMPDirectiveKind, 4> CaptureRegions;
@@ -1173,11 +1201,91 @@ bool CodeGenFunction::EmitOMPFirstprivateClause(const OMPExecutableDirective &D,
const auto *IRef = C->varlist_begin();
const auto *InitsRef = C->inits().begin();
for (const Expr *IInit : C->private_copies()) {
- const auto *OrigVD = cast<VarDecl>(cast<DeclRefExpr>(*IRef)->getDecl());
+ const auto *OrigDecl = cast<DeclRefExpr>(*IRef)->getDecl();
+ const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(IInit)->getDecl());
+
+ if (const auto *BD = dyn_cast<BindingDecl>(OrigDecl)) {
+ // Check if this binding is also lastprivate.
+ bool ThisFirstprivateIsLastprivate =
+ Lastprivates.count(BD->getCanonicalDecl()) > 0;
+ const auto *DD = cast<VarDecl>(BD->getDecomposedDecl());
+ // If the decomposition is captured by copy, the captured field is
+ // already a private copy; map the binding to its member directly.
+ if (!MustEmitFirstprivateCopy && !ThisFirstprivateIsLastprivate) {
+ if (const FieldDecl *FD = CapturedStmtInfo->lookup(DD)) {
+ if (!FD->getType()->isReferenceType()) {
+ bool IsRegistered = PrivateScope.addPrivate(
+ BD, EmitOMPCapturedBindingLValue(BD).getAddress());
+ assert(IsRegistered &&
+ "firstprivate var already registered as firstprivate");
+ (void)IsRegistered;
+ ++IRef;
+ ++InitsRef;
+ continue;
+ }
+ }
+ }
+ const auto *VDInit =
+ cast<VarDecl>(cast<DeclRefExpr>(*InitsRef)->getDecl());
+ Address OriginalAddr =
+ EmitOMPBindingOriginalAddr(BD, (*IRef)->getExprLoc());
+
+ QualType Type = VD->getType();
+ bool IsRegistered;
+ if (Type->isArrayType()) {
+ // For array bindings, use array copy logic
+ AutoVarEmission Emission = EmitAutoVarAlloca(*VD);
+ const Expr *Init = VD->getInit();
+ LValue OriginalLVal = MakeAddrLValue(OriginalAddr, Type);
+ if (!Init || !isa<CXXConstructExpr>(Init) ||
+ isTrivialInitializer(Init)) {
+ // Perform simple memcpy.
+ LValue Dest = MakeAddrLValue(Emission.getAllocatedAddress(), Type);
+ EmitAggregateAssign(Dest, OriginalLVal, Type);
+ } else {
+ EmitOMPAggregateAssign(
+ Emission.getAllocatedAddress(), OriginalAddr, Type,
+ [this, VDInit, Init](Address DestElement, Address SrcElement) {
+ // Clean up any temporaries needed by the initialization.
+ RunCleanupsScope InitScope(*this);
+ // Emit initialization for single element.
+ setAddrOfLocalVar(VDInit, SrcElement);
+ EmitAnyExprToMem(Init, DestElement,
+ Init->getType().getQualifiers(),
+ /*IsInitializer*/ false);
+ LocalDeclMap.erase(VDInit);
+ });
+ }
+ EmitAutoVarCleanups(Emission);
+ IsRegistered =
+ PrivateScope.addPrivate(BD, Emission.getAllocatedAddress());
+ } else {
+ // VD now has the binding's type (e.g., int), not the struct type.
+ // Emit VD initialized from the binding's field address.
+ setAddrOfLocalVar(VDInit, OriginalAddr);
+ EmitDecl(*VD);
+ LocalDeclMap.erase(VDInit);
+ Address VDAddr = GetAddrOfLocalVar(VD);
+ // VD is the private copy of the binding, map BD to VDAddr directly
+ IsRegistered = PrivateScope.addPrivate(BD, VDAddr);
+ }
+
+ assert(IsRegistered &&
+ "firstprivate var already registered as firstprivate");
+ (void)IsRegistered;
+ FirstprivateIsLastprivate =
+ FirstprivateIsLastprivate || ThisFirstprivateIsLastprivate;
+ ++IRef;
+ ++InitsRef;
+ continue;
+ }
+
+ // Original VarDecl logic.
+ const VarDecl *OrigVD = dyn_cast<VarDecl>(OrigDecl);
+ assert(OrigVD && "Expected VarDecl for non-BindingDecl firstprivate");
bool ThisFirstprivateIsLastprivate =
Lastprivates.count(OrigVD->getCanonicalDecl()) > 0;
const FieldDecl *FD = CapturedStmtInfo->lookup(OrigVD);
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(IInit)->getDecl());
if (!MustEmitFirstprivateCopy && !ThisFirstprivateIsLastprivate && FD &&
!FD->getType()->isReferenceType() &&
(!VD || !VD->hasAttr<OMPAllocateDeclAttr>())) {
@@ -1296,19 +1404,18 @@ void CodeGenFunction::EmitOMPPrivateClause(
CodeGenFunction::OMPPrivateScope &PrivateScope) {
if (!HaveInsertPoint())
return;
- llvm::DenseSet<const VarDecl *> EmittedAsPrivate;
+ llvm::SmallDenseSet<const ValueDecl *> EmittedAsPrivate;
for (const auto *C : D.getClausesOfKind<OMPPrivateClause>()) {
auto IRef = C->varlist_begin();
for (const Expr *IInit : C->private_copies()) {
- const auto *OrigVD = cast<VarDecl>(cast<DeclRefExpr>(*IRef)->getDecl());
- if (EmittedAsPrivate.insert(OrigVD->getCanonicalDecl()).second) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(IInit)->getDecl());
+ const auto *OrigDecl = cast<DeclRefExpr>(*IRef)->getDecl();
+ const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(IInit)->getDecl());
+ if (EmittedAsPrivate.insert(cast<ValueDecl>(OrigDecl->getCanonicalDecl()))
+ .second) {
EmitDecl(*VD);
- // Emit private VarDecl with copy init.
bool IsRegistered =
- PrivateScope.addPrivate(OrigVD, GetAddrOfLocalVar(VD));
+ PrivateScope.addPrivate(OrigDecl, GetAddrOfLocalVar(VD));
assert(IsRegistered && "private var already registered as private");
- // Silence the warning about unused variable.
(void)IsRegistered;
}
++IRef;
@@ -1402,7 +1509,7 @@ bool CodeGenFunction::EmitOMPLastprivateClauseInit(
cast<VarDecl>(cast<DeclRefExpr>(C)->getDecl())->getCanonicalDecl());
}
}
- llvm::DenseSet<const VarDecl *> AlreadyEmittedVars;
+ llvm::SmallDenseSet<const ValueDecl *> AlreadyEmittedVars;
for (const auto *C : D.getClausesOfKind<OMPLastprivateClause>()) {
HasAtLeastOneLastprivate = true;
if (isOpenMPTaskLoopDirective(EKind) && !getLangOpts().OpenMPSimd)
@@ -1412,7 +1519,34 @@ bool CodeGenFunction::EmitOMPLastprivateClauseInit(
for (const Expr *IInit : C->private_copies()) {
// Keep the address of the original variable for future update at the end
// of the loop.
- const auto *OrigVD = cast<VarDecl>(cast<DeclRefExpr>(*IRef)->getDecl());
+ const auto *OrigDecl = cast<DeclRefExpr>(*IRef)->getDecl();
+ // Handle BindingDecls with the same level of support as VarDecls.
+ if (const auto *BD = dyn_cast<BindingDecl>(OrigDecl)) {
+ if (AlreadyEmittedVars.insert(cast<ValueDecl>(BD->getCanonicalDecl()))
+ .second) {
+ const auto *DestVD =
+ cast<VarDecl>(cast<DeclRefExpr>(*IDestRef)->getDecl());
+
+ // Get the original binding address.
+ Address OrigAddr =
+ EmitOMPBindingOriginalAddr(BD, (*IRef)->getExprLoc());
+ PrivateScope.addPrivate(DestVD, OrigAddr);
+ if (IInit) {
+ const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(IInit)->getDecl());
+ // Emit private VarDecl with copy init.
+ EmitDecl(*VD);
+ Address VDAddr = GetAddrOfLocalVar(VD);
+ bool IsRegistered = PrivateScope.addPrivate(BD, VDAddr);
+ assert(IsRegistered &&
+ "lastprivate binding already registered as private");
+ (void)IsRegistered;
+ }
+ }
+ ++IRef;
+ ++IDestRef;
+ continue;
+ }
+ const auto *OrigVD = cast<VarDecl>(OrigDecl);
// Taskloops do not require additional initialization, it is done in
// runtime support library.
if (AlreadyEmittedVars.insert(OrigVD->getCanonicalDecl()).second) {
@@ -1482,8 +1616,8 @@ void CodeGenFunction::EmitOMPLastprivateClauseFinal(
Builder.CreateCondBr(IsLastIterCond, ThenBB, DoneBB);
EmitBlock(ThenBB);
}
- llvm::DenseSet<const VarDecl *> AlreadyEmittedVars;
- llvm::DenseMap<const VarDecl *, const Expr *> LoopCountersAndUpdates;
+ llvm::DenseSet<const ValueDecl *> AlreadyEmittedVars;
+ llvm::SmallDenseMap<const VarDecl *, const Expr *> LoopCountersAndUpdates;
if (const auto *LoopDirective = dyn_cast<OMPLoopDirective>(&D)) {
auto IC = LoopDirective->counters().begin();
for (const Expr *F : LoopDirective->finals()) {
@@ -1501,27 +1635,69 @@ void CodeGenFunction::EmitOMPLastprivateClauseFinal(
auto ISrcRef = C->source_exprs().begin();
auto IDestRef = C->destination_exprs().begin();
for (const Expr *AssignOp : C->assignment_ops()) {
- const auto *PrivateVD =
- cast<VarDecl>(cast<DeclRefExpr>(*IRef)->getDecl());
- QualType Type = PrivateVD->getType();
- const auto *CanonicalVD = PrivateVD->getCanonicalDecl();
- if (AlreadyEmittedVars.insert(CanonicalVD).second) {
+ const auto *PrivateDecl = cast<DeclRefExpr>(*IRef)->getDecl();
+
+ // For BindingDecls, check if we should use .lastprivate.src or the BD
+ // itself.
+ const VarDecl *PrivateVD = nullptr;
+ const BindingDecl *BD = nullptr;
+ if ((BD = dyn_cast<BindingDecl>(PrivateDecl))) {
+ // Check if .lastprivate.src is available (taskloop case).
+ const auto *SrcVD =
+ cast<VarDecl>(cast<DeclRefExpr>(*ISrcRef)->getDecl());
+
+ if (LocalDeclMap.count(SrcVD)) {
+ // Taskloop case: use .lastprivate.src.
+ PrivateVD = SrcVD;
+ } else if (OMPPrivatizedBindings.count(BD)) {
+ // Parallel for case: BindingDecl is directly privatized.
+ // Leave PrivateVD as nullptr to handle specially below.
+ }
+ } else {
+ PrivateVD = cast<VarDecl>(PrivateDecl);
+ }
+
+ QualType Type = PrivateVD ? PrivateVD->getType() : BD->getType();
+ const auto *CanonicalVD =
+ PrivateVD ? PrivateVD->getCanonicalDecl() : nullptr;
+
+ // Check if already emitted.
+ bool ShouldEmit =
+ AlreadyEmittedVars
+ .insert(PrivateVD ? static_cast<const ValueDecl *>(CanonicalVD)
+ : static_cast<const ValueDecl *>(BD))
+ .second;
+ if (ShouldEmit) {
// If lastprivate variable is a loop control variable for loop-based
// directive, update its value before copyin back to original
// variable.
- if (const Expr *FinalExpr = LoopCountersAndUpdates.lookup(CanonicalVD))
- EmitIgnoredExpr(FinalExpr);
+ if (CanonicalVD) {
+ if (const Expr *FinalExpr =
+ LoopCountersAndUpdates.lookup(CanonicalVD))
+ EmitIgnoredExpr(FinalExpr);
+ }
const auto *SrcVD =
cast<VarDecl>(cast<DeclRefExpr>(*ISrcRef)->getDecl());
const auto *DestVD =
cast<VarDecl>(cast<DeclRefExpr>(*IDestRef)->getDecl());
+
// Get the address of the private variable.
- Address PrivateAddr = GetAddrOfLocalVar(PrivateVD);
- if (const auto *RefTy = PrivateVD->getType()->getAs<ReferenceType>())
- PrivateAddr = Address(
- Builder.CreateLoad(PrivateAddr),
- CGM.getTypes().ConvertTypeForMem(RefTy->getPointeeType()),
- CGM.getNaturalTypeAlignment(RefTy->getPointeeType()));
+ Address PrivateAddr = Address::invalid();
+ if (PrivateVD) {
+ PrivateAddr = GetAddrOfLocalVar(PrivateVD);
+ } else {
+ auto It = OMPPrivatizedBindings.find(BD);
+ assert(It != OMPPrivatizedBindings.end() &&
+ "BindingDecl should be privatized");
+ PrivateAddr = It->second;
+ }
+ if (PrivateVD) {
+ if (const auto *RefTy = PrivateVD->getType()->getAs<ReferenceType>())
+ PrivateAddr = Address(
+ Builder.CreateLoad(PrivateAddr),
+ CGM.getTypes().ConvertTypeForMem(RefTy->getPointeeType()),
+ CGM.getNaturalTypeAlignment(RefTy->getPointeeType()));
+ }
// Store the last value to the private copy in the last iteration.
if (C->getKind() == OMPC_LASTPRIVATE_conditional)
CGM.getOpenMPRuntime().emitLastprivateConditionalFinalUpdate(
@@ -1580,7 +1756,6 @@ void CodeGenFunction::EmitOMPReductionClauseInit(
auto *IPriv = Privates.begin();
for (const Expr *IRef : Shareds) {
const auto *PrivateVD = cast<VarDecl>(cast<DeclRefExpr>(*IPriv)->getDecl());
- // Emit private VarDecl with reduction init.
RedCG.emitSharedOrigLValue(*this, Count);
RedCG.emitAggregateType(*this, Count);
AutoVarEmission Emission = EmitAutoVarAlloca(*PrivateVD);
@@ -1847,7 +2022,11 @@ checkForLastprivateConditionalUpdate(CodeGenFunction &CGF,
const auto *DRE = dyn_cast<DeclRefExpr>(Ref->IgnoreParenImpCasts());
if (!DRE)
continue;
- PrivateDecls.insert(cast<VarDecl>(DRE->getDecl()));
+ // Skip BindingDecls - lastprivate conditional only applies to VarDecls.
+ const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
+ if (!VD)
+ continue;
+ PrivateDecls.insert(VD);
CGF.CGM.getOpenMPRuntime().checkAndEmitLastprivateConditional(CGF, Ref);
}
}
@@ -1858,8 +2037,12 @@ checkForLastprivateConditionalUpdate(CodeGenFunction &CGF,
const auto *DRE = dyn_cast<DeclRefExpr>(Ref->IgnoreParenImpCasts());
if (!DRE)
continue;
- PrivateDecls.insert(cast<VarDecl>(DRE->getDecl()));
- CGF.CGM.getOpenMPRuntime().checkAndEmitLastprivateConditional(CGF, Ref);
+ // Skip BindingDecls - they don't use the same conditional lastprivate
+ // mechanism.
+ if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+ PrivateDecls.insert(VD);
+ CGF.CGM.getOpenMPRuntime().checkAndEmitLastprivateConditional(CGF, Ref);
+ }
}
}
for (const auto *C : S.getClausesOfKind<OMPLinearClause>()) {
@@ -1869,8 +2052,10 @@ checkForLastprivateConditionalUpdate(CodeGenFunction &CGF,
const auto *DRE = dyn_cast<DeclRefExpr>(Ref->IgnoreParenImpCasts());
if (!DRE)
continue;
- PrivateDecls.insert(cast<VarDecl>(DRE->getDecl()));
- CGF.CGM.getOpenMPRuntime().checkAndEmitLastprivateConditional(CGF, Ref);
+ if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+ PrivateDecls.insert(VD);
+ CGF.CGM.getOpenMPRuntime().checkAndEmitLastprivateConditional(CGF, Ref);
+ }
}
}
// Privates should ne analyzed since they are not captured at all.
@@ -1884,7 +2069,9 @@ checkForLastprivateConditionalUpdate(CodeGenFunction &CGF,
const auto *DRE = dyn_cast<DeclRefExpr>(Ref->IgnoreParenImpCasts());
if (!DRE)
continue;
- PrivateDecls.insert(cast<VarDecl>(DRE->getDecl()));
+ // Only track VarDecl, not BindingDecl.
+ if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ PrivateDecls.insert(VD);
}
}
CGF.CGM.getOpenMPRuntime().checkAndEmitSharedLastprivateConditional(
@@ -2547,17 +2734,21 @@ bool CodeGenFunction::EmitOMPLinearClauseInit(const OMPLoopDirective &D) {
const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(Init)->getDecl());
if (const auto *Ref =
dyn_cast<DeclRefExpr>(VD->getInit()->IgnoreImpCasts())) {
- AutoVarEmission Emission = EmitAutoVarAlloca(*VD);
- const auto *OrigVD = cast<VarDecl>(Ref->getDecl());
- DeclRefExpr DRE(getContext(), const_cast<VarDecl *>(OrigVD),
- CapturedStmtInfo->lookup(OrigVD) != nullptr,
- VD->getInit()->getType(), VK_LValue,
- VD->getInit()->getExprLoc());
- EmitExprAsInit(
- &DRE, VD,
- MakeAddrLValue(Emission.getAllocatedAddress(), VD->getType()),
- /*capturedByInit=*/false);
- EmitAutoVarCleanups(Emission);
+ if (isa<BindingDecl>(Ref->getDecl())) {
+ EmitVarDecl(*VD);
+ } else {
+ AutoVarEmission Emission = EmitAutoVarAlloca(*VD);
+ const auto *OrigVD = cast<VarDecl>(Ref->getDecl());
+ DeclRefExpr DRE(getContext(), const_cast<VarDecl *>(OrigVD),
+ CapturedStmtInfo->lookup(OrigVD) != nullptr,
+ VD->getInit()->getType(), VK_LValue,
+ VD->getInit()->getExprLoc());
+ EmitExprAsInit(
+ &DRE, VD,
+ MakeAddrLValue(Emission.getAllocatedAddress(), VD->getType()),
+ /*capturedByInit=*/false);
+ EmitAutoVarCleanups(Emission);
+ }
} else {
EmitVarDecl(*VD);
}
@@ -2594,13 +2785,19 @@ void CodeGenFunction::EmitOMPLinearClauseFinal(
EmitBlock(ThenBB);
}
}
- const auto *OrigVD = cast<VarDecl>(cast<DeclRefExpr>(*IC)->getDecl());
- DeclRefExpr DRE(getContext(), const_cast<VarDecl *>(OrigVD),
- CapturedStmtInfo->lookup(OrigVD) != nullptr,
- (*IC)->getType(), VK_LValue, (*IC)->getExprLoc());
- Address OrigAddr = EmitLValue(&DRE).getAddress();
+ const auto *OrigDecl = cast<DeclRefExpr>(*IC)->getDecl();
+ Address OrigAddr = Address::invalid();
+ if (const auto *BD = dyn_cast<BindingDecl>(OrigDecl)) {
+ OrigAddr = EmitOMPBindingOriginalAddr(BD, (*IC)->getExprLoc());
+ } else {
+ const auto *OrigVD = cast<VarDecl>(OrigDecl);
+ DeclRefExpr DRE(getContext(), const_cast<VarDecl *>(OrigVD),
+ CapturedStmtInfo->lookup(OrigVD) != nullptr,
+ (*IC)->getType(), VK_LValue, (*IC)->getExprLoc());
+ OrigAddr = EmitLValue(&DRE).getAddress();
+ }
CodeGenFunction::OMPPrivateScope VarScope(*this);
- VarScope.addPrivate(OrigVD, OrigAddr);
+ VarScope.addPrivate(OrigDecl, OrigAddr);
(void)VarScope.Privatize();
EmitIgnoredExpr(F);
++IC;
@@ -2743,14 +2940,17 @@ void CodeGenFunction::EmitOMPLinearClause(
for (const auto *C : D.getClausesOfKind<OMPLinearClause>()) {
auto CurPrivate = C->privates().begin();
for (const Expr *E : C->varlist()) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
+ const auto *VD = cast<DeclRefExpr>(E)->getDecl();
const auto *PrivateVD =
cast<VarDecl>(cast<DeclRefExpr>(*CurPrivate)->getDecl());
- if (!SIMDLCVs.count(VD->getCanonicalDecl())) {
+ bool IsSIMDLCV = false;
+ if (const auto *VarD = dyn_cast<VarDecl>(VD))
+ IsSIMDLCV = SIMDLCVs.count(VarD->getCanonicalDecl());
+ if (!IsSIMDLCV) {
// Emit private VarDecl with copy init.
EmitVarDecl(*PrivateVD);
- bool IsRegistered =
- PrivateScope.addPrivate(VD, GetAddrOfLocalVar(PrivateVD));
+ Address PrivateAddr = GetAddrOfLocalVar(PrivateVD);
+ bool IsRegistered = PrivateScope.addPrivate(VD, PrivateAddr);
assert(IsRegistered && "linear var already registered as private");
// Silence the warning about unused variable.
(void)IsRegistered;
@@ -5237,13 +5437,14 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
}
// The first function argument for tasks is a thread id, the second one is a
// part id (0 for tied tasks, >=0 for untied task).
- llvm::DenseSet<const VarDecl *> EmittedAsPrivate;
+ llvm::DenseSet<const ValueDecl *> EmittedAsPrivate;
// Get list of private variables.
for (const auto *C : S.getClausesOfKind<OMPPrivateClause>()) {
auto IRef = C->varlist_begin();
for (const Expr *IInit : C->private_copies()) {
- const auto *OrigVD = cast<VarDecl>(cast<DeclRefExpr>(*IRef)->getDecl());
- if (EmittedAsPrivate.insert(OrigVD->getCanonicalDecl()).second) {
+ const auto *OrigDecl = cast<DeclRefExpr>(*IRef)->getDecl();
+ if (EmittedAsPrivate.insert(cast<ValueDecl>(OrigDecl->getCanonicalDecl()))
+ .second) {
Data.PrivateVars.push_back(*IRef);
Data.PrivateCopies.push_back(IInit);
}
@@ -5256,8 +5457,9 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
auto IRef = C->varlist_begin();
auto IElemInitRef = C->inits().begin();
for (const Expr *IInit : C->private_copies()) {
- const auto *OrigVD = cast<VarDecl>(cast<DeclRefExpr>(*IRef)->getDecl());
- if (EmittedAsPrivate.insert(OrigVD->getCanonicalDecl()).second) {
+ const auto *OrigDecl = cast<DeclRefExpr>(*IRef)->getDecl();
+ if (EmittedAsPrivate.insert(cast<ValueDecl>(OrigDecl->getCanonicalDecl()))
+ .second) {
Data.FirstprivateVars.push_back(*IRef);
Data.FirstprivateCopies.push_back(IInit);
Data.FirstprivateInits.push_back(*IElemInitRef);
@@ -5267,21 +5469,26 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
}
}
// Get list of lastprivate variables (for taskloops).
- llvm::MapVector<const VarDecl *, const DeclRefExpr *> LastprivateDstsOrigs;
+ llvm::MapVector<const ValueDecl *, const DeclRefExpr *> LastprivateDstsOrigs;
+ llvm::MapVector<const ValueDecl *, const DeclRefExpr *> LastprivateSrcsOrigs;
for (const auto *C : S.getClausesOfKind<OMPLastprivateClause>()) {
auto IRef = C->varlist_begin();
auto ID = C->destination_exprs().begin();
+ auto IS = C->source_exprs().begin();
for (const Expr *IInit : C->private_copies()) {
- const auto *OrigVD = cast<VarDecl>(cast<DeclRefExpr>(*IRef)->getDecl());
- if (EmittedAsPrivate.insert(OrigVD->getCanonicalDecl()).second) {
+ const auto *OrigDecl = cast<DeclRefExpr>(*IRef)->getDecl();
+ if (EmittedAsPrivate.insert(cast<ValueDecl>(OrigDecl->getCanonicalDecl()))
+ .second) {
Data.LastprivateVars.push_back(*IRef);
Data.LastprivateCopies.push_back(IInit);
}
- LastprivateDstsOrigs.insert(
- std::make_pair(cast<VarDecl>(cast<DeclRefExpr>(*ID)->getDecl()),
- cast<DeclRefExpr>(*IRef)));
+ LastprivateDstsOrigs.insert(std::make_pair(
+ cast<DeclRefExpr>(*ID)->getDecl(), cast<DeclRefExpr>(*IRef)));
+ LastprivateSrcsOrigs.insert(std::make_pair(
+ cast<DeclRefExpr>(*IS)->getDecl(), cast<DeclRefExpr>(*IRef)));
++IRef;
++ID;
+ ++IS;
}
}
SmallVector<const Expr *, 4> LHSs;
@@ -5307,8 +5514,8 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
Checker.getPrivateDecls().end());
}
auto &&CodeGen = [&Data, &S, CS, &BodyGen, &LastprivateDstsOrigs,
- CapturedRegion](CodeGenFunction &CGF,
- PrePostActionTy &Action) {
+ &LastprivateSrcsOrigs, CapturedRegion](
+ CodeGenFunction &CGF, PrePostActionTy &Action) {
llvm::MapVector<CanonicalDeclPtr<const VarDecl>,
std::pair<Address, Address>>
UntiedLocalVars;
@@ -5374,7 +5581,8 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
}
}
}
- llvm::SmallVector<std::pair<const VarDecl *, Address>, 16> FirstprivatePtrs;
+ llvm::SmallVector<std::pair<const ValueDecl *, Address>, 16>
+ FirstprivatePtrs;
if (!Data.PrivateVars.empty() || !Data.FirstprivateVars.empty() ||
!Data.LastprivateVars.empty() || !Data.PrivateLocals.empty()) {
enum { PrivatesParam = 2, CopyFnParam = 3 };
@@ -5383,13 +5591,13 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
llvm::Value *PrivatesPtr = CGF.Builder.CreateLoad(CGF.GetAddrOfLocalVar(
CS->getCapturedDecl()->getParam(PrivatesParam)));
// Map privates.
- llvm::SmallVector<std::pair<const VarDecl *, Address>, 16> PrivatePtrs;
+ llvm::SmallVector<std::pair<const ValueDecl *, Address>, 16> PrivatePtrs;
llvm::SmallVector<llvm::Value *, 16> CallArgs;
llvm::SmallVector<llvm::Type *, 4> ParamTypes;
CallArgs.push_back(PrivatesPtr);
ParamTypes.push_back(PrivatesPtr->getType());
for (const Expr *E : Data.PrivateVars) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
+ const auto *VD = cast<DeclRefExpr>(E)->getDecl();
RawAddress PrivatePtr = CGF.CreateMemTempWithoutCast(
CGF.getContext().getPointerType(E->getType()), ".priv.ptr.addr");
PrivatePtrs.emplace_back(VD, PrivatePtr);
@@ -5397,7 +5605,7 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
ParamTypes.push_back(PrivatePtr.getType());
}
for (const Expr *E : Data.FirstprivateVars) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
+ const auto *VD = cast<DeclRefExpr>(E)->getDecl();
RawAddress PrivatePtr = CGF.CreateMemTempWithoutCast(
CGF.getContext().getPointerType(E->getType()),
".firstpriv.ptr.addr");
@@ -5407,7 +5615,7 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
ParamTypes.push_back(PrivatePtr.getType());
}
for (const Expr *E : Data.LastprivateVars) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
+ const auto *VD = cast<DeclRefExpr>(E)->getDecl();
RawAddress PrivatePtr = CGF.CreateMemTempWithoutCast(
CGF.getContext().getPointerType(E->getType()),
".lastpriv.ptr.addr");
@@ -5437,13 +5645,21 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
CGF.CGM.getOpenMPRuntime().emitOutlinedFunctionCall(
CGF, S.getBeginLoc(), {CopyFnTy, CopyFn}, CallArgs);
for (const auto &Pair : LastprivateDstsOrigs) {
- const auto *OrigVD = cast<VarDecl>(Pair.second->getDecl());
- DeclRefExpr DRE(CGF.getContext(), const_cast<VarDecl *>(OrigVD),
- /*RefersToEnclosingVariableOrCapture=*/
- CGF.CapturedStmtInfo->lookup(OrigVD) != nullptr,
- Pair.second->getType(), VK_LValue,
- Pair.second->getExprLoc());
- Scope.addPrivate(Pair.first, CGF.EmitLValue(&DRE).getAddress());
+ const auto *OrigDecl = Pair.second->getDecl();
+ if (const auto *BD = dyn_cast<BindingDecl>(OrigDecl)) {
+ // For BindingDecls, emit the binding's LValue directly.
+ Address OrigAddr =
+ CGF.EmitOMPBindingOriginalAddr(BD, Pair.second->getExprLoc());
+ Scope.addPrivate(Pair.first, OrigAddr);
+ } else {
+ const auto *OrigVD = cast<VarDecl>(OrigDecl);
+ DeclRefExpr DRE(CGF.getContext(), const_cast<VarDecl *>(OrigVD),
+ /*RefersToEnclosingVariableOrCapture=*/
+ CGF.CapturedStmtInfo->lookup(OrigVD) != nullptr,
+ Pair.second->getType(), VK_LValue,
+ Pair.second->getExprLoc());
+ Scope.addPrivate(Pair.first, CGF.EmitLValue(&DRE).getAddress());
+ }
}
for (const auto &Pair : PrivatePtrs) {
Address Replacement = Address(
@@ -5451,11 +5667,25 @@ void CodeGenFunction::EmitOMPTaskBasedDirective(
CGF.ConvertTypeForMem(Pair.first->getType().getNonReferenceType()),
CGF.getContext().getDeclAlign(Pair.first));
Scope.addPrivate(Pair.first, Replacement);
+
+ // For BindingDecls with lastprivate, also map the .lastprivate.src
+ // pseudo-variable to the same private address.
+ if (isa<BindingDecl>(Pair.first)) {
+ for (const auto &SrcPair : LastprivateSrcsOrigs) {
+ if (SrcPair.second->getDecl() == Pair.first) {
+ Scope.addPrivate(SrcPair.first, Replacement);
+ break;
+ }
+ }
+ }
+
if (auto *DI = CGF.getDebugInfo())
if (CGF.CGM.getCodeGenOpts().hasReducedDebugInfo())
- (void)DI->EmitDeclareOfAutoVariable(
- Pair.first, Pair.second.getBasePointer(), CGF.Builder,
- /*UsePointerValue*/ true);
+ // Only emit debug info for VarDecls, not BindingDecls.
+ if (const auto *VD = dyn_cast<VarDecl>(Pair.first))
+ (void)DI->EmitDeclareOfAutoVariable(
+ VD, Pair.second.getBasePointer(), CGF.Builder,
+ /*UsePointerValue*/ true);
}
// Adjust mapping for internal locals by mapping actual memory instead of
// a pointer to this memory.
@@ -5705,13 +5935,13 @@ void CodeGenFunction::EmitOMPTargetTaskBasedDirective(
llvm::Value *PrivatesPtr = CGF.Builder.CreateLoad(CGF.GetAddrOfLocalVar(
CS->getCapturedDecl()->getParam(PrivatesParam)));
// Map privates.
- llvm::SmallVector<std::pair<const VarDecl *, Address>, 16> PrivatePtrs;
+ llvm::SmallVector<std::pair<const ValueDecl *, Address>, 16> PrivatePtrs;
llvm::SmallVector<llvm::Value *, 16> CallArgs;
llvm::SmallVector<llvm::Type *, 4> ParamTypes;
CallArgs.push_back(PrivatesPtr);
ParamTypes.push_back(PrivatesPtr->getType());
for (const Expr *E : Data.FirstprivateVars) {
- const auto *VD = cast<VarDecl>(cast<DeclRefExpr>(E)->getDecl());
+ const auto *VD = cast<DeclRefExpr>(E)->getDecl();
RawAddress PrivatePtr = CGF.CreateMemTempWithoutCast(
CGF.getContext().getPointerType(E->getType()),
".firstpriv.ptr.addr");
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index c9d06ef34907b..fe6c7c5deb7eb 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1157,9 +1157,10 @@ class CodeGenFunction : public CodeGenTypeCache {
/// Sets the address of the variable \p LocalVD to be \p TempAddr in
/// function \p CGF.
/// \return true if at least one variable was set already, false otherwise.
- bool setVarAddr(CodeGenFunction &CGF, const VarDecl *LocalVD,
+ bool setVarAddr(CodeGenFunction &CGF, const ValueDecl *LocalVD,
Address TempAddr) {
- LocalVD = LocalVD->getCanonicalDecl();
+ LocalVD = cast<ValueDecl>(LocalVD->getCanonicalDecl());
+
// Only save it once.
if (SavedLocals.count(LocalVD))
return false;
@@ -1178,6 +1179,8 @@ class CodeGenFunction : public CodeGenTypeCache {
CGF.Builder.CreateStore(TempAddr.emitRawPointer(CGF), Temp);
TempAddr = Temp;
}
+ if (const auto *BD = dyn_cast<BindingDecl>(LocalVD))
+ CGF.OMPPrivatizedBindings.insert_or_assign(BD, TempAddr);
SavedTempAddresses.try_emplace(LocalVD, TempAddr);
return true;
@@ -1220,6 +1223,7 @@ class CodeGenFunction : public CodeGenTypeCache {
OMPMapVars MappedVars;
OMPPrivateScope(const OMPPrivateScope &) = delete;
void operator=(const OMPPrivateScope &) = delete;
+ llvm::DenseMap<const BindingDecl *, Address> BindingChanges;
public:
/// Enter a new OpenMP private scope.
@@ -1230,8 +1234,14 @@ class CodeGenFunction : public CodeGenTypeCache {
/// PrivateGen is the address of the generated private variable.
/// \return true if the variable is registered as private, false if it has
/// been privatized already.
- bool addPrivate(const VarDecl *LocalVD, Address Addr) {
+ bool addPrivate(const ValueDecl *LocalVD, Address Addr) {
assert(PerformCleanup && "adding private to dead scope");
+ if (const auto *BD = dyn_cast<BindingDecl>(LocalVD->getCanonicalDecl())) {
+ auto It = CGF.OMPPrivatizedBindings.find(BD);
+ BindingChanges.insert({BD, It != CGF.OMPPrivatizedBindings.end()
+ ? It->second
+ : Address::invalid()});
+ }
return MappedVars.setVarAddr(CGF, LocalVD, Addr);
}
@@ -1254,6 +1264,17 @@ class CodeGenFunction : public CodeGenTypeCache {
~OMPPrivateScope() {
if (PerformCleanup)
ForceCleanup();
+ for (auto &Change : BindingChanges) {
+ if (Change.second.isValid()) {
+ auto It = CGF.OMPPrivatizedBindings.find(Change.first);
+ if (It != CGF.OMPPrivatizedBindings.end())
+ It->second = Change.second;
+ else
+ CGF.OMPPrivatizedBindings.insert({Change.first, Change.second});
+ } else {
+ CGF.OMPPrivatizedBindings.erase(Change.first);
+ }
+ }
}
/// Checks if the global variable is captured in current function.
@@ -1561,6 +1582,11 @@ class CodeGenFunction : public CodeGenTypeCache {
/// decls.
DeclMapTy LocalDeclMap;
+ /// Lookup map for privatized BindingDecls.
+ /// Used when BindingDecls are remapped during OpenMP outlining, since the
+ /// remapped BindingDecl has a
diff erent pointer than the original.
+ llvm::SmallDenseMap<const BindingDecl *, Address> OMPPrivatizedBindings;
+
// Keep track of the cleanups for callee-destructed parameters pushed to the
// cleanup stack so that they can be deactivated later.
llvm::DenseMap<const ParmVarDecl *, EHScopeStack::stable_iterator>
@@ -2244,6 +2270,17 @@ class CodeGenFunction : public CodeGenTypeCache {
const TargetInfo &getTarget() const { return Target; }
llvm::LLVMContext &getLLVMContext() { return CGM.getLLVMContext(); }
+
+ /// Accessors for LocalDeclMap.
+ DeclMapTy::iterator findLocalDecl(const Decl *D) {
+ return LocalDeclMap.find(D);
+ }
+ DeclMapTy::iterator localDeclMapEnd() { return LocalDeclMap.end(); }
+ std::pair<DeclMapTy::iterator, bool> insertLocalDecl(const Decl *D,
+ Address Addr) {
+ return LocalDeclMap.insert({D, Addr});
+ }
+ void eraseLocalDecl(const Decl *D) { LocalDeclMap.erase(D); }
const TargetCodeGenInfo &getTargetHooks() const {
return CGM.getTargetCodeGenInfo();
}
@@ -4154,6 +4191,9 @@ class CodeGenFunction : public CodeGenTypeCache {
/// Emits the lvalue for the expression with possibly captured variable.
LValue EmitOMPSharedLValue(const Expr *E);
+ /// Emits the original address for a structured binding.
+ Address EmitOMPBindingOriginalAddr(const BindingDecl *BD, SourceLocation Loc);
+
private:
/// Helpers for blocks.
llvm::Value *EmitBlockLiteral(const CGBlockInfo &Info);
@@ -4499,6 +4539,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/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index c93e9fcd1dde6..be1dc9f85d4f7 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19656,6 +19656,15 @@ static bool isVariableCapturable(CapturingScopeInfo *CSI, ValueDecl *Var,
bool IsBlock = isa<BlockScopeInfo>(CSI);
bool IsLambda = isa<LambdaScopeInfo>(CSI);
+ // Reject bindings referenced from a lambda or block that wraps an OpenMP
+ // region.
+ if ((IsLambda || IsBlock) && S.getLangOpts().OpenMP &&
+ isa<DecompositionDecl>(Var)) {
+ if (Diagnose)
+ S.Diag(Loc, diag::err_omp_unsupported_on_binding) << 3;
+ return false;
+ }
+
// Lambdas are not allowed to capture unnamed variables
// (e.g. anonymous unions).
// FIXME: The C++11 rule don't actually state this explicitly, but I'm
@@ -19707,7 +19716,23 @@ static bool isVariableCapturable(CapturingScopeInfo *CSI, ValueDecl *Var,
return false;
}
- if (isa<BindingDecl>(Var)) {
+ if (auto *BD = dyn_cast<BindingDecl>(Var)) {
+ if (auto *RSI = dyn_cast<CapturedRegionScopeInfo>(CSI)) {
+ if (RSI->CapRegionKind == CR_OpenMP) {
+ if (BD->getHoldingVar()) {
+ if (Diagnose) {
+ S.Diag(Loc, diag::err_capture_tuple_binding_openmp) << Var;
+ S.Diag(Var->getLocation(), diag::note_entity_declared_at) << Var;
+ }
+ return false;
+ }
+ if (Diagnose && S.getLangOpts().CPlusPlus) {
+ S.DiagCompat(Loc, diag_compat::capture_binding) << Var;
+ S.Diag(Var->getLocation(), diag::note_entity_declared_at) << Var;
+ }
+ return true;
+ }
+ }
if (!IsLambda || !S.getLangOpts().CPlusPlus) {
if (Diagnose)
diagnoseUncapturableValueReferenceOrBinding(S, Loc, Var);
@@ -19797,23 +19822,46 @@ static bool captureInCapturedRegion(
Sema &S, bool Invalid) {
// By default, capture variables by reference.
bool ByRef = true;
+ bool IsBindingDecl = isa<BindingDecl>(Var);
+ ValueDecl *DSAVar = Var;
if (IsTopScope && Kind != TryCaptureKind::Implicit) {
ByRef = (Kind == TryCaptureKind::ExplicitByRef);
} else if (S.getLangOpts().OpenMP && RSI->CapRegionKind == CR_OpenMP) {
// Using an LValue reference type is consistent with Lambdas (see below).
- if (S.OpenMP().isOpenMPCapturedDecl(Var)) {
+ if (VarDecl *VD = S.OpenMP().isOpenMPCapturedDecl(Var)) {
+ Var = VD; // Capture the DecompositionDecl.
bool HasConst = DeclRefType.isConstQualified();
+ // Note: DeclRefType should remain the BindingDecl's type (e.g., int),
+ // not the DecompositionDecl's type (e.g., Point). The variable being
+ // captured is the DecompositionDecl, but expressions still reference
+ // the individual binding's type.
DeclRefType = DeclRefType.getUnqualifiedType();
// Don't lose diagnostics about assignments to const.
if (HasConst)
DeclRefType.addConst();
}
- // Do not capture firstprivates in tasks.
- if (S.OpenMP().isOpenMPPrivateDecl(Var, RSI->OpenMPLevel,
- RSI->OpenMPCaptureLevel) != OMPC_unknown)
+ // Do not capture firstprivates in tasks. For bindings the DSA is on the
+ // binding, not on the DecompositionDecl; the task firstprivate path still
+ // needs the DecompositionDecl capture, so skip only private.
+ OpenMPClauseKind PrivateKind = S.OpenMP().isOpenMPPrivateDecl(
+ IsBindingDecl ? DSAVar : Var, RSI->OpenMPLevel,
+ RSI->OpenMPCaptureLevel);
+ if (IsBindingDecl ? PrivateKind == OMPC_private
+ : PrivateKind != OMPC_unknown)
return true;
- ByRef = S.OpenMP().isOpenMPCapturedByRef(Var, RSI->OpenMPLevel,
+ ByRef = S.OpenMP().isOpenMPCapturedByRef(DSAVar, RSI->OpenMPLevel,
RSI->OpenMPCaptureLevel);
+ // Bindings share the DecompositionDecl storage; a second capture with
+ // a
diff erent capture kind is not representable.
+ if (BuildAndDiagnose && IsBindingDecl) {
+ unsigned Idx = RSI->CaptureMap.lookup(Var);
+ if (Idx != 0 && RSI->Captures[Idx - 1].isReferenceCapture() != ByRef) {
+ S.Diag(Loc,
+ diag::err_omp_decomposition_bindings_
diff erent_capture_kinds)
+ << DSAVar;
+ return false;
+ }
+ }
}
if (ByRef)
@@ -19826,6 +19874,11 @@ static bool captureInCapturedRegion(
RSI->addCapture(Var, /*isBlock*/ false, ByRef, RefersToCapturedVariable,
Loc, SourceLocation(), CaptureType, Invalid);
+ if (BuildAndDiagnose && IsBindingDecl)
+ // Key the binding to its own capture entry so repeated uses hit the
+ // already-captured path.
+ RSI->CaptureMap[DSAVar] = RSI->Captures.size();
+
return !Invalid;
}
@@ -20212,14 +20265,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/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 288f3c4f664cb..0c9a92cdd0c32 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1988,9 +1988,21 @@ 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.
+ auto *BD = dyn_cast<BindingDecl>(Var);
+ if (IsOpenMPMapping && BD)
+ // When capturing a BindingDecl in an OpenMP mapping context, we need to
+ // capture the DecompositionDecl instead. BindingDecls are references to
+ // storage owned by the DecompositionDecl.
+ // Example:
+ // auto [a, b] = p;
+ // auto lambda = [a]() { return a; }; // In OpenMP context.
+ // This is reached during lambda capture for OpenMP mappings.
+ Var = BD->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:
@@ -2096,14 +2108,32 @@ bool Sema::DiagnoseUnusedLambdaCapture(SourceRange CaptureRange,
/// Create a field within the lambda class or captured statement record for the
/// given capture.
-FieldDecl *Sema::BuildCaptureField(RecordDecl *RD,
- const sema::Capture &Capture) {
+FieldDecl *Sema::BuildCaptureField(RecordDecl *RD, const sema::Capture &Capture,
+ bool IsOpenMP) {
SourceLocation Loc = Capture.getLocation();
QualType FieldType = Capture.getCaptureType();
-
TypeSourceInfo *TSI = nullptr;
if (Capture.isVariableCapture()) {
- const auto *Var = dyn_cast_or_null<VarDecl>(Capture.getVariable());
+ const VarDecl *Var = nullptr;
+ if (IsOpenMP) {
+ if (auto *BD = dyn_cast_or_null<BindingDecl>(Capture.getVariable())) {
+ Var = cast<VarDecl>(BD->getDecomposedDecl());
+ FieldType = Var->getType().getNonReferenceType();
+ if (Capture.isReferenceCapture())
+ FieldType = Context.getLValueReferenceType(FieldType);
+ } else if (auto *DD = dyn_cast_or_null<DecompositionDecl>(
+ Capture.getVariable())) {
+ // OpenMP already transformed BindingDecl to DecompositionDecl
+ // before buildCapturedStmtCaptureList. Use the DecompositionDecl
+ // type.
+ Var = DD;
+ FieldType = DD->getType().getNonReferenceType();
+ if (Capture.isReferenceCapture())
+ FieldType = Context.getLValueReferenceType(DD->getType());
+ }
+ }
+ if (!Var)
+ Var = dyn_cast_or_null<VarDecl>(Capture.getVariable());
if (Var && Var->isInitCapture())
TSI = Var->getTypeSourceInfo();
}
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index 88b642b73895f..5825a441d3abb 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -24,6 +24,7 @@
#include "clang/AST/DeclOpenMP.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/AST/OpenMPClause.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtOpenMP.h"
#include "clang/AST/StmtVisitor.h"
@@ -179,6 +180,8 @@ class DSAStackTy {
UsedRefMapTy NontemporalMap;
MappedExprComponentsTy MappedExprComponents;
LoopControlVariablesMapTy LCVMap;
+ /// Track DecompositionDecls and their data-sharing attributes to detect
+ /// conflicting clauses on bindings from the same decomposition.
DefaultDataSharingAttributes DefaultAttr = DSA_unspecified;
SourceLocation DefaultAttrLoc;
DefaultDataSharingVCAttributes DefaultVCAttr = DSA_VC_all;
@@ -1364,7 +1367,11 @@ DSAStackTy::DSAVarData DSAStackTy::getDSA(const_iterator &Iter,
DVar.ImplicitDSALoc = Iter->DefaultAttrLoc;
DVar.Modifier = Data.Modifier;
DVar.AppliedToPointee = Data.AppliedToPointee;
- return DVar;
+ // For BindingDecls with OMPC_unknown, fall through to implicit DSA logic
+ // instead of treating them as explicitly specified.
+ if (!(isa<BindingDecl>(D) && DVar.CKind == OMPC_unknown)) {
+ return DVar;
+ }
}
DefaultDataSharingAttributes IterDA = Iter->DefaultAttr;
@@ -2181,6 +2188,8 @@ bool SemaOpenMP::isOpenMPCapturedByRef(const ValueDecl *D, unsigned Level,
// Find the directive that is associated with the provided scope.
D = cast<ValueDecl>(D->getCanonicalDecl());
QualType Ty = D->getType();
+ if (const auto *BD = dyn_cast<BindingDecl>(D))
+ Ty = BD->getDecomposedDecl()->getType();
bool IsVariableUsedInMapClause = false;
if (DSAStack->hasExplicitDirective(isOpenMPTargetExecutionDirective, Level)) {
@@ -2432,6 +2441,10 @@ VarDecl *SemaOpenMP::isOpenMPCapturedDecl(ValueDecl *D, bool CheckScopeInfo,
assert(getLangOpts().OpenMP && "OpenMP is not allowed");
D = getCanonicalDecl(D);
+ if (auto *BD = dyn_cast<BindingDecl>(D)) {
+ if (!BD->getHoldingVar())
+ D = cast<VarDecl>(BD->getDecomposedDecl());
+ }
auto *VD = dyn_cast<VarDecl>(D);
// Do not capture constexpr variables.
if (VD && VD->isConstexpr())
@@ -2973,7 +2986,30 @@ void SemaOpenMP::EndOpenMPDSABlock(Stmt *CurDirective) {
continue;
}
auto *DRE = cast<DeclRefExpr>(DE->IgnoreParens());
- auto *VD = cast<VarDecl>(DRE->getDecl());
+ auto *D = DRE->getDecl();
+ if (auto *BD = dyn_cast<BindingDecl>(D)) {
+ QualType Type = BD->getType().getNonReferenceType();
+ const DSAStackTy::DSAVarData DVar =
+ DSAStack->getTopDSA(BD, /*FromParent=*/false);
+ if (DVar.CKind != OMPC_lastprivate) {
+ // The variable is also a firstprivate, so initialization sequence
+ // for private copy is generated already.
+ PrivateCopies.push_back(nullptr);
+ continue;
+ }
+ VarDecl *VDPrivate = buildVarDecl(
+ SemaRef, DE->getExprLoc(), Type.getUnqualifiedType(), BD->getName(),
+ BD->hasAttrs() ? &BD->getAttrs() : nullptr, DRE);
+ SemaRef.ActOnUninitializedDecl(VDPrivate);
+ if (VDPrivate->isInvalidDecl()) {
+ PrivateCopies.push_back(nullptr);
+ continue;
+ }
+ PrivateCopies.push_back(buildDeclRefExpr(
+ SemaRef, VDPrivate, DE->getType(), DE->getExprLoc()));
+ continue;
+ }
+ auto *VD = cast<VarDecl>(D);
QualType Type = VD->getType().getNonReferenceType();
const DSAStackTy::DSAVarData DVar =
DSAStack->getTopDSA(VD, /*FromParent=*/false);
@@ -3867,6 +3903,212 @@ static void reportOriginalDsa(Sema &SemaRef, const DSAStackTy *Stack,
}
}
+namespace {
+/// Visitor to collect variables used in a statement.
+class VarUsageVisitor : public DynamicRecursiveASTVisitor {
+ llvm::SmallPtrSet<const VarDecl *, 8> &UsedVars;
+ llvm::SmallPtrSet<const BindingDecl *, 8> &UsedBindings;
+
+public:
+ VarUsageVisitor(llvm::SmallPtrSet<const VarDecl *, 8> &UsedVars,
+ llvm::SmallPtrSet<const BindingDecl *, 8> &UsedBindings)
+ : UsedVars(UsedVars), UsedBindings(UsedBindings) {}
+
+ bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
+ if (auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ UsedVars.insert(cast<VarDecl>(VD->getCanonicalDecl()));
+ else if (auto *BD = dyn_cast<BindingDecl>(DRE->getDecl()))
+ UsedBindings.insert(cast<BindingDecl>(BD->getCanonicalDecl()));
+ return true;
+ }
+};
+} // namespace
+
+/// Check if bindings from the same structured binding have conflicting
+/// capture kinds (by-ref vs by-copy). Bindings share the underlying
+/// DecompositionDecl storage, so mixing by-ref and by-copy clauses on
+///
diff erent bindings from the same decomposition is not representable.
+/// For example: map(a) creates by-ref, firstprivate(b) creates by-copy.
+static bool checkDecompositionCaptureConflict(
+ Sema &SemaRef, OpenMPDirectiveKind DKind,
+ llvm::SmallDenseMap<const DecompositionDecl *,
+ std::pair<bool, SourceLocation>, 4> &SeenDecompositions,
+ const ValueDecl *D, SourceLocation ELoc, OpenMPClauseKind ClauseKind) {
+ const auto *BD = dyn_cast<BindingDecl>(D);
+ if (!BD)
+ return false;
+
+ const auto *DD = dyn_cast<DecompositionDecl>(BD->getDecomposedDecl());
+ if (!DD)
+ return false;
+
+ // Determine if this clause creates by-ref or by-copy capture.
+ bool IsByRef = false;
+ switch (ClauseKind) {
+ case OMPC_map:
+ case OMPC_shared:
+ // Map and shared clauses are by-reference.
+ IsByRef = true;
+ break;
+ case OMPC_firstprivate:
+ case OMPC_private:
+ case OMPC_lastprivate:
+ // These are by-copy.
+ IsByRef = false;
+ break;
+ default:
+ // Other clauses don't create capture conflicts.
+ return false;
+ }
+ auto [It, Inserted] = SeenDecompositions.insert({DD, {IsByRef, ELoc}});
+ if (!Inserted && It->second.first != IsByRef) {
+ // Conflict: same DecompositionDecl needs both by-ref and by-copy
+ // Emit diagnostic showing the binding name, not the decomposition.
+ SemaRef.Diag(ELoc,
+ diag::err_omp_decomposition_bindings_
diff erent_capture_kinds)
+ << BD;
+ return true;
+ }
+ return false;
+}
+
+/// Helper to check all clauses in a directive for structured binding
+/// capture conflicts. Returns true if an error was found. If Body is
+/// provided and the directive has default(shared), bindings used in the
+/// body but not listed in explicit clauses are treated as implicit
+/// shared (by-reference).
+static bool checkClausesForDecompositionConflicts(Sema &SemaRef,
+ OpenMPDirectiveKind DKind,
+ ArrayRef<OMPClause *> Clauses,
+ Stmt *Body = nullptr) {
+ llvm::SmallDenseMap<const DecompositionDecl *,
+ std::pair<bool, SourceLocation>, 4>
+ SeenDecompositions;
+ llvm::SmallPtrSet<const BindingDecl *, 8> ExplicitBindings;
+ bool HasError = false;
+ bool HasDefaultShared = false;
+ SourceLocation DefaultSharedLoc;
+ for (OMPClause *C : Clauses) {
+ if (auto *DC = dyn_cast<OMPDefaultClause>(C)) {
+ if (DC->getDefaultKind() == llvm::omp::DefaultKind::OMP_DEFAULT_shared) {
+ HasDefaultShared = true;
+ DefaultSharedLoc = DC->getBeginLoc();
+ }
+ continue;
+ }
+ OpenMPClauseKind CK = C->getClauseKind();
+ if (CK != OMPC_map && CK != OMPC_firstprivate && CK != OMPC_private &&
+ CK != OMPC_shared && CK != OMPC_lastprivate)
+ continue;
+ ArrayRef<Expr *> Varlist;
+ if (auto *MPC = dyn_cast<OMPMapClause>(C))
+ Varlist = MPC->varlist();
+ else if (auto *FPC = dyn_cast<OMPFirstprivateClause>(C))
+ Varlist = FPC->varlist();
+ else if (auto *PC = dyn_cast<OMPPrivateClause>(C))
+ Varlist = PC->varlist();
+ else if (auto *SC = dyn_cast<OMPSharedClause>(C))
+ Varlist = SC->varlist();
+ else if (auto *LPC = dyn_cast<OMPLastprivateClause>(C))
+ Varlist = LPC->varlist();
+
+ for (Expr *VE : Varlist) {
+ if (auto *DRE = dyn_cast<DeclRefExpr>(VE->IgnoreParenImpCasts())) {
+ ValueDecl *D = DRE->getDecl();
+ // Look through OMPCapturedExprDecl (used when clauses reference
+ // variables from an enclosing captured region) to find the
+ // underlying BindingDecl.
+ if (auto *CED = dyn_cast<OMPCapturedExprDecl>(D)) {
+ if (auto *InitDRE =
+ dyn_cast<DeclRefExpr>(CED->getInit()->IgnoreParenImpCasts()))
+ D = InitDRE->getDecl();
+ }
+ if (auto *BD = dyn_cast<BindingDecl>(D))
+ ExplicitBindings.insert(cast<BindingDecl>(BD->getCanonicalDecl()));
+ if (checkDecompositionCaptureConflict(
+ SemaRef, DKind, SeenDecompositions, D, VE->getExprLoc(), CK))
+ HasError = true;
+ }
+ }
+ }
+
+ // With default(shared), bindings used in the body but not in an explicit
+ // clause become implicit shared (by-reference). Check those for conflicts
+ // with explicit by-copy clauses on the same decomposition.
+ if (HasDefaultShared && Body) {
+ llvm::SmallPtrSet<const VarDecl *, 8> UsedVars;
+ llvm::SmallPtrSet<const BindingDecl *, 8> UsedBindings;
+ VarUsageVisitor Visitor(UsedVars, UsedBindings);
+ Visitor.TraverseStmt(Body);
+ for (const BindingDecl *BD : UsedBindings) {
+ if (ExplicitBindings.contains(BD))
+ continue;
+ if (checkDecompositionCaptureConflict(SemaRef, DKind, SeenDecompositions,
+ BD, DefaultSharedLoc, OMPC_shared))
+ HasError = true;
+ }
+ }
+ return HasError;
+}
+
+/// Check if original variable is explicitly mapped but only bindings are used.
+/// Returns true if an error was found.
+static bool checkOriginalVarMappedButOnlyBindingsUsed(
+ Sema &SemaRef, ArrayRef<OMPClause *> Clauses, Stmt *Body) {
+ llvm::SmallDenseMap<const VarDecl *, SourceLocation, 4> MappedOrigVars;
+
+ for (OMPClause *C : Clauses) {
+ if (auto *MPC = dyn_cast<OMPMapClause>(C)) {
+ for (Expr *VE : MPC->varlist()) {
+ if (auto *DRE = dyn_cast<DeclRefExpr>(VE->IgnoreParenImpCasts())) {
+ if (auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+ MappedOrigVars[VD->getCanonicalDecl()] = VE->getExprLoc();
+ }
+ }
+ }
+ }
+ }
+ if (MappedOrigVars.empty())
+ return false;
+
+ llvm::SmallPtrSet<const VarDecl *, 8> UsedVars;
+ llvm::SmallPtrSet<const BindingDecl *, 8> UsedBindings;
+ VarUsageVisitor Visitor(UsedVars, UsedBindings);
+ Visitor.TraverseStmt(Body);
+ bool HasError = false;
+ for (const auto &Entry : MappedOrigVars) {
+ const VarDecl *OrigVar = Entry.first;
+ SourceLocation Loc = Entry.second;
+
+ // Check if this original variable has bindings that are used.
+ // For reference bindings (`auto &[a, b] = p`), the bindings alias the
+ // original variable, so mapping `p` covers them correctly.
+ bool BindingsFromThisVarUsed = false;
+ for (const BindingDecl *BD : UsedBindings) {
+ if (auto *DD = dyn_cast<DecompositionDecl>(BD->getDecomposedDecl())) {
+ if (DD->getType()->isReferenceType())
+ continue;
+ if (auto *OrigFromDD = DD->getOriginalVar().Var) {
+ if (OrigFromDD->getCanonicalDecl() == OrigVar) {
+ BindingsFromThisVarUsed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Error if: original is mapped, bindings are used, but original is not
+ // used.
+ if (BindingsFromThisVarUsed && !UsedVars.count(OrigVar)) {
+ SemaRef.Diag(Loc, diag::err_omp_original_var_mapped_bindings_only_used)
+ << OrigVar;
+ HasError = true;
+ }
+ }
+
+ return HasError;
+}
+
static OpenMPMapClauseKind
getMapClauseKindFromModifier(OpenMPDefaultmapClauseModifier M,
bool IsAggregateOrDeclareTarget,
@@ -3948,6 +4190,19 @@ static bool hasConstQualifiedMappingType(QualType T) {
}
namespace {
+/// Try to extract the original variable from a DecompositionDecl.
+/// If extraction fails, emit a diagnostic. Returns the original VarDecl* on
+/// success, nullptr on failure.
+static const VarDecl *getOriginalVarOrDiagnose(Sema &S,
+ const DecompositionDecl *DD,
+ SourceLocation Loc) {
+ auto Result = DD->getOriginalVar();
+ if (!Result.Var)
+ S.Diag(Loc, diag::err_omp_unsupported_structured_binding_init)
+ << Result.DiagKind;
+ return Result.Var;
+}
+
struct VariableImplicitInfo {
static const unsigned MapKindNum = OMPC_MAP_unknown;
static const unsigned DefaultmapKindNum = OMPC_DEFAULTMAP_unknown + 1;
@@ -4019,14 +4274,21 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
E->isInstantiationDependent() ||
E->isNonOdrUse() == clang::NOUR_Unevaluated)
return;
- if (auto *VD = dyn_cast<VarDecl>(E->getDecl())) {
+ ValueDecl *D = const_cast<ValueDecl *>(E->getDecl());
+ BindingDecl *BD = dyn_cast<BindingDecl>(D);
+ if (BD)
+ D = BD->getDecomposedDecl();
+ if (auto *VD = dyn_cast<VarDecl>(D)) {
+ // For BindingDecls, use the original binding for DSA lookups;
+ // fall back to DecompositionDecl if lookup fails.
+ ValueDecl *LookupDecl = BD ? static_cast<ValueDecl *>(BD) : VD;
// Check the datasharing rules for the expressions in the clauses.
if (!CS || (isa<OMPCapturedExprDecl>(VD) && !CS->capturesVariable(VD) &&
- !Stack->getTopDSA(VD, /*FromParent=*/false).RefExpr &&
+ !Stack->getTopDSA(LookupDecl, /*FromParent=*/false).RefExpr &&
!Stack->isImplicitDefaultFirstprivateFD(VD))) {
if (auto *CED = dyn_cast<OMPCapturedExprDecl>(VD))
if (!CED->hasAttr<OMPCaptureNoInitAttr>()) {
- Visit(CED->getInit());
+ Visit(const_cast<Expr *>(CED->getInit()));
return;
}
} else if (VD->isImplicit() || isa<OMPCapturedExprDecl>(VD))
@@ -4035,6 +4297,29 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
if (!Stack->isImplicitDefaultFirstprivateFD(VD))
return;
VD = VD->getCanonicalDecl();
+ // Skip DecompositionDecls (but not BindingDecls in DSA contexts) -
+ // they should be handled through explicit mapping of the original
+ // variable or as member expressions. When bindings are captured,
+ // the original variable is what needs to be mapped, not the
+ // decomposition itself. However, BindingDecls need DSA checking for
+ // default(none)/private/firstprivate contexts, but NOT in target
+ // offloading contexts (where map clause handles them).
+ if (isa<DecompositionDecl>(VD)) {
+ // For BindingDecls, continue checking only if:
+ // - We are in a DSA context (tasking/parallel/worksharing/teams), or
+ // - We are in a target context and the binding is scalar (implicit
+ // firstprivate).
+ bool InTargetAndScalar = BD &&
+ isOpenMPTargetExecutionDirective(DKind) &&
+ BD->getType()->isScalarType();
+ if (!BD ||
+ (isOpenMPTargetExecutionDirective(DKind) && !InTargetAndScalar) ||
+ (!InTargetAndScalar && !isImplicitOrExplicitTaskingRegion(DKind) &&
+ !isOpenMPParallelDirective(DKind) &&
+ !isOpenMPWorksharingDirective(DKind) &&
+ !isOpenMPTeamsDirective(DKind)))
+ return;
+ }
// Skip internally declared variables.
if (VD->hasLocalStorage() && CS && !CS->capturesVariable(VD) &&
!Stack->isImplicitDefaultFirstprivateFD(VD) &&
@@ -4044,9 +4329,16 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
if (Stack->isUsesAllocatorsDecl(VD))
return;
- DSAStackTy::DSAVarData DVar = Stack->getTopDSA(VD, /*FromParent=*/false);
+ DSAStackTy::DSAVarData DVar =
+ Stack->getTopDSA(LookupDecl, /*FromParent=*/false);
+ // If lookup on BindingDecl failed, try on DecompositionDecl.
+ if (BD && !DVar.RefExpr)
+ DVar = Stack->getTopDSA(VD, /*FromParent=*/false);
// Check if the variable has explicit DSA set and stop analysis if it so.
- if (DVar.RefExpr || !ImplicitDeclarations.insert(VD).second)
+ if (DVar.RefExpr ||
+ !ImplicitDeclarations
+ .insert(cast<ValueDecl>(LookupDecl->getCanonicalDecl()))
+ .second)
return;
// Skip internally declared static variables.
@@ -4074,11 +4366,14 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
if (!InheritedDSA && (Stack->getDefaultDSA() == DSA_firstprivate ||
Stack->getDefaultDSA() == DSA_private)) {
DSAStackTy::DSAVarData DVar =
- Stack->getImplicitDSA(VD, /*FromParent=*/false);
+ Stack->getImplicitDSA(LookupDecl, /*FromParent=*/false);
+ if (BD && DVar.CKind == OMPC_unknown)
+ DVar = Stack->getImplicitDSA(VD, /*FromParent=*/false);
InheritedDSA = DVar.CKind == OMPC_unknown;
}
if (InheritedDSA)
- VarsWithInheritedDSA[VD] = E;
+ VarsWithInheritedDSA[cast<ValueDecl>(
+ LookupDecl->getCanonicalDecl())] = E;
if (Stack->getDefaultDSA() == DSA_none)
return;
}
@@ -4091,8 +4386,10 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
// data-sharing attribute clause (including a data-sharing attribute
// clause on a combined construct where target. is one of the
// constituent constructs), or an is_device_ptr clause.
- OpenMPDefaultmapClauseKind ClauseKind =
- getVariableCategoryFromDecl(SemaRef.getLangOpts(), VD);
+ // For BindingDecls, use the binding's type to determine category.
+ OpenMPDefaultmapClauseKind ClauseKind = getVariableCategoryFromDecl(
+ SemaRef.getLangOpts(), BD ? static_cast<const ValueDecl *>(BD)
+ : static_cast<const ValueDecl *>(VD));
if (SemaRef.getLangOpts().OpenMP >= 50) {
bool IsModifierNone = Stack->getDefaultmapModifier(ClauseKind) ==
OMPC_DEFAULTMAP_MODIFIER_none;
@@ -4110,7 +4407,8 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
auto ME = MapExprComponents.rend();
return MI != ME && MI->getAssociatedDeclaration() == VD;
})) {
- VarsWithInheritedDSA[VD] = E;
+ VarsWithInheritedDSA[cast<ValueDecl>(
+ LookupDecl->getCanonicalDecl())] = E;
return;
}
}
@@ -4129,34 +4427,51 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
if (isOpenMPTargetExecutionDirective(DKind) &&
!Stack->isLoopControlVariable(VD).first) {
- if (!Stack->checkMappableExprComponentListsForDecl(
- VD, /*CurrentRegionOnly=*/true,
- [this](OMPClauseMappableExprCommon::MappableExprComponentListRef
- StackComponents,
- OpenMPClauseKind) {
+ // Check if VD is already mapped. For DecompositionDecls, also check if
+ // the original variable they decompose has been mapped (via BindingDecl
+ // map clauses).
+ bool AlreadyMapped = Stack->checkMappableExprComponentListsForDecl(
+ VD, /*CurrentRegionOnly=*/true, [this](auto StackComponents, auto) {
+ if (SemaRef.LangOpts.OpenMP >= 50)
+ return !StackComponents.empty();
+ // Variable is used if it has been marked as an array, array
+ // section, array shaping or the variable itself.
+ return StackComponents.size() == 1 ||
+ llvm::all_of(
+ llvm::drop_begin(llvm::reverse(StackComponents)),
+ [](const auto &MC) {
+ return MC.getAssociatedDeclaration() == nullptr &&
+ (isa<ArraySectionExpr>(
+ MC.getAssociatedExpression()) ||
+ isa<OMPArrayShapingExpr>(
+ MC.getAssociatedExpression()) ||
+ isa<ArraySubscriptExpr>(
+ MC.getAssociatedExpression()));
+ });
+ });
+
+ // For DecompositionDecls, check if the original variable has been
+ // mapped.
+ const auto *DD = dyn_cast<DecompositionDecl>(VD);
+ if (!AlreadyMapped && DD) {
+ // Don't diagnose here. Just check if we can extract the original
+ // var. Diagnostics happen when processing explicit map clauses.
+ if (const VarDecl *OrigVar = DD->getOriginalVar().Var) {
+ AlreadyMapped = Stack->checkMappableExprComponentListsForDecl(
+ OrigVar, /*CurrentRegionOnly=*/true,
+ [this](auto StackComponents, auto) {
if (SemaRef.LangOpts.OpenMP >= 50)
return !StackComponents.empty();
- // Variable is used if it has been marked as an array, array
- // section, array shaping or the variable itself.
- return StackComponents.size() == 1 ||
- llvm::all_of(
- llvm::drop_begin(llvm::reverse(StackComponents)),
- [](const OMPClauseMappableExprCommon::
- MappableComponent &MC) {
- return MC.getAssociatedDeclaration() ==
- nullptr &&
- (isa<ArraySectionExpr>(
- MC.getAssociatedExpression()) ||
- isa<OMPArrayShapingExpr>(
- MC.getAssociatedExpression()) ||
- isa<ArraySubscriptExpr>(
- MC.getAssociatedExpression()));
- });
- })) {
+ return StackComponents.size() == 1;
+ });
+ }
+ }
+ if (!AlreadyMapped) {
bool IsFirstprivate = false;
// By default lambdas are captured as firstprivates.
+ QualType CheckType = BD ? BD->getType() : VD->getType();
if (const auto *RD =
- VD->getType().getNonReferenceType()->getAsCXXRecordDecl())
+ CheckType.getNonReferenceType()->getAsCXXRecordDecl())
IsFirstprivate = RD->isLambda();
IsFirstprivate =
IsFirstprivate || (Stack->mustBeFirstprivate(ClauseKind) && !Res);
@@ -4183,7 +4498,7 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
// enclosing worksharing or parallel construct may not be accessed in an
// explicit task.
DVar = Stack->hasInnermostDSA(
- VD,
+ LookupDecl,
[](OpenMPClauseKind C, bool AppliedToPointee) {
return C == OMPC_reduction && !AppliedToPointee;
},
@@ -4195,12 +4510,14 @@ class DSAAttrChecker final : public StmtVisitor<DSAAttrChecker, void> {
if (isOpenMPTaskingDirective(DKind) && DVar.CKind == OMPC_reduction) {
ErrorFound = true;
SemaRef.Diag(ELoc, diag::err_omp_reduction_in_task);
- reportOriginalDsa(SemaRef, Stack, VD, DVar);
+ reportOriginalDsa(SemaRef, Stack, BD ? LookupDecl : VD, DVar);
return;
}
// Define implicit data-sharing attributes for task.
- DVar = Stack->getImplicitDSA(VD, /*FromParent=*/false);
+ DVar = Stack->getImplicitDSA(LookupDecl, /*FromParent=*/false);
+ if (BD && DVar.CKind == OMPC_unknown)
+ DVar = Stack->getImplicitDSA(VD, /*FromParent=*/false);
if (((isOpenMPTaskingDirective(DKind) && DVar.CKind != OMPC_shared) ||
(((Stack->getDefaultDSA() == DSA_firstprivate &&
DVar.CKind == OMPC_firstprivate) ||
@@ -4697,12 +5014,21 @@ static OMPCapturedExprDecl *buildCaptureDecl(Sema &S, IdentifierInfo *Id,
static DeclRefExpr *buildCapture(Sema &S, ValueDecl *D, Expr *CaptureExpr,
bool WithInit) {
OMPCapturedExprDecl *CD;
- if (VarDecl *VD = S.OpenMP().isOpenMPCapturedDecl(D))
- CD = cast<OMPCapturedExprDecl>(VD);
- else
+ // For BindingDecls, always create a new capture instead of reusing the
+ // decomposed decl, since the decomposed decl is a regular VarDecl, not an
+ // OMPCapturedExprDecl.
+ if (!isa<BindingDecl>(D)) {
+ if (VarDecl *VD = S.OpenMP().isOpenMPCapturedDecl(D))
+ CD = cast<OMPCapturedExprDecl>(VD);
+ else
+ CD = buildCaptureDecl(S, D->getIdentifier(), CaptureExpr, WithInit,
+ S.CurContext,
+ /*AsExpression=*/false);
+ } else {
CD = buildCaptureDecl(S, D->getIdentifier(), CaptureExpr, WithInit,
S.CurContext,
/*AsExpression=*/false);
+ }
return buildDeclRefExpr(S, CD, CD->getType().getNonReferenceType(),
CaptureExpr->getExprLoc());
}
@@ -5454,7 +5780,7 @@ getPrivateItem(Sema &S, Expr *&RefExpr, SourceLocation &ELoc,
RefExpr = RefExpr->IgnoreParenImpCasts();
auto *DE = dyn_cast_or_null<DeclRefExpr>(RefExpr);
auto *ME = dyn_cast_or_null<MemberExpr>(RefExpr);
- if ((!DE || !isa<VarDecl>(DE->getDecl())) &&
+ if ((!DE || !isa<VarDecl, BindingDecl>(DE->getDecl())) &&
(S.getCurrentThisType().isNull() || !ME ||
!isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()) ||
!isa<FieldDecl>(ME->getMemberDecl()))) {
@@ -8033,6 +8359,11 @@ SemaOpenMP::ActOnOpenMPParallelDirective(ArrayRef<OMPClause *> Clauses,
if (!AStmt)
return StmtError();
+ // Check for conflicting capture kinds on structured bindings.
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_parallel, Clauses,
+ AStmt))
+ return StmtError();
+
setBranchProtectedScope(SemaRef, OMPD_parallel, AStmt);
return OMPParallelDirective::Create(
@@ -8570,6 +8901,19 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
}
}
+ // Helper lambda to reject structured bindings used as OpenMP loop
+ // control variables. Loop counters are implicitly private, but bindings
+ // share storage with their decomposition, so this is not representable.
+ auto CheckBindingAsLoopVar = [&](ValueDecl *LoopVar,
+ SourceLocation Loc) -> bool {
+ if (!isa<BindingDecl>(LoopVar))
+ return false;
+ if (EmitDiags)
+ SemaRef.Diag(Loc, diag::err_omp_loop_var_is_structured_binding)
+ << LoopVar;
+ return true;
+ };
+
// Helper lambda to check if a loop variable is already used in an outer
// loop.
auto CheckLoopVarReuse = [&](ValueDecl *LoopVar, SourceLocation Loc) -> bool {
@@ -8598,6 +8942,8 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
return setLCDeclAndLB(LoopVar, ME, BO->getRHS(), EmitDiags);
}
ValueDecl *LoopVar = DRE->getDecl();
+ if (CheckBindingAsLoopVar(LoopVar, DRE->getLocation()))
+ return true;
if (CheckLoopVarReuse(LoopVar, DRE->getLocation()))
return true;
return setLCDeclAndLB(LoopVar, DRE, BO->getRHS(), EmitDiags);
@@ -8645,6 +8991,8 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
return setLCDeclAndLB(LoopVar, ME, CE->getArg(1), EmitDiags);
}
ValueDecl *LoopVar = DRE->getDecl();
+ if (CheckBindingAsLoopVar(LoopVar, DRE->getLocation()))
+ return true;
if (CheckLoopVarReuse(LoopVar, DRE->getLocation()))
return true;
return setLCDeclAndLB(LoopVar, DRE, CE->getArg(1), EmitDiags);
@@ -11358,6 +11706,9 @@ StmtResult SemaOpenMP::ActOnOpenMPTeamsGenericLoopDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_teams_loop, Clauses))
+ return StmtError();
+
// OpenMP 5.1 [2.11.7, loop construct, Restrictions]
// A list item may not appear in a lastprivate clause unless it is the
// loop iteration variable of a loop that is associated with the construct.
@@ -11400,6 +11751,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetTeamsGenericLoopDirective(
DSAStack))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_target_teams_loop,
+ Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS =
setBranchProtectedScope(SemaRef, OMPD_target_teams_loop, AStmt);
@@ -11461,6 +11819,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetParallelGenericLoopDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_target_parallel_loop,
+ Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
// OpenMP 5.1 [2.11.7, loop construct, Restrictions]
// A list item may not appear in a lastprivate clause unless it is the
// loop iteration variable of a loop that is associated with the construct.
@@ -11731,6 +12096,10 @@ StmtResult SemaOpenMP::ActOnOpenMPTaskDirective(ArrayRef<OMPClause *> Clauses,
{OMPC_detach, OMPC_mergeable}))
return StmtError();
+ // Check for conflicting capture kinds on structured bindings.
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_task, Clauses, AStmt))
+ return StmtError();
+
setBranchProtectedScope(SemaRef, OMPD_task, AStmt);
return OMPTaskDirective::Create(getASTContext(), StartLoc, EndLoc, Clauses,
@@ -13711,6 +14080,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetDirective(ArrayRef<OMPClause *> Clauses,
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ // Check for conflicting capture kinds on structured bindings.
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_target, Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS = setBranchProtectedScope(SemaRef, OMPD_target, AStmt);
// OpenMP [2.16, Nesting of Regions]
@@ -13765,6 +14141,14 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetParallelDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ // Check for conflicting capture kinds on structured bindings.
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_target_parallel,
+ Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
setBranchProtectedScope(SemaRef, OMPD_target_parallel, AStmt);
return OMPTargetParallelDirective::Create(
@@ -13781,6 +14165,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetParallelForDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_target_parallel_for,
+ Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS =
setBranchProtectedScope(SemaRef, OMPD_target_parallel_for, AStmt);
@@ -13942,6 +14333,9 @@ StmtResult SemaOpenMP::ActOnOpenMPTeamsDirective(ArrayRef<OMPClause *> Clauses,
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_teams, Clauses))
+ return StmtError();
+
// Report affected OpenMP target offloading behavior when in HIP lang-mode.
if (getLangOpts().HIP && (DSAStack->getParentDirective() == OMPD_target))
Diag(StartLoc, diag::warn_hip_omp_target_directives);
@@ -14527,6 +14921,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetParallelForSimdDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(
+ SemaRef, OMPD_target_parallel_for_simd, Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS =
setBranchProtectedScope(SemaRef, OMPD_target_parallel_for_simd, AStmt);
@@ -14559,6 +14960,12 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetSimdDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_target_simd, Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS = setBranchProtectedScope(SemaRef, OMPD_target_simd, AStmt);
OMPLoopBasedDirective::HelperExprs B;
@@ -14590,6 +14997,10 @@ StmtResult SemaOpenMP::ActOnOpenMPTeamsDistributeDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_teams_distribute,
+ Clauses))
+ return StmtError();
+
CapturedStmt *CS =
setBranchProtectedScope(SemaRef, OMPD_teams_distribute, AStmt);
@@ -14620,6 +15031,9 @@ StmtResult SemaOpenMP::ActOnOpenMPTeamsDistributeSimdDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_teams_distribute_simd,
+ Clauses))
+ return StmtError();
CapturedStmt *CS =
setBranchProtectedScope(SemaRef, OMPD_teams_distribute_simd, AStmt);
@@ -14655,6 +15069,10 @@ StmtResult SemaOpenMP::ActOnOpenMPTeamsDistributeParallelForSimdDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(
+ SemaRef, OMPD_teams_distribute_parallel_for_simd, Clauses))
+ return StmtError();
+
CapturedStmt *CS = setBranchProtectedScope(
SemaRef, OMPD_teams_distribute_parallel_for_simd, AStmt);
@@ -14689,6 +15107,10 @@ StmtResult SemaOpenMP::ActOnOpenMPTeamsDistributeParallelForDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(
+ SemaRef, OMPD_teams_distribute_parallel_for, Clauses))
+ return StmtError();
+
CapturedStmt *CS = setBranchProtectedScope(
SemaRef, OMPD_teams_distribute_parallel_for, AStmt);
@@ -14719,6 +15141,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetTeamsDirective(
if (!AStmt)
return StmtError();
+ if (checkClausesForDecompositionConflicts(SemaRef, OMPD_target_teams,
+ Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
setBranchProtectedScope(SemaRef, OMPD_target_teams, AStmt);
if (validateMultidimClauses(*this, Clauses, /*MayHaveBareClause=*/true))
@@ -14737,6 +15166,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetTeamsDistributeDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(
+ SemaRef, OMPD_target_teams_distribute, Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS =
setBranchProtectedScope(SemaRef, OMPD_target_teams_distribute, AStmt);
@@ -14766,6 +15202,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetTeamsDistributeParallelForDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(
+ SemaRef, OMPD_target_teams_distribute_parallel_for, Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS = setBranchProtectedScope(
SemaRef, OMPD_target_teams_distribute_parallel_for, AStmt);
@@ -14796,6 +15239,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetTeamsDistributeParallelForSimdDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(
+ SemaRef, OMPD_target_teams_distribute_parallel_for_simd, Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS = setBranchProtectedScope(
SemaRef, OMPD_target_teams_distribute_parallel_for_simd, AStmt);
@@ -14829,6 +15279,13 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetTeamsDistributeSimdDirective(
if (validateMultidimClauses(*this, Clauses))
return StmtError();
+ if (checkClausesForDecompositionConflicts(
+ SemaRef, OMPD_target_teams_distribute_simd, Clauses))
+ return StmtError();
+
+ if (checkOriginalVarMappedButOnlyBindingsUsed(SemaRef, Clauses, AStmt))
+ return StmtError();
+
CapturedStmt *CS = setBranchProtectedScope(
SemaRef, OMPD_target_teams_distribute_simd, AStmt);
@@ -19942,7 +20399,8 @@ OMPClause *SemaOpenMP::ActOnOpenMPPrivateClause(ArrayRef<Expr *> VarList,
SemaRef, VDPrivate, RefExpr->getType().getUnqualifiedType(), ELoc);
DeclRefExpr *Ref = nullptr;
- if (!VD && !SemaRef.CurContext->isDependentContext()) {
+ bool IsBindingDecl = isa<BindingDecl>(D);
+ if (!VD && !IsBindingDecl && !SemaRef.CurContext->isDependentContext()) {
auto *FD = dyn_cast<FieldDecl>(D);
VarDecl *VD = FD ? DSAStack->getImplicitFDCapExprDecl(FD) : nullptr;
if (VD)
@@ -19951,11 +20409,13 @@ OMPClause *SemaOpenMP::ActOnOpenMPPrivateClause(ArrayRef<Expr *> VarList,
else
Ref = buildCapture(SemaRef, D, SimpleRefExpr, /*WithInit=*/false);
}
- if (!IsImplicitClause)
+ if (!IsImplicitClause) {
DSAStack->addDSA(D, RefExpr->IgnoreParens(), OMPC_private, Ref);
- Vars.push_back((VD || SemaRef.CurContext->isDependentContext())
- ? RefExpr->IgnoreParens()
- : Ref);
+ }
+ Vars.push_back(
+ (VD || IsBindingDecl || SemaRef.CurContext->isDependentContext())
+ ? RefExpr->IgnoreParens()
+ : Ref);
PrivateCopies.push_back(VDPrivateRefExpr);
}
@@ -20172,19 +20632,54 @@ OMPClause *SemaOpenMP::ActOnOpenMPFirstprivateClause(ArrayRef<Expr *> VarList,
}
Type = Type.getUnqualifiedType();
- VarDecl *VDPrivate =
- buildVarDecl(SemaRef, ELoc, Type, D->getName(),
- D->hasAttrs() ? &D->getAttrs() : nullptr,
- VD ? cast<DeclRefExpr>(SimpleRefExpr) : nullptr);
+
+ // For BindingDecls, use the DecompositionDecl's type so all bindings from
+ // the same decomposition are accessible in the privatized region. We do
+ // this after conflict checking to preserve diagnostic errors. Don't modify
+ // RefExpr/SimpleRefExpr to keep diagnostics working.
+ bool IsBindingDecl = isa<BindingDecl>(D);
+ VarDecl *PrivateVD = VD;
+ QualType PrivateType = Type;
+ if (IsBindingDecl) {
+ const auto *BD = cast<BindingDecl>(D);
+ PrivateVD = cast<VarDecl>(BD->getDecomposedDecl());
+ // PrivateType stays as the BindingDecl's type (Type), not the
+ // DecompositionDecl's type.
+ }
+
+ // For regular arrays, pass the original var ref. For BindingDecls, don't
+ // pass the DecompositionDecl ref since VDPrivate has the binding's type,
+ // not the decomposition type.
+ VarDecl *VDPrivate = buildVarDecl(SemaRef, ELoc, PrivateType, D->getName(),
+ D->hasAttrs() ? &D->getAttrs() : nullptr,
+ (PrivateVD && !IsBindingDecl)
+ ? cast<DeclRefExpr>(SimpleRefExpr)
+ : nullptr);
+
// Generate helper private variable and initialize it with the value of the
// original variable. The address of the original variable is replaced by
// the address of the new private variable in the CodeGen. This new variable
// is not added to IdResolver, so the code in the OpenMP region uses
// original variable for proper diagnostics and variable capturing.
Expr *VDInitRefExpr = nullptr;
- // For arrays generate initializer for single element and replace it by the
- // original array element in CodeGen.
- if (Type->isArrayType()) {
+
+ // For BindingDecls, VDPrivate should have the binding's type (not the
+ // DecompositionDecl's type), and will be initialized from the binding's
+ // field in the original DecompositionDecl.
+ if (IsBindingDecl && !PrivateType->isArrayType()) {
+ // For non-array bindings, create a simple copy initialization.
+ VarDecl *VDInit = buildVarDecl(SemaRef, RefExpr->getExprLoc(),
+ PrivateType, ".firstprivate.temp");
+ VDInitRefExpr =
+ buildDeclRefExpr(SemaRef, VDInit, PrivateType, RefExpr->getExprLoc());
+
+ // Initialize VDPrivate from VDInit (which will point to the field).
+ SemaRef.AddInitializerToDecl(
+ VDPrivate, SemaRef.DefaultLvalueConversion(VDInitRefExpr).get(),
+ /*DirectInit=*/false);
+ } else if (Type->isArrayType()) {
+ // For arrays generate initializer for single element and replace it by
+ // the original array element in CodeGen.
VarDecl *VDInit =
buildVarDecl(SemaRef, RefExpr->getExprLoc(), ElemType, D->getName());
VDInitRefExpr = buildDeclRefExpr(SemaRef, VDInit, ElemType, ELoc);
@@ -20228,7 +20723,7 @@ OMPClause *SemaOpenMP::ActOnOpenMPFirstprivateClause(ArrayRef<Expr *> VarList,
if (!VD && !SemaRef.CurContext->isDependentContext()) {
if (TopDVar.CKind == OMPC_lastprivate) {
Ref = TopDVar.PrivateCopy;
- } else {
+ } else if (!IsBindingDecl) {
auto *FD = dyn_cast<FieldDecl>(D);
VarDecl *VD = FD ? DSAStack->getImplicitFDCapExprDecl(FD) : nullptr;
if (VD)
@@ -20243,9 +20738,10 @@ OMPClause *SemaOpenMP::ActOnOpenMPFirstprivateClause(ArrayRef<Expr *> VarList,
}
if (!IsImplicitClause)
DSAStack->addDSA(D, RefExpr->IgnoreParens(), OMPC_firstprivate, Ref);
- Vars.push_back((VD || SemaRef.CurContext->isDependentContext())
- ? RefExpr->IgnoreParens()
- : Ref);
+ Vars.push_back(
+ (VD || IsBindingDecl || SemaRef.CurContext->isDependentContext())
+ ? RefExpr->IgnoreParens()
+ : Ref);
PrivateCopies.push_back(VDPrivateRefExpr);
Inits.push_back(VDInitRefExpr);
}
@@ -20297,6 +20793,14 @@ OMPClause *SemaOpenMP::ActOnOpenMPLastprivateClause(
QualType Type = D->getType();
auto *VD = dyn_cast<VarDecl>(D);
+ // Structured bindings with conditional modifier are currently not
+ // supported.
+ if (LPKind == OMPC_LASTPRIVATE_conditional && isa<BindingDecl>(D)) {
+ Diag(ELoc, diag::err_omp_unsupported_on_binding) << 1;
+ Diag(D->getLocation(), diag::note_defined_here) << D;
+ continue;
+ }
+
// OpenMP [2.14.3.5, Restrictions, C/C++, p.2]
// A variable that appears in a lastprivate clause must not have an
// incomplete type or a reference type.
@@ -20408,9 +20912,10 @@ OMPClause *SemaOpenMP::ActOnOpenMPLastprivateClause(
if (!isOpenMPCapturedDecl(D))
ExprCaptures.push_back(Ref->getDecl());
}
- if ((TopDVar.CKind == OMPC_firstprivate && !TopDVar.PrivateCopy) ||
- (!isOpenMPCapturedDecl(D) &&
- Ref->getDecl()->hasAttr<OMPCaptureNoInitAttr>())) {
+ if (Ref &&
+ ((TopDVar.CKind == OMPC_firstprivate && !TopDVar.PrivateCopy) ||
+ (!isOpenMPCapturedDecl(D) &&
+ Ref->getDecl()->hasAttr<OMPCaptureNoInitAttr>()))) {
ExprResult RefRes = SemaRef.DefaultLvalueConversion(Ref);
if (!RefRes.isUsable())
continue;
@@ -20424,9 +20929,11 @@ OMPClause *SemaOpenMP::ActOnOpenMPLastprivateClause(
}
}
DSAStack->addDSA(D, RefExpr->IgnoreParens(), OMPC_lastprivate, Ref);
- Vars.push_back((VD || SemaRef.CurContext->isDependentContext())
- ? RefExpr->IgnoreParens()
- : Ref);
+ bool IsBindingDecl = isa<BindingDecl>(D);
+ Vars.push_back(
+ (VD || IsBindingDecl || SemaRef.CurContext->isDependentContext())
+ ? RefExpr->IgnoreParens()
+ : Ref);
SrcExprs.push_back(PseudoSrcExpr);
DstExprs.push_back(PseudoDstExpr);
AssignmentOps.push_back(AssignmentOp.get());
@@ -21116,6 +21623,13 @@ static bool actOnOMPReductionKindClause(
}
auto *VD = dyn_cast<VarDecl>(D);
+ auto *BD = dyn_cast<BindingDecl>(D);
+ if (BD) {
+ // FIXME: reductions on bindings are rejected.
+ S.Diag(ELoc, diag::err_omp_unsupported_on_binding) << 0;
+ continue;
+ }
+
// OpenMP [2.9.3.3, Restrictions, C/C++, p.3]
// A variable that appears in a private clause must not have an incomplete
// type or a reference type.
@@ -21672,7 +22186,8 @@ static bool actOnOMPReductionKindClause(
DeclRefExpr *Ref = nullptr;
Expr *VarsExpr = RefExpr->IgnoreParens();
- if (!VD && !S.CurContext->isDependentContext()) {
+ bool IsBindingDecl = isa<BindingDecl>(D);
+ if (!VD && !IsBindingDecl && !S.CurContext->isDependentContext()) {
if (ASE || OASE) {
TransformExprToCaptures RebuildToCapture(S, D);
VarsExpr =
@@ -21910,6 +22425,15 @@ OMPClause *SemaOpenMP::ActOnOpenMPLinearClause(
if (!D)
continue;
+ // Linear on bindings only works for simple simd, not parallel constructs.
+ if (isa<BindingDecl>(D)) {
+ OpenMPDirectiveKind DKind = DSAStack->getCurrentDirective();
+ if (isOpenMPParallelDirective(DKind)) {
+ Diag(ELoc, diag::err_omp_unsupported_on_binding) << 2;
+ continue;
+ }
+ }
+
QualType Type = D->getType();
auto *VD = dyn_cast<VarDecl>(D);
@@ -21940,7 +22464,8 @@ OMPClause *SemaOpenMP::ActOnOpenMPLinearClause(
VarDecl *Init = buildVarDecl(SemaRef, ELoc, Type, ".linear.start");
Expr *InitExpr;
DeclRefExpr *Ref = nullptr;
- if (!VD && !SemaRef.CurContext->isDependentContext()) {
+ bool IsBindingDecl = isa<BindingDecl>(D);
+ if (!VD && !IsBindingDecl && !SemaRef.CurContext->isDependentContext()) {
Ref = buildCapture(SemaRef, D, SimpleRefExpr, /*WithInit=*/false);
if (!isOpenMPCapturedDecl(D)) {
ExprCaptures.push_back(Ref->getDecl());
@@ -21961,16 +22486,17 @@ OMPClause *SemaOpenMP::ActOnOpenMPLinearClause(
if (LinKind == OMPC_LINEAR_uval)
InitExpr = VD ? VD->getInit() : SimpleRefExpr;
else
- InitExpr = VD ? SimpleRefExpr : Ref;
+ InitExpr = (VD || IsBindingDecl) ? SimpleRefExpr : Ref;
SemaRef.AddInitializerToDecl(
Init, SemaRef.DefaultLvalueConversion(InitExpr).get(),
/*DirectInit=*/false);
DeclRefExpr *InitRef = buildDeclRefExpr(SemaRef, Init, Type, ELoc);
DSAStack->addDSA(D, RefExpr->IgnoreParens(), OMPC_linear, Ref);
- Vars.push_back((VD || SemaRef.CurContext->isDependentContext())
- ? RefExpr->IgnoreParens()
- : Ref);
+ Vars.push_back(
+ (VD || IsBindingDecl || SemaRef.CurContext->isDependentContext())
+ ? RefExpr->IgnoreParens()
+ : Ref);
Privates.push_back(PrivateRef);
Inits.push_back(InitRef);
}
@@ -22069,13 +22595,16 @@ static bool FinishOpenMPLinearClause(OMPLinearClause &Clause, DeclRefExpr *IV,
// Build privatized reference to the current linear var.
auto *DE = cast<DeclRefExpr>(SimpleRefExpr);
Expr *CapturedRef;
- if (LinKind == OMPC_LINEAR_uval)
+ if (isa<BindingDecl>(DE->getDecl())) {
+ CapturedRef = SimpleRefExpr;
+ } else if (LinKind == OMPC_LINEAR_uval) {
CapturedRef = cast<VarDecl>(DE->getDecl())->getInit();
- else
+ } else {
CapturedRef =
buildDeclRefExpr(SemaRef, cast<VarDecl>(DE->getDecl()),
DE->getType().getUnqualifiedType(), DE->getExprLoc(),
/*RefersToCapture=*/true);
+ }
// Build update: Var = InitExpr + IV * Step
ExprResult Update;
@@ -23040,14 +23569,91 @@ class MapBaseChecker final : public StmtVisitor<MapBaseChecker, bool> {
public:
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
- if (!isa<VarDecl>(DRE->getDecl())) {
+ ValueDecl *D = DRE->getDecl();
+ Expr *E = DRE;
+
+ // Handle BindingDecls by mapping them as member accesses.
+ // When the user writes:
+ // auto [a, b] = p;
+ // #pragma omp target map(tofrom:a) map(to:b)
+ // we transform it to:
+ // #pragma omp target map(tofrom:p.x) map(to:p.y)
+ // This avoids conflicts when
diff erent bindings have
diff erent map types.
+ if (auto *BD = dyn_cast<BindingDecl>(D)) {
+ auto *DD = cast<DecompositionDecl>(BD->getDecomposedDecl());
+ Expr *BindingExpr = BD->getBinding();
+
+ // Check if the binding is a member expression (struct/class
+ // decomposition).
+ if (auto *ME = dyn_cast_or_null<MemberExpr>(BindingExpr)) {
+
+ // Use the DecompositionDecl as the base for the member expression.
+ // The structured binding creates a copy (if initialized from a
+ // variable) or holds the only storage (if initialized from a prvalue).
+ // Using DD ensures map clauses reference the correct storage.
+ Expr *BaseExpr =
+ SemaRef
+ .BuildDeclarationNameExpr(
+ CXXScopeSpec(),
+ DeclarationNameInfo(DD->getDeclName(), DRE->getLocation()),
+ DD)
+ .get();
+
+ // Create member expression: base.member.
+ E = MemberExpr::Create(
+ SemaRef.Context, BaseExpr, /*IsArrow=*/false, ME->getOperatorLoc(),
+ ME->getQualifierLoc(), ME->getTemplateKeywordLoc(),
+ ME->getMemberDecl(), ME->getFoundDecl(), ME->getMemberNameInfo(),
+ /*TemplateArgs=*/nullptr, ME->getType(), ME->getValueKind(),
+ ME->getObjectKind(), ME->isNonOdrUse());
+
+ // Now process this as a member expression, which will properly
+ // handle the field-level mapping.
+ return Visit(E);
+ }
+ if (auto *ASE = dyn_cast_or_null<ArraySubscriptExpr>(BindingExpr)) {
+ Expr *BaseExpr =
+ SemaRef
+ .BuildDeclarationNameExpr(
+ CXXScopeSpec(),
+ DeclarationNameInfo(DD->getDeclName(), DRE->getLocation()),
+ DD)
+ .get();
+ E = new (SemaRef.Context) ArraySubscriptExpr(
+ BaseExpr, ASE->getIdx(), ASE->getType(), ASE->getValueKind(),
+ ASE->getObjectKind(), ASE->getRBracketLoc());
+ return Visit(E);
+ }
+ // Tuple-like should already be rejected; do not map DD as a silent
+ // fallback.
+ SemaRef.Diag(DRE->getExprLoc(),
+ diag::err_omp_unsupported_structured_binding_init)
+ << 4;
+ return false;
+ }
+ // Handle DecompositionDecl directly (implicit captures).
+ else if (auto *DD = dyn_cast<DecompositionDecl>(D)) {
+ if (const VarDecl *OrigVar =
+ getOriginalVarOrDiagnose(SemaRef, DD, DRE->getExprLoc())) {
+ D = const_cast<VarDecl *>(OrigVar);
+ DeclarationNameInfo NameInfo(D->getDeclName(), DRE->getLocation());
+ E = DeclRefExpr::Create(SemaRef.Context, DRE->getQualifierLoc(),
+ DRE->getTemplateKeywordLoc(), D,
+ /*RefersToEnclosingVariableOrCapture=*/false,
+ NameInfo, D->getType(), DRE->getValueKind(),
+ DRE->getFoundDecl(),
+ /*TemplateArgs=*/nullptr, DRE->isNonOdrUse());
+ } else {
+ return false;
+ }
+ } else if (!isa<VarDecl>(D)) {
emitErrorMsg();
return false;
}
assert(!RelevantExpr && "RelevantExpr is expected to be nullptr");
- RelevantExpr = DRE;
+ RelevantExpr = E;
// Record the component.
- Components.emplace_back(DRE, DRE->getDecl(), IsNonContiguous);
+ Components.emplace_back(E, D, IsNonContiguous);
return true;
}
@@ -24018,7 +24624,6 @@ static void checkMappableExpressionList(
}
Expr *SimpleExpr = RE->IgnoreParenCasts();
-
if (!RE->isLValue()) {
if (SemaRef.getLangOpts().OpenMP < 50) {
SemaRef.Diag(
@@ -24031,6 +24636,30 @@ static void checkMappableExpressionList(
continue;
}
+ // Check for unsupported structured bindings early.
+ if (!NoDiagnose) {
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(SimpleExpr)) {
+ const DecompositionDecl *DD = nullptr;
+ const BindingDecl *BD = nullptr;
+ if (const auto *B = dyn_cast<BindingDecl>(DRE->getDecl())) {
+ BD = B;
+ DD = cast<DecompositionDecl>(B->getDecomposedDecl());
+ } else if (const auto *D =
+ dyn_cast<DecompositionDecl>(DRE->getDecl())) {
+ DD = D;
+ }
+ if (DD) {
+ if (BD && BD->getHoldingVar()) {
+ SemaRef.Diag(ELoc,
+ diag::err_omp_unsupported_structured_binding_init)
+ << 4;
+ continue;
+ }
+ if (!getOriginalVarOrDiagnose(SemaRef, DD, ELoc))
+ continue;
+ }
+ }
+ }
OMPClauseMappableExprCommon::MappableExprComponentList CurComponents;
ValueDecl *CurDeclaration = nullptr;
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 331c1866e36ec..74fe253efa137 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -4708,10 +4708,43 @@ static bool
buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
SmallVectorImpl<CapturedStmt::Capture> &Captures,
SmallVectorImpl<Expr *> &CaptureInits) {
+ bool HasError = false; // Track if any errors occurred.
+ 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)) {
+ // Detect structured bindings in OpenMP captured regions.
+ // When a BindingDecl (e.g., 'a' from 'auto [a, b] = p')
+ // is referenced inside an OpenMP region.
+ // isVariableCapturable() in SemaExpr.cpp already resets this to the
+ // DecompositionDecl during per-use expression checking. This runs
+ // later, at region-end (ActOnCapturedRegionEnd), over the
+ // already-built capture list, catching captures added without going
+ // through that per-use path (e.g. via explicit map clauses).
+ if (RSI->CapRegionKind == CR_OpenMP && BD->getHoldingVar()) {
+ S.Diag(Cap.getLocation(), diag::err_capture_tuple_binding_openmp)
+ << CapVar;
+ S.Diag(CapVar->getLocation(), diag::note_entity_declared_at)
+ << CapVar;
+ HasError = true; // Mark error but continue.
+ continue; // Skip this capture, move to next.
+ }
+ CapVar = cast<VarDecl>(BD->getDecomposedDecl());
+ }
+ if (RSI->CapRegionKind == CR_OpenMP) {
+ if (auto *DD = dyn_cast<DecompositionDecl>(CapVar)) {
+ if (!CapturedDecomposed.insert(DD).second) {
+ continue; // Skip duplicate
+ }
+ }
+ }
+ }
+
// Form the initializer for the capture.
ExprResult Init = S.BuildCaptureInit(Cap, Cap.getLocation(),
RSI->CapRegionKind == CR_OpenMP);
@@ -4719,32 +4752,38 @@ buildCapturedStmtCaptureList(Sema &S, CapturedRegionScopeInfo *RSI,
// FIXME: Bail out now if the capture is not used and the initializer has
// no side-effects.
- // Create a field for this capture.
- FieldDecl *Field = S.BuildCaptureField(RSI->TheRecordDecl, Cap);
+ // Build the capture field. For OpenMP, pass IsOpenMP=true to handle
+ // DecompositionDecl captures correctly.
+ FieldDecl *Field = S.BuildCaptureField(RSI->TheRecordDecl, Cap,
+ RSI->CapRegionKind == CR_OpenMP);
// 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));
} else {
assert(Cap.isVariableCapture() && "unknown kind of capture");
- if (S.getLangOpts().OpenMP && RSI->CapRegionKind == CR_OpenMP)
- 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())));
+ if (S.getLangOpts().OpenMP && RSI->CapRegionKind == CR_OpenMP) {
+ const ValueDecl *DSAVar = Cap.getVariable();
+ // DSAs are tracked per binding; a captured DecompositionDecl has no
+ // own DSA entry.
+ if (const auto *DD = dyn_cast<DecompositionDecl>(DSAVar))
+ if (!DD->bindings().empty())
+ DSAVar = *DD->bindings().begin();
+ S.OpenMP().setOpenMPCaptureKind(Field, DSAVar, RSI->OpenMPLevel);
+ }
+ Captures.emplace_back(Cap.getLocation(),
+ Cap.isReferenceCapture() ? CapturedStmt::VCK_ByRef
+ : CapturedStmt::VCK_ByCopy,
+ cast<VarDecl>(CapVar));
}
CaptureInits.push_back(Init.get());
}
- return false;
+ return HasError;
}
static std::optional<int>
diff --git a/clang/test/OpenMP/structured-bindings-ast.cpp b/clang/test/OpenMP/structured-bindings-ast.cpp
new file mode 100644
index 0000000000000..0eee8158ca798
--- /dev/null
+++ b/clang/test/OpenMP/structured-bindings-ast.cpp
@@ -0,0 +1,33 @@
+// RUN: %clang_cc1 -fopenmp -std=c++20 -triple x86_64-unknown-linux-gnu \
+// RUN: -ast-dump %s | FileCheck %s
+
+
+struct Point {
+ int x, y;
+};
+
+void test_target_firstprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp target firstprivate(a)
+ {
+ a = 1;
+ }
+}
+
+// CHECK-LABEL: test_target_firstprivate
+// CHECK: OMPCaptureKindAttr {{.*}} firstprivate
+// CHECK-NOT: OMPCaptureKindAttr {{.*}} map
+
+void test_target_parallel_firstprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp target parallel firstprivate(a)
+ {
+ a = 1;
+ }
+}
+
+// CHECK-LABEL: test_target_parallel_firstprivate
+// CHECK: OMPCaptureKindAttr {{.*}} firstprivate
+// CHECK-NOT: OMPCaptureKindAttr {{.*}} map
diff --git a/clang/test/OpenMP/structured-bindings-codegen.cpp b/clang/test/OpenMP/structured-bindings-codegen.cpp
new file mode 100644
index 0000000000000..56788e8f331b3
--- /dev/null
+++ b/clang/test/OpenMP/structured-bindings-codegen.cpp
@@ -0,0 +1,4372 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --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 -std=c++20 -triple x86_64-unknown-unknown -fopenmp-targets=x86_64-pc-linux-gnu -emit-llvm %s -o - | FileCheck %s
+
+// RUN: %clang_cc1 -fopenmp -fopenmp-version=51 -std=c++20 -triple x86_64-unknown-unknown -fopenmp-targets=x86_64-pc-linux-gnu -emit-pch -o %t %s
+// RUN: %clang_cc1 -fopenmp -fopenmp-version=51 -std=c++20 -triple x86_64-unknown-unknown -fopenmp-targets=x86_64-pc-linux-gnu -include-pch %t -verify %s -emit-llvm -o - | FileCheck %s
+
+// expected-no-diagnostics
+
+#ifndef HEADER
+#define HEADER
+
+struct Point { int x, y; };
+struct Point3D { int x, y, z; };
+struct Triple { int x, y, z; };
+
+void test_binding_and_orig_separate() {
+ Triple t{1, 2, 3};
+ auto [a, b, c] = t;
+
+#pragma omp target map(tofrom: t)
+ {
+ t.x = 100;
+ a = a + 1;
+
+ }
+}
+
+void test_target_implicit_map() {
+ Point p{1, 2};
+ auto [a, b] = p;
+
+#pragma omp target
+ {
+ int sum = a + b;
+ }
+}
+
+void test_target_parallel() {
+ Point p{3, 4};
+ auto [a, b] = p;
+
+#pragma omp target parallel
+ {
+ int sum = a + b;
+ }
+}
+
+void test_target_parallel_for() {
+ Point p{5, 6};
+ auto [a, b] = p;
+
+#pragma omp target parallel for
+ for (int i = 0; i < 10; i++) {
+ int result = a + b + i;
+ }
+}
+
+void test_firstprivate_dsa() {
+ Point p{7, 8};
+ auto [a, b] = p;
+
+#pragma omp parallel firstprivate(p)
+ {
+ int sum = a + b;
+ }
+}
+
+void test_shared_dsa() {
+ Point p{9, 10};
+ auto [a, b] = p;
+
+#pragma omp parallel shared(p)
+ {
+ int sum = a + b;
+ }
+}
+
+void test_reduction_dsa() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ int sum = 0;
+
+#pragma omp parallel for reduction(+:sum)
+ for (int i = 0; i < 10; i++) {
+ sum += a + b;
+ }
+}
+
+void test_parallel_for_() {
+ Point p{11, 12};
+ auto [a, b] = p;
+
+#pragma omp parallel for
+ for (int i = 0; i < 10; i++) {
+ int result = a + b + i;
+ }
+}
+
+void test_parallel_for_simd_() {
+ Point p{13, 14};
+ auto [a, b] = p;
+
+#pragma omp parallel for simd
+ for (int i = 0; i < 10; i++) {
+ int result = a + b + i;
+ }
+}
+
+void test_target_teams_distribute() {
+ Point p{15, 16};
+ auto [a, b] = p;
+
+#pragma omp target teams distribute
+ for (int i = 0; i < 10; i++) {
+ int result = a + b + i;
+ }
+}
+
+void test_task() {
+ Point p{17, 18};
+ auto [a, b] = p;
+
+#pragma omp task
+ {
+ int sum = a + b;
+ }
+}
+
+void test_task_firstprivate() {
+ Point p{18, 19};
+ auto [a, b] = p;
+
+#pragma omp task firstprivate(a) firstprivate(b)
+ {
+ int sum = a + b;
+ }
+}
+
+void test_task_firstprivate_with_shared() {
+ Point p{18, 19};
+ auto [a, b] = p;
+ int ra = 0, rb = 0;
+#pragma omp task firstprivate(a, b) shared(ra, rb)
+ {
+ ra = a;
+ rb = b;
+ }
+#pragma omp taskwait
+}
+
+void test_task_depend() {
+ Point p{19, 20};
+ auto [a, b] = p;
+
+#pragma omp task depend(in: a, b)
+ {
+ int sum = a + b;
+ }
+}
+
+void test_taskloop_() {
+ Point p{21, 22};
+ auto [a, b] = p;
+
+#pragma omp taskloop
+ for (int i = 0; i < 10; i++) {
+ int result = a + b + i;
+ }
+}
+
+template<typename T>
+int test_template_bas(T p) {
+ auto [a, b] = p;
+ int result = 0;
+
+#pragma omp parallel reduction(+:result)
+ {
+ result = a + b;
+ }
+ return result;
+}
+
+template<typename T>
+int test_template_target(T p) {
+ auto [a, b] = p;
+ int result = 0;
+
+#pragma omp target map(tofrom: result)
+ {
+ result = a + b;
+ }
+ return result;
+}
+
+template<typename T>
+int test_template_task(T p) {
+ auto [a, b] = p;
+ int result = 0;
+
+#pragma omp task shared(result)
+ {
+ result = a + b;
+ }
+#pragma omp taskwait
+ return result;
+}
+
+template<typename T>
+int test_template_3_bindings(T p) {
+ auto [x, y, z] = p;
+ int result = 0;
+
+#pragma omp parallel reduction(+:result)
+ {
+ result = x + y + z;
+ }
+ return result;
+}
+
+void instantiate_templates() {
+ Point p2{1, 2};
+ Point3D p3{1, 2, 3};
+
+ test_template_bas(p2);
+ test_template_bas(Point{3, 4});
+ test_template_target(p2);
+ test_template_task(p2);
+ test_template_3_bindings(p3);
+}
+
+void test_static_binding() {
+ static Point p{23, 24};
+ static auto [a, b] = p;
+
+#pragma omp parallel
+ {
+ int sum = a + b;
+ }
+}
+
+void test_static_binding_shared() {
+ static Point p{25, 26};
+ static auto [a, b] = p;
+
+#pragma omp parallel shared(p)
+ {
+ a = a + 1;
+ b = b + 1;
+ }
+}
+
+void test_array_target() {
+ int arr[2] = {27, 28};
+ auto [a, b] = arr;
+
+#pragma omp target
+ {
+ int sum = a + b;
+ }
+}
+
+void test_array_task() {
+ int arr[2] = {29, 30};
+ auto [a, b] = arr;
+
+#pragma omp task
+ {
+ int sum = a + b;
+ }
+}
+
+void test_nested() {
+ Point p{31, 32};
+ auto [a, b] = p;
+
+#pragma omp parallel
+ {
+#pragma omp critical
+ {
+ int sum = a + b;
+ }
+
+#pragma omp task
+ {
+ int product = a * b;
+ }
+ }
+}
+
+void test_reference_binding() {
+ Point p{31, 32};
+ auto& [a, b] = p;
+
+#pragma omp parallel
+ {
+ int sum = a + b;
+ }
+}
+
+void test_const_binding() {
+ const Point p{33, 34};
+ const auto [a, b] = p;
+
+#pragma omp parallel
+ {
+ int sum = a + b;
+ }
+}
+
+void test_multiple_bindings() {
+ Point p1{33, 34};
+ Point p2{35, 36};
+ auto [a, b] = p1;
+ auto [c, d] = p2;
+
+#pragma omp parallel
+ {
+ int sum = a + b + c + d;
+ }
+}
+
+void test_multiple_bindings_mixed_dsa() {
+ Point p1{37, 38};
+ Point p2{39, 40};
+ auto [a, b] = p1;
+ auto [c, d] = p2;
+
+#pragma omp parallel firstprivate(p1) shared(p2)
+ {
+ int result = a + b + c + d;
+ }
+}
+
+void test_array_3_elements() {
+ int arr[3] = {35, 36, 37};
+ auto [a, b, c] = arr;
+
+#pragma omp parallel
+ {
+ int sum = a + b + c;
+ }
+}
+
+void test_single() {
+ Point p{38, 39};
+ auto [a, b] = p;
+
+#pragma omp parallel
+#pragma omp single
+ {
+ int sum = a + b;
+ }
+}
+
+void test_sections() {
+ Point p{40, 41};
+ auto [a, b] = p;
+
+#pragma omp parallel sections
+ {
+#pragma omp section
+ { int sum = a + b; }
+#pragma omp section
+ { int
diff = a - b; }
+ }
+}
+
+void test_nested_parallel() {
+ Point p{42, 43};
+ auto [a, b] = p;
+
+#pragma omp parallel
+ {
+#pragma omp parallel
+ {
+ int sum = a + b;
+ }
+ }
+}
+
+void test_simd_() {
+ Point p{44, 45};
+ auto [a, b] = p;
+
+#pragma omp simd
+ for (int i = 0; i < 10; i++) {
+ int result = a + b + i;
+ }
+}
+
+void test_simd_write_binding() {
+ Point p{1, 2};
+ auto [a, b] = p;
+
+#pragma omp simd
+ for (int i = 0; i < 10; ++i)
+ a = i;
+}
+
+void test_simd_lastprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+
+#pragma omp simd lastprivate(a)
+ for (int i = 0; i < 10; ++i)
+ a = i;
+}
+
+void test_private_individual_bindings() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp parallel private(a)
+ {
+ a = 2;
+ }
+}
+
+void test_firstprivate_individual_bindings() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp parallel firstprivate(b)
+ {
+ b = b + 10;
+ }
+}
+
+void test_static_bindings() {
+ static auto [a, b] = Point{1, 2};
+#pragma omp parallel
+ {
+ (void)(a + b);
+ }
+}
+
+void use(int a);
+
+void test_shadowing() {
+ auto [a, b] = Point{1, 2};
+#pragma omp parallel private(a)
+ {
+ use(a);
+ {
+ auto [a, b] = Point{10, 20};
+ use(a);
+ }
+ use(a);
+ }
+ use(a);
+}
+
+void test_simd_private_then_parallel() {
+ Point p{1,2};
+ auto [a,b] = p;
+#pragma omp simd private(a)
+ for (int i=0;i<10;++i) a += i;
+#pragma omp parallel
+ {
+ use(a);
+ }
+}
+
+void test_linear_binding() {
+ Point p{1, 2};
+ auto [a, b] = p;
+
+#pragma omp simd linear(a:1)
+ for (int i = 0; i < 10; ++i) {
+ a += 1;
+ use(a);
+ }
+}
+
+void test_lastprivate_binding() {
+ Point p{1, 2};
+ auto [a, b] = p;
+
+#pragma omp parallel for lastprivate(a)
+ for (int i = 0; i < 10; ++i) {
+ a = i * 10;
+ }
+ use(a);
+}
+
+void test_binding_name_collision() {
+ Point p1{1, 2};
+ auto [a, b] = p1;
+
+#pragma omp parallel
+ {
+ Point p2{10, 20};
+ auto [a, b] = p2;
+ a += 1;
+ use(a);
+ }
+}
+
+struct NonTrivialCopy {
+ int value;
+ int copy_count;
+
+ NonTrivialCopy(int v) : value(v), copy_count(0) {}
+ NonTrivialCopy() : value(0), copy_count(0) {}
+ NonTrivialCopy(const NonTrivialCopy &other)
+ : value(other.value), copy_count(other.copy_count + 1) {}
+
+ ~NonTrivialCopy() {}
+};
+
+struct Pair {
+ NonTrivialCopy x;
+ NonTrivialCopy y;
+};
+
+void test_firstprivate_nontrivial_sb() {
+ Pair p{NonTrivialCopy(10), NonTrivialCopy(20)};
+ auto [a, b] = p;
+
+#pragma omp parallel firstprivate(a)
+ {
+ (void)a.value;
+ }
+}
+
+void test_firstprivate_ref_binding_sb() {
+ Pair p{NonTrivialCopy(10), NonTrivialCopy(20)};
+ auto& [a, b] = p;
+
+#pragma omp parallel firstprivate(a)
+ {
+ (void)a.value;
+ }
+}
+
+void test_firstprivate_const_ref_binding_sb() {
+ Pair p{NonTrivialCopy(10), NonTrivialCopy(20)};
+ const auto& [a, b] = p;
+
+#pragma omp parallel firstprivate(a)
+ {
+ (void)a.value;
+ }
+}
+
+void test_firstprivate_multiple_bindings_sb() {
+ Pair p{NonTrivialCopy(10), NonTrivialCopy(20)};
+ auto [a, b] = p;
+
+#pragma omp parallel firstprivate(a, b)
+ {
+ (void)(a.value + b.value);
+ }
+}
+
+struct WithDtor {
+ int *ptr;
+ WithDtor(int v) : ptr(new int(v)) {}
+ WithDtor(const WithDtor &other) : ptr(new int(*other.ptr)) {}
+ ~WithDtor() { delete ptr; }
+};
+
+struct PairWithDtor {
+ WithDtor x;
+ WithDtor y;
+};
+
+void test_firstprivate_with_destructor_sb() {
+ PairWithDtor p{WithDtor(100), WithDtor(200)};
+ auto [a, b] = p;
+
+#pragma omp parallel firstprivate(a)
+ {
+ (void)(*a.ptr);
+ }
+}
+
+void test_firstprivate_array_bindings_sb() {
+ NonTrivialCopy arr[3] = {NonTrivialCopy(1), NonTrivialCopy(2), NonTrivialCopy(3)};
+ auto [a, b, c] = arr;
+
+
+#pragma omp parallel firstprivate(a, b, c)
+ {
+ (void)(a.value + b.value + c.value);
+ }
+}
+
+struct Inner {
+ int val;
+ Inner(int v) : val(v) {}
+ Inner(const Inner &o) : val(o.val * 2) {}
+ ~Inner() {}
+};
+
+struct Outer {
+ Inner i1;
+ Inner i2;
+};
+
+void test_firstprivate_nested_struct_sb() {
+ Outer o{Inner(5), Inner(10)};
+ auto [x, y] = o;
+
+#pragma omp parallel firstprivate(x)
+ {
+ (void)x.val;
+ }
+}
+
+void test_firstprivate_ref_binding_both_bindings_sb() {
+ Pair p{NonTrivialCopy(10), NonTrivialCopy(20)};
+ auto& [a, b] = p;
+
+#pragma omp parallel firstprivate(a, b)
+ {
+ (void)(a.value + b.value);
+ }
+}
+
+
+void test_firstprivate_const_ref_binding_both_bindings_sb() {
+ Pair p{NonTrivialCopy(10), NonTrivialCopy(20)};
+ const auto& [a, b] = p;
+
+#pragma omp parallel firstprivate(a, b)
+ {
+ (void)(a.value + b.value);
+ }
+}
+
+void test_lambda_capture_binding_by_value() {
+ Point p{10, 20};
+ auto [a, b] = p;
+ auto lambda = [a]() {
+ return a + a;
+ };
+ int result = lambda();
+}
+
+
+void test_lambda_capture_binding_by_ref() {
+ Point p{10, 20};
+ auto [a, b] = p;
+ auto lambda = [&a]() {
+ a = 100;
+ };
+ lambda();
+}
+
+
+void test_lambda_capture_multiple_bindings() {
+ Point p{10, 20};
+ auto [a, b] = p;
+ auto lambda = [a, &b]() {
+ b = a + 50;
+ };
+ lambda();
+}
+
+
+void test_lambda_implicit_capture() {
+ Point p{10, 20};
+ auto [a, b] = p;
+ auto lambda = [=]() {
+ return a + b;
+ };
+ lambda();
+}
+
+// Test parallel for with linear clause on binding.
+void test_parallel_for_linear() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp parallel
+ {
+#pragma omp for linear(a : 1)
+ for (int i = 0; i < 10; ++i) {
+ a += 1;
+ use(a);
+ }
+ }
+}
+
+// Test taskloop with lastprivate clause on binding.
+void test_taskloop_lastprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp parallel
+ {
+#pragma omp taskloop lastprivate(a)
+ for (int i = 0; i < 10; ++i)
+ a = i * 10;
+ }
+}
+
+// Test task with shared clause on binding.
+void test_task_shared() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp parallel
+ {
+#pragma omp task shared(a)
+ { a = 42; }
+ }
+}
+
+// Test parallel for with firstprivate and lastprivate on binding.
+void test_parallel_for_firstprivate_lastprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp parallel
+ {
+#pragma omp for firstprivate(a) lastprivate(a)
+ for (int i = 0; i < 10; ++i)
+ a = i * 10;
+ }
+}
+
+// Test parallel for with private and lastprivate on
diff erent bindings.
+void test_private_plus_lastprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp parallel for private(a) lastprivate(b)
+ for (int i = 0; i < 10; ++i) {
+ a = i;
+ b = a;
+ }
+}
+
+void test_target_map_orig_use_binding() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp target map(tofrom: p)
+ {
+ p.x = 1;
+ a = 2;
+ }
+}
+#endif
+// CHECK-LABEL: define dso_local void @_Z30test_binding_and_orig_separatev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[T:%.*]], ptr align 4 @__const._Z30test_binding_and_orig_separatev.t, i64 12, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[T]], i64 12, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[T]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[T]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 2
+// CHECK: store ptr null, ptr [[TMP9]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z30test_binding_and_orig_separatev_l20(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(12) [[T:%.*]], ptr noundef nonnull align 4 dereferenceable(12) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[T]], ptr [[T_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[T_ADDR]], align 8, !nonnull [[META18:![0-9]+]], !align [[META19:![0-9]+]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: store i32 100, ptr [[X]], align 4
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X1]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP3]], 1
+// CHECK: [[X2:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: store i32 [[ADD]], ptr [[X2]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z24test_target_implicit_mapv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z24test_target_implicit_mapv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z24test_target_implicit_mapv_l32(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[A:%.*]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP3]], ptr [[B:%.*]], align 4
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z20test_target_parallelv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z20test_target_parallelv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z20test_target_parallelv_l42(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1:[0-9]+]], i32 1, ptr @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z20test_target_parallelv_l42.omp_outlined, ptr [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z20test_target_parallelv_l42.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[A:%.*]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP3]], ptr [[B:%.*]], align 4
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z24test_target_parallel_forv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z24test_target_parallel_forv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z24test_target_parallel_forv_l52(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z24test_target_parallel_forv_l52.omp_outlined, ptr [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z24test_target_parallel_forv_l52.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[A:%.*]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP3]], ptr [[B:%.*]], align 4
+// CHECK: [[TMP4:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[TMP4]], align 4
+// CHECK: call void @__kmpc_for_static_init_4(ptr @[[GLOB2:[0-9]+]], i32 [[TMP5]], i32 34, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP:%.*]] = icmp sgt i32 [[TMP6]], 9
+// CHECK: br i1 [[CMP]], label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
+// CHECK: [[COND_TRUE]]:
+// CHECK: br label %[[COND_END:.*]]
+// CHECK: [[COND_FALSE]]:
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: br label %[[COND_END]]
+// CHECK: [[COND_END]]:
+// CHECK: [[COND:%.*]] = phi i32 [ 9, %[[COND_TRUE]] ], [ [[TMP7]], %[[COND_FALSE]] ]
+// CHECK: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
+// CHECK: store i32 [[TMP8]], ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP1:%.*]] = icmp sle i32 [[TMP9]], [[TMP10]]
+// CHECK: br i1 [[CMP1]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP11]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP12]], [[TMP13]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[I]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP14]]
+// CHECK: store i32 [[ADD3]], ptr [[RESULT:%.*]], align 4
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP15]], 1
+// CHECK: store i32 [[ADD4]], 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 [[TMP5]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z21test_firstprivate_dsav(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z21test_firstprivate_dsav.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 2, ptr @_Z21test_firstprivate_dsav.omp_outlined, ptr [[TMP0]], ptr [[P]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z21test_firstprivate_dsav.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[P:%.*]]) #{{[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: store ptr [[P]], ptr [[P_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[P_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P1:%.*]], ptr align 4 [[TMP2]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP3]], [[TMP4]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z15test_shared_dsav(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z15test_shared_dsav.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z15test_shared_dsav.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z15test_shared_dsav.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// 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: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z18test_reduction_dsav(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z18test_reduction_dsav.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[SUM:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 2, ptr @_Z18test_reduction_dsav.omp_outlined, ptr [[SUM]], ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z18test_reduction_dsav.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[SUM:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[SUM]], ptr [[SUM_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[SUM_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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: store i32 0, ptr [[SUM1:%.*]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[TMP3]], align 4
+// CHECK: call void @__kmpc_for_static_init_4(ptr @[[GLOB2]], i32 [[TMP4]], i32 34, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP:%.*]] = icmp sgt i32 [[TMP5]], 9
+// CHECK: br i1 [[CMP]], label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
+// CHECK: [[COND_TRUE]]:
+// CHECK: br label %[[COND_END:.*]]
+// CHECK: [[COND_FALSE]]:
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: br label %[[COND_END]]
+// CHECK: [[COND_END]]:
+// CHECK: [[COND:%.*]] = phi i32 [ 9, %[[COND_TRUE]] ], [ [[TMP6]], %[[COND_FALSE]] ]
+// CHECK: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
+// CHECK: store i32 [[TMP7]], ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP2:%.*]] = icmp sle i32 [[TMP8]], [[TMP9]]
+// CHECK: br i1 [[CMP2]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP10]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[TMP11]], [[TMP12]]
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[SUM1]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP13]], [[ADD3]]
+// CHECK: store i32 [[ADD4]], ptr [[SUM1]], align 4
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD5:%.*]] = add nsw i32 [[TMP14]], 1
+// CHECK: store i32 [[ADD5]], 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 [[TMP4]])
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds [1 x ptr], ptr [[DOTOMP_REDUCTION_RED_LIST:%.*]], i64 0, i64 0
+// CHECK: store ptr [[SUM1]], ptr [[TMP15]], align 8
+// CHECK: [[TMP16:%.*]] = call i32 @__kmpc_reduce_nowait(ptr @[[GLOB3:[0-9]+]], i32 [[TMP4]], i32 1, i64 8, ptr [[DOTOMP_REDUCTION_RED_LIST]], ptr @_Z18test_reduction_dsav.omp_outlined.omp.reduction.reduction_func, ptr @.gomp_critical_user_.reduction.var)
+// CHECK: switch i32 [[TMP16]], label %[[DOTOMP_REDUCTION_DEFAULT:.*]] [
+// CHECK: i32 1, label %[[DOTOMP_REDUCTION_CASE1:.*]]
+// CHECK: i32 2, label %[[DOTOMP_REDUCTION_CASE2:.*]]
+// CHECK: ]
+// CHECK: [[DOTOMP_REDUCTION_CASE1]]:
+// CHECK: [[TMP17:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK: [[TMP18:%.*]] = load i32, ptr [[SUM1]], align 4
+// CHECK: [[ADD6:%.*]] = add nsw i32 [[TMP17]], [[TMP18]]
+// CHECK: store i32 [[ADD6]], ptr [[TMP1]], align 4
+// CHECK: call void @__kmpc_end_reduce_nowait(ptr @[[GLOB3]], i32 [[TMP4]], ptr @.gomp_critical_user_.reduction.var)
+// CHECK: br label %[[DOTOMP_REDUCTION_DEFAULT]]
+// CHECK: [[DOTOMP_REDUCTION_CASE2]]:
+// CHECK: [[TMP19:%.*]] = load i32, ptr [[SUM1]], align 4
+// CHECK: [[TMP20:%.*]] = atomicrmw add ptr [[TMP1]], i32 [[TMP19]] monotonic, align 4
+// CHECK: br label %[[DOTOMP_REDUCTION_DEFAULT]]
+// CHECK: [[DOTOMP_REDUCTION_DEFAULT]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z18test_reduction_dsav.omp_outlined.omp.reduction.reduction_func(
+// CHECK-SAME: ptr noundef [[TMP0:%.*]], ptr noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [1 x ptr], ptr [[TMP3]], i64 0, i64 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[TMP4]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [1 x ptr], ptr [[TMP2]], i64 0, i64 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[TMP7]], align 4
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[TMP5]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP8]], [[TMP9]]
+// CHECK: store i32 [[ADD]], ptr [[TMP7]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z18test_parallel_for_v(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z18test_parallel_for_v.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z18test_parallel_for_v.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z18test_parallel_for_v.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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]], 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]], 9
+// 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 [ 9, %[[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: [[CMP1:%.*]] = icmp sle i32 [[TMP7]], [[TMP8]]
+// CHECK: br i1 [[CMP1]], 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: [[MUL:%.*]] = mul nsw i32 [[TMP9]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP10]], [[TMP11]]
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[I]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP12]]
+// CHECK: store i32 [[ADD3]], ptr [[RESULT:%.*]], align 4
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP13]], 1
+// CHECK: store i32 [[ADD4]], 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 @_Z23test_parallel_for_simd_v(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z23test_parallel_for_simd_v.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z23test_parallel_for_simd_v.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z23test_parallel_for_simd_v.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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]], 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]], 9
+// 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 [ 9, %[[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, !llvm.access.group [[ACC_GRP22:![0-9]+]]
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: [[CMP1:%.*]] = icmp sle i32 [[TMP7]], [[TMP8]]
+// CHECK: br i1 [[CMP1]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP9]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[X]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[Y]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP10]], [[TMP11]]
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[I]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP12]]
+// CHECK: store i32 [[ADD3]], ptr [[RESULT:%.*]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP13]], 1
+// CHECK: store i32 [[ADD4]], ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP22]]
+// CHECK: br label %[[OMP_INNER_FOR_COND]], !llvm.loop [[LOOP23:![0-9]+]]
+// 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: [[TMP14:%.*]] = load i32, ptr [[DOTOMP_IS_LAST]], align 4
+// CHECK: [[TMP15:%.*]] = icmp ne i32 [[TMP14]], 0
+// CHECK: br i1 [[TMP15]], label %[[DOTOMP_FINAL_THEN:.*]], label %[[DOTOMP_FINAL_DONE:.*]]
+// CHECK: [[DOTOMP_FINAL_THEN]]:
+// CHECK: store i32 10, ptr [[I]], align 4
+// CHECK: br label %[[DOTOMP_FINAL_DONE]]
+// CHECK: [[DOTOMP_FINAL_DONE]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z28test_target_teams_distributev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z28test_target_teams_distributev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z28test_target_teams_distributev_l113(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_teams(ptr @[[GLOB1]], i32 1, ptr @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z28test_target_teams_distributev_l113.omp_outlined, ptr [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z28test_target_teams_distributev_l113.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[A:%.*]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP3]], ptr [[B:%.*]], align 4
+// CHECK: [[TMP4:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[TMP4]], align 4
+// CHECK: call void @__kmpc_for_static_init_4(ptr @[[GLOB4:[0-9]+]], i32 [[TMP5]], i32 92, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP:%.*]] = icmp sgt i32 [[TMP6]], 9
+// CHECK: br i1 [[CMP]], label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
+// CHECK: [[COND_TRUE]]:
+// CHECK: br label %[[COND_END:.*]]
+// CHECK: [[COND_FALSE]]:
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: br label %[[COND_END]]
+// CHECK: [[COND_END]]:
+// CHECK: [[COND:%.*]] = phi i32 [ 9, %[[COND_TRUE]] ], [ [[TMP7]], %[[COND_FALSE]] ]
+// CHECK: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
+// CHECK: store i32 [[TMP8]], ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP1:%.*]] = icmp sle i32 [[TMP9]], [[TMP10]]
+// CHECK: br i1 [[CMP1]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP11]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP12]], [[TMP13]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[I]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD2]], [[TMP14]]
+// CHECK: store i32 [[ADD3]], ptr [[RESULT:%.*]], align 4
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP15]], 1
+// CHECK: store i32 [[ADD4]], 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 @[[GLOB4]], i32 [[TMP5]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z9test_taskv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP1:%.*]] = call i32 @__kmpc_global_thread_num(ptr @[[GLOB1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z9test_taskv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP1]], i32 1, i64 48, i64 8, ptr @.omp_task_entry.)
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load ptr, ptr [[TMP5]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP6]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T:%.*]], ptr [[TMP7]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP9]], ptr [[TMP8]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T]], ptr [[TMP7]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP11]], ptr [[TMP10]], align 4
+// CHECK: [[TMP12:%.*]] = call i32 @__kmpc_omp_task(ptr @[[GLOB1]], i32 [[TMP1]], ptr [[TMP3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map.(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]], ptr noalias noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[TMP2]], ptr [[DOTADDR2:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP4]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTADDR2]], align 8
+// CHECK: store ptr [[TMP6]], ptr [[TMP7]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry.(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META26:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META29:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META31:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META33:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META35:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META35]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META35]]
+// CHECK: store ptr @.omp_task_privates_map., ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META35]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META35]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META35]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META35]]
+// CHECK: [[TMP10:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META35]]
+// CHECK: [[TMP11:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META35]]
+// CHECK: call void [[TMP10]](ptr [[TMP11]], ptr [[DOTFIRSTPRIV_PTR_ADDR_I:%.*]], ptr [[DOTFIRSTPRIV_PTR_ADDR1_I:%.*]]) #{{[0-9]+}}, !inline_history [[META36:![0-9]+]]
+// CHECK: [[TMP12:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR_I]], align 8, !noalias [[META35]]
+// CHECK: [[TMP13:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR1_I]], align 8, !noalias [[META35]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[TMP12]], align 4
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[TMP13]], align 4
+// CHECK: [[ADD_I:%.*]] = add nsw i32 [[TMP14]], [[TMP15]]
+// CHECK: store i32 [[ADD_I]], ptr [[SUM_I:%.*]], align 4, !noalias [[META35]]
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z22test_task_firstprivatev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP1:%.*]] = call i32 @__kmpc_global_thread_num(ptr @[[GLOB1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z22test_task_firstprivatev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_0:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP1]], i32 1, i64 48, i64 8, ptr @.omp_task_entry..11)
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_2:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load ptr, ptr [[TMP5]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP6]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_2]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_1:%.*]], ptr [[TMP7]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP9]], ptr [[TMP8]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_1]], ptr [[TMP7]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP11]], ptr [[TMP10]], align 4
+// CHECK: [[TMP12:%.*]] = call i32 @__kmpc_omp_task(ptr @[[GLOB1]], i32 [[TMP1]], ptr [[TMP3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map..10(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]], ptr noalias noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[TMP2]], ptr [[DOTADDR2:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_1:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP4]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_1]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTADDR2]], align 8
+// CHECK: store ptr [[TMP6]], ptr [[TMP7]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..11(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_2:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_2]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META37:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META40:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META42:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META44:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META46:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META46]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META46]]
+// CHECK: store ptr @.omp_task_privates_map..10, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META46]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META46]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META46]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META46]]
+// CHECK: [[TMP10:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META46]]
+// CHECK: [[TMP11:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META46]]
+// CHECK: call void [[TMP10]](ptr [[TMP11]], ptr [[DOTFIRSTPRIV_PTR_ADDR_I:%.*]], ptr [[DOTFIRSTPRIV_PTR_ADDR1_I:%.*]]) #{{[0-9]+}}, !inline_history [[META47:![0-9]+]]
+// CHECK: [[TMP12:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR_I]], align 8, !noalias [[META46]]
+// CHECK: [[TMP13:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR1_I]], align 8, !noalias [[META46]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[TMP12]], align 4
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[TMP13]], align 4
+// CHECK: [[ADD_I:%.*]] = add nsw i32 [[TMP14]], [[TMP15]]
+// CHECK: store i32 [[ADD_I]], ptr [[SUM_I:%.*]], align 4, !noalias [[META46]]
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z34test_task_firstprivate_with_sharedv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP1:%.*]] = call i32 @__kmpc_global_thread_num(ptr @[[GLOB1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z34test_task_firstprivate_with_sharedv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[RA:%.*]], align 4
+// CHECK: store i32 0, ptr [[RB:%.*]], align 4
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_3:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[RA]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_3]], ptr [[AGG_CAPTURED]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_3]], ptr [[AGG_CAPTURED]], i32 0, i32 2
+// CHECK: store ptr [[RB]], ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP1]], i32 1, i64 48, i64 24, ptr @.omp_task_entry..14)
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_5:%.*]], ptr [[TMP5]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP6]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = load ptr, ptr [[TMP7]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP8]], ptr align 8 [[AGG_CAPTURED]], i64 24, i1 false)
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_5]], ptr [[TMP5]], i32 0, i32 1
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_4:%.*]], ptr [[TMP9]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP11]], ptr [[TMP10]], align 8
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_4]], ptr [[TMP9]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP13]], ptr [[TMP12]], align 4
+// CHECK: [[TMP14:%.*]] = call i32 @__kmpc_omp_task(ptr @[[GLOB1]], i32 [[TMP1]], ptr [[TMP5]])
+// CHECK: [[TMP15:%.*]] = call i32 @__kmpc_omp_taskwait(ptr @[[GLOB1]], i32 [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map..13(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]], ptr noalias noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[TMP2]], ptr [[DOTADDR2:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_4:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP4]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_4]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTADDR2]], align 8
+// CHECK: store ptr [[TMP6]], ptr [[TMP7]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..14(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_5:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_5]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META48:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META51:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META53:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META55:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META57:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META57]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META57]]
+// CHECK: store ptr @.omp_task_privates_map..13, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META57]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META57]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META57]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META57]]
+// CHECK: [[TMP10:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META57]]
+// CHECK: [[TMP11:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META57]]
+// CHECK: call void [[TMP10]](ptr [[TMP11]], ptr [[DOTFIRSTPRIV_PTR_ADDR_I:%.*]], ptr [[DOTFIRSTPRIV_PTR_ADDR1_I:%.*]]) #{{[0-9]+}}, !inline_history [[META58:![0-9]+]]
+// CHECK: [[TMP12:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR_I]], align 8, !noalias [[META57]]
+// CHECK: [[TMP13:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR1_I]], align 8, !noalias [[META57]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[TMP12]], align 4
+// CHECK: [[TMP15:%.*]] = load ptr, ptr [[TMP9]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store i32 [[TMP14]], ptr [[TMP15]], align 4
+// CHECK: [[TMP16:%.*]] = load i32, ptr [[TMP13]], align 4
+// CHECK: [[TMP17:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_3:%.*]], ptr [[TMP9]], i32 0, i32 2
+// CHECK: [[TMP18:%.*]] = load ptr, ptr [[TMP17]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store i32 [[TMP16]], ptr [[TMP18]], align 4
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z16test_task_dependv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP1:%.*]] = call i32 @__kmpc_global_thread_num(ptr @[[GLOB1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z16test_task_dependv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_6:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP1]], i32 1, i64 48, i64 8, ptr @.omp_task_entry..17)
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_8:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load ptr, ptr [[TMP5]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP6]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_8]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_7:%.*]], ptr [[TMP7]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP9]], ptr [[TMP8]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_7]], ptr [[TMP7]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP11]], ptr [[TMP10]], align 4
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds [2 x [[STRUCT_KMP_DEPEND_INFO:%.*]]], ptr [[DOTDEP_ARR_ADDR:%.*]], i64 0, i64 0
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP13:%.*]] = ptrtoint ptr [[X1]] to i64
+// CHECK: [[TMP14:%.*]] = getelementptr [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP12]], i64 0
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP14]], i32 0, i32 0
+// CHECK: store i64 [[TMP13]], ptr [[TMP15]], align 8
+// CHECK: [[TMP16:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP14]], i32 0, i32 1
+// CHECK: store i64 4, ptr [[TMP16]], align 8
+// CHECK: [[TMP17:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP14]], i32 0, i32 2
+// CHECK: store i8 1, ptr [[TMP17]], align 8
+// CHECK: [[Y2:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP18:%.*]] = ptrtoint ptr [[Y2]] to i64
+// CHECK: [[TMP19:%.*]] = getelementptr [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP12]], i64 1
+// CHECK: [[TMP20:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP19]], i32 0, i32 0
+// CHECK: store i64 [[TMP18]], ptr [[TMP20]], align 8
+// CHECK: [[TMP21:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP19]], i32 0, i32 1
+// CHECK: store i64 4, ptr [[TMP21]], align 8
+// CHECK: [[TMP22:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_DEPEND_INFO]], ptr [[TMP19]], i32 0, i32 2
+// CHECK: store i8 1, ptr [[TMP22]], align 8
+// CHECK: store i64 2, ptr [[DEP_COUNTER_ADDR:%.*]], align 8
+// CHECK: [[TMP23:%.*]] = call i32 @__kmpc_omp_task_with_deps(ptr @[[GLOB1]], i32 [[TMP1]], ptr [[TMP3]], i32 2, ptr [[TMP12]], i32 0, ptr null)
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map..16(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]], ptr noalias noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[TMP2]], ptr [[DOTADDR2:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_7:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP4]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_7]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTADDR2]], align 8
+// CHECK: store ptr [[TMP6]], ptr [[TMP7]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..17(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_8:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_8]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META59:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META62:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META64:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META66:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META68:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META68]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META68]]
+// CHECK: store ptr @.omp_task_privates_map..16, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META68]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META68]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META68]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META68]]
+// CHECK: [[TMP10:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META68]]
+// CHECK: [[TMP11:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META68]]
+// CHECK: call void [[TMP10]](ptr [[TMP11]], ptr [[DOTFIRSTPRIV_PTR_ADDR_I:%.*]], ptr [[DOTFIRSTPRIV_PTR_ADDR1_I:%.*]]) #{{[0-9]+}}, !inline_history [[META69:![0-9]+]]
+// CHECK: [[TMP12:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR_I]], align 8, !noalias [[META68]]
+// CHECK: [[TMP13:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR1_I]], align 8, !noalias [[META68]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[TMP12]], align 4
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[TMP13]], align 4
+// CHECK: [[ADD_I:%.*]] = add nsw i32 [[TMP14]], [[TMP15]]
+// CHECK: store i32 [[ADD_I]], ptr [[SUM_I:%.*]], align 4, !noalias [[META68]]
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z14test_taskloop_v(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP1:%.*]] = call i32 @__kmpc_global_thread_num(ptr @[[GLOB1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z14test_taskloop_v.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_9:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: call void @__kmpc_taskgroup(ptr @[[GLOB1]], i32 [[TMP1]])
+// CHECK: [[TMP3:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP1]], i32 1, i64 88, i64 8, ptr @.omp_task_entry..20)
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_11:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12:%.*]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load ptr, ptr [[TMP5]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP6]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_11]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_10:%.*]], ptr [[TMP7]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP9]], ptr [[TMP8]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_10]], ptr [[TMP7]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP11]], ptr [[TMP10]], align 4
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 5
+// CHECK: store i64 0, ptr [[TMP12]], align 8
+// CHECK: [[TMP13:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 6
+// CHECK: store i64 9, ptr [[TMP13]], align 8
+// CHECK: [[TMP14:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 7
+// CHECK: store i64 1, ptr [[TMP14]], align 8
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 9
+// CHECK: call void @llvm.memset.p0.i64(ptr align 8 [[TMP15]], i8 0, i64 8, i1 false)
+// CHECK: [[TMP16:%.*]] = load i64, ptr [[TMP14]], align 8
+// CHECK: call void @__kmpc_taskloop(ptr @[[GLOB1]], i32 [[TMP1]], ptr [[TMP3]], i32 1, ptr [[TMP12]], ptr [[TMP13]], i64 [[TMP16]], i32 1, i32 0, i64 0, ptr null)
+// CHECK: call void @__kmpc_end_taskgroup(ptr @[[GLOB1]], i32 [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map..19(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]], ptr noalias noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[TMP2]], ptr [[DOTADDR2:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_10:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP4]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_10]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTADDR2]], align 8
+// CHECK: store ptr [[TMP6]], ptr [[TMP7]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..20(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_11:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_11]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 5
+// CHECK: [[TMP10:%.*]] = load i64, ptr [[TMP9]], align 8
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 6
+// CHECK: [[TMP12:%.*]] = load i64, ptr [[TMP11]], align 8
+// CHECK: [[TMP13:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 7
+// CHECK: [[TMP14:%.*]] = load i64, ptr [[TMP13]], align 8
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 8
+// CHECK: [[TMP16:%.*]] = load i32, ptr [[TMP15]], align 8
+// CHECK: [[TMP17:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 9
+// CHECK: [[TMP18:%.*]] = load ptr, ptr [[TMP17]], align 8
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META70:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META73:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META75:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META77:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META79:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META81:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store ptr @.omp_task_privates_map..19, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store i64 [[TMP10]], ptr [[DOTLB__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store i64 [[TMP12]], ptr [[DOTUB__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store i64 [[TMP14]], ptr [[DOTST__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store i32 [[TMP16]], ptr [[DOTLITER__ADDR_I:%.*]], align 4, !noalias [[META81]]
+// CHECK: store ptr [[TMP18]], ptr [[DOTREDUCTIONS__ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META81]]
+// CHECK: [[TMP19:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META81]]
+// CHECK: [[TMP20:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META81]]
+// CHECK: [[TMP21:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META81]]
+// CHECK: call void [[TMP20]](ptr [[TMP21]], ptr [[DOTFIRSTPRIV_PTR_ADDR_I:%.*]], ptr [[DOTFIRSTPRIV_PTR_ADDR1_I:%.*]]) #{{[0-9]+}}, !inline_history [[META82:![0-9]+]]
+// CHECK: [[TMP22:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR_I]], align 8, !noalias [[META81]]
+// CHECK: [[TMP23:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR1_I]], align 8, !noalias [[META81]]
+// CHECK: [[TMP24:%.*]] = load i64, ptr [[DOTLB__ADDR_I]], align 8, !noalias [[META81]]
+// CHECK: [[CONV_I:%.*]] = trunc i64 [[TMP24]] to i32
+// CHECK: store i32 [[CONV_I]], ptr [[DOTOMP_IV_I:%.*]], align 4, !noalias [[META81]]
+// CHECK: [[TMP25:%.*]] = load ptr, ptr [[TMP19]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: br label %[[OMP_INNER_FOR_COND_I:.*]]
+// CHECK: [[OMP_INNER_FOR_COND_I]]:
+// CHECK: [[TMP26:%.*]] = load i32, ptr [[DOTOMP_IV_I]], align 4, !noalias [[META81]]
+// CHECK: [[CONV2_I:%.*]] = sext i32 [[TMP26]] to i64
+// CHECK: [[TMP27:%.*]] = load i64, ptr [[DOTUB__ADDR_I]], align 8, !noalias [[META81]]
+// CHECK: [[CMP_I:%.*]] = icmp ule i64 [[CONV2_I]], [[TMP27]]
+// CHECK: br i1 [[CMP_I]], label %[[OMP_INNER_FOR_BODY_I:.*]], label %[[DOTOMP_OUTLINED__18_EXIT:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY_I]]:
+// CHECK: [[TMP28:%.*]] = load i32, ptr [[DOTOMP_IV_I]], align 4, !noalias [[META81]]
+// CHECK: store i32 [[TMP28]], ptr [[I_I:%.*]], align 4, !noalias [[META81]]
+// CHECK: [[TMP29:%.*]] = load i32, ptr [[TMP22]], align 4
+// CHECK: [[TMP30:%.*]] = load i32, ptr [[TMP23]], align 4
+// CHECK: [[ADD3_I:%.*]] = add nsw i32 [[TMP29]], [[TMP30]]
+// CHECK: [[TMP31:%.*]] = load i32, ptr [[I_I]], align 4, !noalias [[META81]]
+// CHECK: [[ADD4_I:%.*]] = add nsw i32 [[ADD3_I]], [[TMP31]]
+// CHECK: store i32 [[ADD4_I]], ptr [[RESULT_I:%.*]], align 4, !noalias [[META81]]
+// CHECK: [[TMP32:%.*]] = load i32, ptr [[DOTOMP_IV_I]], align 4, !noalias [[META81]]
+// CHECK: [[ADD5_I:%.*]] = add nsw i32 [[TMP32]], 1
+// CHECK: store i32 [[ADD5_I]], ptr [[DOTOMP_IV_I]], align 4, !noalias [[META81]]
+// CHECK: br label %[[OMP_INNER_FOR_COND_I]]
+// CHECK: [[DOTOMP_OUTLINED__18_EXIT]]:
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z21instantiate_templatesv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P2:%.*]], ptr align 4 @__const._Z21instantiate_templatesv.p2, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P3:%.*]], ptr align 4 @__const._Z21instantiate_templatesv.p3, i64 12, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[AGG_TMP:%.*]], ptr align 4 [[P2]], i64 8, i1 false)
+// CHECK: [[TMP0:%.*]] = load i64, ptr [[AGG_TMP]], align 4
+// CHECK: [[CALL:%.*]] = call noundef i32 @_Z17test_template_basI5PointEiT_(i64 [[TMP0]])
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[AGG_TMP1:%.*]], i32 0, i32 0
+// CHECK: store i32 3, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[AGG_TMP1]], i32 0, i32 1
+// CHECK: store i32 4, ptr [[Y]], align 4
+// CHECK: [[TMP1:%.*]] = load i64, ptr [[AGG_TMP1]], align 4
+// CHECK: [[CALL2:%.*]] = call noundef i32 @_Z17test_template_basI5PointEiT_(i64 [[TMP1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[AGG_TMP3:%.*]], ptr align 4 [[P2]], i64 8, i1 false)
+// CHECK: [[TMP2:%.*]] = load i64, ptr [[AGG_TMP3]], align 4
+// CHECK: [[CALL4:%.*]] = call noundef i32 @_Z20test_template_targetI5PointEiT_(i64 [[TMP2]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[AGG_TMP5:%.*]], ptr align 4 [[P2]], i64 8, i1 false)
+// CHECK: [[TMP3:%.*]] = load i64, ptr [[AGG_TMP5]], align 4
+// CHECK: [[CALL6:%.*]] = call noundef i32 @_Z18test_template_taskI5PointEiT_(i64 [[TMP3]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[AGG_TMP7:%.*]], ptr align 4 [[P3]], i64 12, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[AGG_TMP7_COERCE:%.*]], ptr align 4 [[AGG_TMP7]], i64 12, i1 false)
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw { i64, i32 }, ptr [[AGG_TMP7_COERCE]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load i64, ptr [[TMP4]], align 4
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw { i64, i32 }, ptr [[AGG_TMP7_COERCE]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[TMP6]], align 4
+// CHECK: [[CALL8:%.*]] = call noundef i32 @_Z24test_template_3_bindingsI7Point3DEiT_(i64 [[TMP5]], i32 [[TMP7]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z17test_template_basI5PointEiT_(
+// CHECK-SAME: i64 [[P_COERCE:%.*]]) #{{[0-9]+}} comdat {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i64 [[P_COERCE]], ptr [[P:%.*]], align 4
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[RESULT:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 2, ptr @_Z17test_template_basI5PointEiT_.omp_outlined, ptr [[RESULT]], ptr [[TMP0]])
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[RESULT]], align 4
+// CHECK: ret i32 [[TMP1]]
+//
+//
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z20test_template_targetI5PointEiT_(
+// CHECK-SAME: i64 [[P_COERCE:%.*]]) #{{[0-9]+}} comdat {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i64 [[P_COERCE]], ptr [[P:%.*]], align 4
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[RESULT:%.*]], align 4
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[RESULT]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[RESULT]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 2
+// CHECK: store ptr null, ptr [[TMP9]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z18test_template_taskI5PointEiT_(
+// CHECK-SAME: i64 [[P_COERCE:%.*]]) #{{[0-9]+}} comdat {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP1:%.*]] = call i32 @__kmpc_global_thread_num(ptr @[[GLOB1]])
+// CHECK: store i64 [[P_COERCE]], ptr [[P:%.*]], align 4
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[RESULT:%.*]], align 4
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_14:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[RESULT]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_14]], ptr [[AGG_CAPTURED]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP1]], i32 1, i64 48, i64 16, ptr @.omp_task_entry..25)
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_16:%.*]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP5]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP7]], ptr align 8 [[AGG_CAPTURED]], i64 16, i1 false)
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_16]], ptr [[TMP4]], i32 0, i32 1
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_15:%.*]], ptr [[TMP8]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP10]], ptr [[TMP9]], align 8
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_15]], ptr [[TMP8]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP12]], ptr [[TMP11]], align 4
+// CHECK: [[TMP13:%.*]] = call i32 @__kmpc_omp_task(ptr @[[GLOB1]], i32 [[TMP1]], ptr [[TMP4]])
+// CHECK: [[TMP14:%.*]] = call i32 @__kmpc_omp_taskwait(ptr @[[GLOB1]], i32 [[TMP1]])
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[RESULT]], align 4
+// CHECK: ret i32 [[TMP15]]
+//
+//
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z24test_template_3_bindingsI7Point3DEiT_(
+// CHECK-SAME: i64 [[P_COERCE0:%.*]], i32 [[P_COERCE1:%.*]]) #{{[0-9]+}} comdat {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds nuw { i64, i32 }, ptr [[COERCE:%.*]], i32 0, i32 0
+// CHECK: store i64 [[P_COERCE0]], ptr [[TMP1]], align 4
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw { i64, i32 }, ptr [[COERCE]], i32 0, i32 1
+// CHECK: store i32 [[P_COERCE1]], ptr [[TMP2]], align 4
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 [[COERCE]], i64 12, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 12, i1 false)
+// CHECK: store i32 0, ptr [[RESULT:%.*]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 2, ptr @_Z24test_template_3_bindingsI7Point3DEiT_.omp_outlined, ptr [[RESULT]], ptr [[TMP0]])
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[RESULT]], align 4
+// CHECK: ret i32 [[TMP3]]
+//
+//
+// CHECK-LABEL: define internal void @_Z17test_template_basI5PointEiT_.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[RESULT:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[RESULT]], ptr [[RESULT_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[RESULT_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[RESULT1:%.*]], align 4
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP3]], [[TMP4]]
+// CHECK: store i32 [[ADD]], ptr [[RESULT1]], align 4
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [1 x ptr], ptr [[DOTOMP_REDUCTION_RED_LIST:%.*]], i64 0, i64 0
+// CHECK: store ptr [[RESULT1]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[TMP6]], align 4
+// CHECK: [[TMP8:%.*]] = call i32 @__kmpc_reduce_nowait(ptr @[[GLOB3]], i32 [[TMP7]], i32 1, i64 8, ptr [[DOTOMP_REDUCTION_RED_LIST]], ptr @_Z17test_template_basI5PointEiT_.omp_outlined.omp.reduction.reduction_func, ptr @.gomp_critical_user_.reduction.var)
+// CHECK: switch i32 [[TMP8]], label %[[DOTOMP_REDUCTION_DEFAULT:.*]] [
+// CHECK: i32 1, label %[[DOTOMP_REDUCTION_CASE1:.*]]
+// CHECK: i32 2, label %[[DOTOMP_REDUCTION_CASE2:.*]]
+// CHECK: ]
+// CHECK: [[DOTOMP_REDUCTION_CASE1]]:
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[RESULT1]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP9]], [[TMP10]]
+// CHECK: store i32 [[ADD2]], ptr [[TMP1]], align 4
+// CHECK: call void @__kmpc_end_reduce_nowait(ptr @[[GLOB3]], i32 [[TMP7]], ptr @.gomp_critical_user_.reduction.var)
+// CHECK: br label %[[DOTOMP_REDUCTION_DEFAULT]]
+// CHECK: [[DOTOMP_REDUCTION_CASE2]]:
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[RESULT1]], align 4
+// CHECK: [[TMP12:%.*]] = atomicrmw add ptr [[TMP1]], i32 [[TMP11]] monotonic, align 4
+// CHECK: br label %[[DOTOMP_REDUCTION_DEFAULT]]
+// CHECK: [[DOTOMP_REDUCTION_DEFAULT]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z17test_template_basI5PointEiT_.omp_outlined.omp.reduction.reduction_func(
+// CHECK-SAME: ptr noundef [[TMP0:%.*]], ptr noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [1 x ptr], ptr [[TMP3]], i64 0, i64 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[TMP4]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [1 x ptr], ptr [[TMP2]], i64 0, i64 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[TMP7]], align 4
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[TMP5]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP8]], [[TMP9]]
+// CHECK: store i32 [[ADD]], ptr [[TMP7]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z20test_template_targetI5PointEiT__l188(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[RESULT:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[RESULT]], ptr [[RESULT_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[RESULT_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP3]], ptr [[A:%.*]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP4]], ptr [[B:%.*]], align 4
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP5]], [[TMP6]]
+// CHECK: store i32 [[ADD]], ptr [[TMP1]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map..24(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]], ptr noalias noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[TMP2]], ptr [[DOTADDR2:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_15:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP4]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_15]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTADDR2]], align 8
+// CHECK: store ptr [[TMP6]], ptr [[TMP7]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..25(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_16:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_16]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META83:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META86:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META88:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META90:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META92:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META92]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META92]]
+// CHECK: store ptr @.omp_task_privates_map..24, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META92]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META92]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META92]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META92]]
+// CHECK: [[TMP10:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META92]]
+// CHECK: [[TMP11:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META92]]
+// CHECK: call void [[TMP10]](ptr [[TMP11]], ptr [[DOTFIRSTPRIV_PTR_ADDR_I:%.*]], ptr [[DOTFIRSTPRIV_PTR_ADDR1_I:%.*]]) #{{[0-9]+}}, !inline_history [[META93:![0-9]+]]
+// CHECK: [[TMP12:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR_I]], align 8, !noalias [[META92]]
+// CHECK: [[TMP13:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR1_I]], align 8, !noalias [[META92]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[TMP12]], align 4
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[TMP13]], align 4
+// CHECK: [[ADD_I:%.*]] = add nsw i32 [[TMP14]], [[TMP15]]
+// CHECK: [[TMP16:%.*]] = load ptr, ptr [[TMP9]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store i32 [[ADD_I]], ptr [[TMP16]], align 4
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define internal void @_Z24test_template_3_bindingsI7Point3DEiT_.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[RESULT:%.*]], ptr noundef nonnull align 4 dereferenceable(12) [[TMP0:%.*]]) #{{[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 [[RESULT]], ptr [[RESULT_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[RESULT_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[RESULT1:%.*]], align 4
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT3D:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT3D]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP3]], [[TMP4]]
+// CHECK: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT3D]], ptr [[TMP2]], i32 0, i32 2
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[Z]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[ADD]], [[TMP5]]
+// CHECK: store i32 [[ADD2]], ptr [[RESULT1]], align 4
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [1 x ptr], ptr [[DOTOMP_REDUCTION_RED_LIST:%.*]], i64 0, i64 0
+// CHECK: store ptr [[RESULT1]], ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[TMP7]], align 4
+// CHECK: [[TMP9:%.*]] = call i32 @__kmpc_reduce_nowait(ptr @[[GLOB3]], i32 [[TMP8]], i32 1, i64 8, ptr [[DOTOMP_REDUCTION_RED_LIST]], ptr @_Z24test_template_3_bindingsI7Point3DEiT_.omp_outlined.omp.reduction.reduction_func, ptr @.gomp_critical_user_.reduction.var)
+// CHECK: switch i32 [[TMP9]], label %[[DOTOMP_REDUCTION_DEFAULT:.*]] [
+// CHECK: i32 1, label %[[DOTOMP_REDUCTION_CASE1:.*]]
+// CHECK: i32 2, label %[[DOTOMP_REDUCTION_CASE2:.*]]
+// CHECK: ]
+// CHECK: [[DOTOMP_REDUCTION_CASE1]]:
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[RESULT1]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[TMP10]], [[TMP11]]
+// CHECK: store i32 [[ADD3]], ptr [[TMP1]], align 4
+// CHECK: call void @__kmpc_end_reduce_nowait(ptr @[[GLOB3]], i32 [[TMP8]], ptr @.gomp_critical_user_.reduction.var)
+// CHECK: br label %[[DOTOMP_REDUCTION_DEFAULT]]
+// CHECK: [[DOTOMP_REDUCTION_CASE2]]:
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[RESULT1]], align 4
+// CHECK: [[TMP13:%.*]] = atomicrmw add ptr [[TMP1]], i32 [[TMP12]] monotonic, align 4
+// CHECK: br label %[[DOTOMP_REDUCTION_DEFAULT]]
+// CHECK: [[DOTOMP_REDUCTION_DEFAULT]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z24test_template_3_bindingsI7Point3DEiT_.omp_outlined.omp.reduction.reduction_func(
+// CHECK-SAME: ptr noundef [[TMP0:%.*]], ptr noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [1 x ptr], ptr [[TMP3]], i64 0, i64 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[TMP4]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [1 x ptr], ptr [[TMP2]], i64 0, i64 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[TMP7]], align 4
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[TMP5]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP8]], [[TMP9]]
+// CHECK: store i32 [[ADD]], ptr [[TMP7]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z19test_static_bindingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP0:%.*]] = load atomic i8, ptr @_ZGVZ19test_static_bindingvEDC1a1bE acquire, align 8
+// CHECK: [[GUARD_UNINITIALIZED:%.*]] = icmp eq i8 [[TMP0]], 0
+// CHECK: br i1 [[GUARD_UNINITIALIZED]], label %[[INIT_CHECK:.*]], label %[[INIT_END:.*]], !prof [[PROF94:![0-9]+]]
+// CHECK: [[INIT_CHECK]]:
+// CHECK: [[TMP1:%.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVZ19test_static_bindingvEDC1a1bE) #{{[0-9]+}}
+// CHECK: [[TOBOOL:%.*]] = icmp ne i32 [[TMP1]], 0
+// CHECK: br i1 [[TOBOOL]], label %[[INIT:.*]], label %[[INIT_END]]
+// CHECK: [[INIT]]:
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 @_ZZ19test_static_bindingvEDC1a1bE, ptr align 4 @_ZZ19test_static_bindingvE1p, i64 8, i1 false)
+// CHECK: call void @__cxa_guard_release(ptr @_ZGVZ19test_static_bindingvEDC1a1bE) #{{[0-9]+}}
+// CHECK: br label %[[INIT_END]]
+// CHECK: [[INIT_END]]:
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 0, ptr @_Z19test_static_bindingv.omp_outlined)
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z19test_static_bindingv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]]) #{{[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: [[TMP0:%.*]] = load i32, ptr @_ZZ19test_static_bindingvEDC1a1bE, align 4
+// CHECK: [[TMP1:%.*]] = load i32, ptr getelementptr inbounds nuw (i8, ptr @_ZZ19test_static_bindingvEDC1a1bE, i64 4), align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP0]], [[TMP1]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z26test_static_binding_sharedv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[TMP0:%.*]] = load atomic i8, ptr @_ZGVZ26test_static_binding_sharedvEDC1a1bE acquire, align 8
+// CHECK: [[GUARD_UNINITIALIZED:%.*]] = icmp eq i8 [[TMP0]], 0
+// CHECK: br i1 [[GUARD_UNINITIALIZED]], label %[[INIT_CHECK:.*]], label %[[INIT_END:.*]], !prof [[PROF94]]
+// CHECK: [[INIT_CHECK]]:
+// CHECK: [[TMP1:%.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVZ26test_static_binding_sharedvEDC1a1bE) #{{[0-9]+}}
+// CHECK: [[TOBOOL:%.*]] = icmp ne i32 [[TMP1]], 0
+// CHECK: br i1 [[TOBOOL]], label %[[INIT:.*]], label %[[INIT_END]]
+// CHECK: [[INIT]]:
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 @_ZZ26test_static_binding_sharedvEDC1a1bE, ptr align 4 @_ZZ26test_static_binding_sharedvE1p, i64 8, i1 false)
+// CHECK: call void @__cxa_guard_release(ptr @_ZGVZ26test_static_binding_sharedvEDC1a1bE) #{{[0-9]+}}
+// CHECK: br label %[[INIT_END]]
+// CHECK: [[INIT_END]]:
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 0, ptr @_Z26test_static_binding_sharedv.omp_outlined)
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z26test_static_binding_sharedv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]]) #{{[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: [[TMP0:%.*]] = load i32, ptr @_ZZ26test_static_binding_sharedvEDC1a1bE, align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP0]], 1
+// CHECK: store i32 [[ADD]], ptr @_ZZ26test_static_binding_sharedvEDC1a1bE, align 4
+// CHECK: [[TMP1:%.*]] = load i32, ptr getelementptr inbounds nuw (i8, ptr @_ZZ26test_static_binding_sharedvEDC1a1bE, i64 4), align 4
+// CHECK: [[ADD1:%.*]] = add nsw i32 [[TMP1]], 1
+// CHECK: store i32 [[ADD1]], ptr getelementptr inbounds nuw (i8, ptr @_ZZ26test_static_binding_sharedvEDC1a1bE, i64 4), align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z17test_array_targetv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*]]:
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[ARR:%.*]], ptr align 4 @__const._Z17test_array_targetv.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: [[TMP3:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [2 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z17test_array_targetv_l256(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i64 0, i64 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[A:%.*]], align 4
+// CHECK: [[ARRAYIDX1:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP1]], i64 0, i64 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
+// CHECK: store i32 [[TMP3]], ptr [[B:%.*]], align 4
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z15test_array_taskv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*]]:
+// CHECK: [[TMP1:%.*]] = call i32 @__kmpc_global_thread_num(ptr @[[GLOB1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[ARR:%.*]], ptr align 4 @__const._Z15test_array_taskv.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: [[TMP2:%.*]] = 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: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK: store i32 [[TMP3]], ptr [[TMP2]], 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: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_17:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP1]], i32 1, i64 48, i64 8, ptr @.omp_task_entry..30)
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_19:%.*]], ptr [[TMP5]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP6]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = load ptr, ptr [[TMP7]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP8]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_19]], ptr [[TMP5]], i32 0, i32 1
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_18:%.*]], ptr [[TMP9]], i32 0, i32 0
+// CHECK: [[ARRAYIDX1:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP0]], i64 0, i64 0
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
+// CHECK: store i32 [[TMP11]], ptr [[TMP10]], align 8
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_18]], ptr [[TMP9]], i32 0, i32 1
+// CHECK: [[ARRAYIDX2:%.*]] = getelementptr inbounds [2 x i32], ptr [[TMP0]], i64 0, i64 1
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4
+// CHECK: store i32 [[TMP13]], ptr [[TMP12]], align 4
+// CHECK: [[TMP14:%.*]] = call i32 @__kmpc_omp_task(ptr @[[GLOB1]], i32 [[TMP1]], ptr [[TMP5]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map..29(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]], ptr noalias noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[TMP2]], ptr [[DOTADDR2:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_18:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP4]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_18]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[DOTADDR2]], align 8
+// CHECK: store ptr [[TMP6]], ptr [[TMP7]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..30(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_19:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_19]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META95:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META98:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META100:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META102:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META104:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META104]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META104]]
+// CHECK: store ptr @.omp_task_privates_map..29, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META104]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META104]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META104]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META104]]
+// CHECK: [[TMP10:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META104]]
+// CHECK: [[TMP11:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META104]]
+// CHECK: call void [[TMP10]](ptr [[TMP11]], ptr [[DOTFIRSTPRIV_PTR_ADDR_I:%.*]], ptr [[DOTFIRSTPRIV_PTR_ADDR1_I:%.*]]) #{{[0-9]+}}, !inline_history [[META105:![0-9]+]]
+// CHECK: [[TMP12:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR_I]], align 8, !noalias [[META104]]
+// CHECK: [[TMP13:%.*]] = load ptr, ptr [[DOTFIRSTPRIV_PTR_ADDR1_I]], align 8, !noalias [[META104]]
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[TMP12]], align 4
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[TMP13]], align 4
+// CHECK: [[ADD_I:%.*]] = add nsw i32 [[TMP14]], [[TMP15]]
+// CHECK: store i32 [[ADD_I]], ptr [[SUM_I:%.*]], align 4, !noalias [[META104]]
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z11test_nestedv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z11test_nestedv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z11test_nestedv.omp_outlined, 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(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[TMP2]], align 4
+// CHECK: call void @__kmpc_critical(ptr @[[GLOB1]], i32 [[TMP3]], ptr @.gomp_critical_user_.var)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP5]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: call void @__kmpc_end_critical(ptr @[[GLOB1]], i32 [[TMP3]], ptr @.gomp_critical_user_.var)
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_20:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP1]], ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP3]], i32 1, i64 40, i64 8, ptr @.omp_task_entry..32)
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_21:%.*]], ptr [[TMP7]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP8]], i32 0, i32 0
+// CHECK: [[TMP10:%.*]] = load ptr, ptr [[TMP9]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP10]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP11:%.*]] = call i32 @__kmpc_omp_task(ptr @[[GLOB1]], i32 [[TMP3]], ptr [[TMP7]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..32(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_21:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META106:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META109:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META111:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META113:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META115:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META115]]
+// CHECK: store ptr null, ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META115]]
+// CHECK: store ptr null, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META115]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META115]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META115]]
+// CHECK: [[TMP8:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META115]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[TMP8]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[TMP9]], align 4
+// CHECK: [[TMP11:%.*]] = load ptr, ptr [[TMP8]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[Y_I:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP11]], i32 0, i32 1
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[Y_I]], align 4
+// CHECK: [[MUL_I:%.*]] = mul nsw i32 [[TMP10]], [[TMP12]]
+// CHECK: store i32 [[MUL_I]], ptr [[PRODUCT_I:%.*]], align 4, !noalias [[META115]]
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z22test_reference_bindingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z22test_reference_bindingv.p, i64 8, i1 false)
+// CHECK: store ptr [[P]], ptr [[TMP0:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[TMP0]], align 8, !nonnull [[META18]], !align [[META19]]
+// 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(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP2]], ptr [[OMP_BINDING_REF:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[OMP_BINDING_REF]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP5]], ptr [[OMP_BINDING_REF1:%.*]], align 8
+// CHECK: [[TMP6:%.*]] = load ptr, ptr [[OMP_BINDING_REF1]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP6]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP4]], [[TMP7]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z18test_const_bindingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z18test_const_bindingv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 @__const._Z18test_const_bindingv., i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z18test_const_bindingv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z18test_const_bindingv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// 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: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z22test_multiple_bindingsv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P1:%.*]], ptr align 4 @__const._Z22test_multiple_bindingsv.p1, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P2:%.*]], ptr align 4 @__const._Z22test_multiple_bindingsv.p2, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P1]], i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP1:%.*]], ptr align 4 [[P2]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 2, ptr @_Z22test_multiple_bindingsv.omp_outlined, ptr [[TMP0]], ptr [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z22test_multiple_bindingsv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP1:%.*]]) #{{[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: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8, !nonnull [[META18]], !align [[META19]]
+// 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: [[X2:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[X2]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD]], [[TMP6]]
+// CHECK: [[Y4:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[Y4]], align 4
+// CHECK: [[ADD5:%.*]] = add nsw i32 [[ADD3]], [[TMP7]]
+// CHECK: store i32 [[ADD5]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z32test_multiple_bindings_mixed_dsav(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P1:%.*]], ptr align 4 @__const._Z32test_multiple_bindings_mixed_dsav.p1, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P2:%.*]], ptr align 4 @__const._Z32test_multiple_bindings_mixed_dsav.p2, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P1]], i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP1:%.*]], ptr align 4 [[P2]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 3, ptr @_Z32test_multiple_bindings_mixed_dsav.omp_outlined, ptr [[TMP0]], ptr [[TMP1]], ptr [[P1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z32test_multiple_bindings_mixed_dsav.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP1:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[P1:%.*]]) #{{[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: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store ptr [[P1]], ptr [[P1_ADDR:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP4:%.*]] = load ptr, ptr [[P1_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P12:%.*]], ptr align 4 [[TMP4]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP5]], [[TMP6]]
+// CHECK: [[X3:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[X3]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[ADD]], [[TMP7]]
+// CHECK: [[Y5:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[Y5]], align 4
+// CHECK: [[ADD6:%.*]] = add nsw i32 [[ADD4]], [[TMP8]]
+// CHECK: store i32 [[ADD6]], ptr [[RESULT:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z21test_array_3_elementsv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*]]:
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[ARR:%.*]], ptr align 4 @__const._Z21test_array_3_elementsv.arr, i64 12, i1 false)
+// CHECK: [[ARRAYINIT_BEGIN:%.*]] = getelementptr inbounds [3 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 [3 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]], 3
+// 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 @_Z21test_array_3_elementsv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z21test_array_3_elementsv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(12) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds [3 x i32], ptr [[TMP1]], i64 0, i64 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK: [[ARRAYIDX1:%.*]] = getelementptr inbounds [3 x i32], ptr [[TMP1]], i64 0, i64 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], [[TMP3]]
+// CHECK: [[ARRAYIDX2:%.*]] = getelementptr inbounds [3 x i32], ptr [[TMP1]], i64 0, i64 2
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[ADD]], [[TMP4]]
+// CHECK: store i32 [[ADD3]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z11test_singlev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z11test_singlev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z11test_singlev.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z11test_singlev.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[TMP2]], align 4
+// CHECK: [[TMP4:%.*]] = call i32 @__kmpc_single(ptr @[[GLOB1]], i32 [[TMP3]])
+// CHECK: [[TMP5:%.*]] = icmp ne i32 [[TMP4]], 0
+// CHECK: br i1 [[TMP5]], label %[[OMP_IF_THEN:.*]], label %[[OMP_IF_END:.*]]
+// CHECK: [[OMP_IF_THEN]]:
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP6]], [[TMP7]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: call void @__kmpc_end_single(ptr @[[GLOB1]], i32 [[TMP3]])
+// CHECK: br label %[[OMP_IF_END]]
+// CHECK: [[OMP_IF_END]]:
+// CHECK: call void @__kmpc_barrier(ptr @[[GLOB5:[0-9]+]], i32 [[TMP3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z13test_sectionsv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z13test_sectionsv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z13test_sectionsv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z13test_sectionsv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_SECTIONS_LB_:%.*]], align 4
+// CHECK: store i32 1, ptr [[DOTOMP_SECTIONS_UB_:%.*]], align 4
+// CHECK: store i32 1, ptr [[DOTOMP_SECTIONS_ST_:%.*]], align 4
+// CHECK: store i32 0, ptr [[DOTOMP_SECTIONS_IL_:%.*]], 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 @[[GLOB6:[0-9]+]], i32 [[TMP3]], i32 34, ptr [[DOTOMP_SECTIONS_IL_]], ptr [[DOTOMP_SECTIONS_LB_]], ptr [[DOTOMP_SECTIONS_UB_]], ptr [[DOTOMP_SECTIONS_ST_]], i32 1, i32 1)
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[DOTOMP_SECTIONS_UB_]], align 4
+// CHECK: [[TMP5:%.*]] = icmp slt i32 [[TMP4]], 1
+// CHECK: [[TMP6:%.*]] = select i1 [[TMP5]], i32 [[TMP4]], i32 1
+// CHECK: store i32 [[TMP6]], ptr [[DOTOMP_SECTIONS_UB_]], align 4
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_SECTIONS_LB_]], align 4
+// CHECK: store i32 [[TMP7]], ptr [[DOTOMP_SECTIONS_IV_:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_SECTIONS_IV_]], align 4
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_SECTIONS_UB_]], align 4
+// CHECK: [[CMP:%.*]] = icmp sle i32 [[TMP8]], [[TMP9]]
+// CHECK: br i1 [[CMP]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_SECTIONS_IV_]], align 4
+// CHECK: switch i32 [[TMP10]], label %[[DOTOMP_SECTIONS_EXIT:.*]] [
+// CHECK: i32 0, label %[[DOTOMP_SECTIONS_CASE:.*]]
+// CHECK: i32 1, label %[[DOTOMP_SECTIONS_CASE1:.*]]
+// CHECK: ]
+// CHECK: [[DOTOMP_SECTIONS_CASE]]:
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP11]], [[TMP12]]
+// CHECK: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: br label %[[DOTOMP_SECTIONS_EXIT]]
+// CHECK: [[DOTOMP_SECTIONS_CASE1]]:
+// CHECK: [[X2:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[X2]], align 4
+// CHECK: [[Y3:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[Y3]], align 4
+// CHECK: [[SUB:%.*]] = sub nsw i32 [[TMP13]], [[TMP14]]
+// CHECK: store i32 [[SUB]], ptr [[DIFF:%.*]], align 4
+// CHECK: br label %[[DOTOMP_SECTIONS_EXIT]]
+// CHECK: [[DOTOMP_SECTIONS_EXIT]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[DOTOMP_SECTIONS_IV_]], align 4
+// CHECK: [[INC:%.*]] = add nsw i32 [[TMP15]], 1
+// CHECK: store i32 [[INC]], ptr [[DOTOMP_SECTIONS_IV_]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND]]
+// CHECK: [[OMP_INNER_FOR_END]]:
+// CHECK: call void @__kmpc_for_static_fini(ptr @[[GLOB6]], i32 [[TMP3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z20test_nested_parallelv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z20test_nested_parallelv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z20test_nested_parallelv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z20test_nested_parallelv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z20test_nested_parallelv.omp_outlined.omp_outlined, ptr [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z20test_nested_parallelv.omp_outlined.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// 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: store i32 [[ADD]], ptr [[SUM:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z10test_simd_v(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z10test_simd_v.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP116:![0-9]+]]
+// CHECK: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 10
+// CHECK: br i1 [[CMP]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP2]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[X]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[Y]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: [[ADD1:%.*]] = add nsw i32 [[TMP3]], [[TMP4]]
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[I]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[ADD1]], [[TMP5]]
+// CHECK: store i32 [[ADD2]], ptr [[RESULT:%.*]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[TMP6]], 1
+// CHECK: store i32 [[ADD3]], ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP116]]
+// CHECK: br label %[[OMP_INNER_FOR_COND]], !llvm.loop [[LOOP117:![0-9]+]]
+// CHECK: [[OMP_INNER_FOR_END]]:
+// CHECK: store i32 10, ptr [[I]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z23test_simd_write_bindingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z23test_simd_write_bindingv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP119:![0-9]+]]
+// CHECK: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 10
+// CHECK: br i1 [[CMP]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP119]]
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP2]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4, !llvm.access.group [[ACC_GRP119]]
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[I]], align 4, !llvm.access.group [[ACC_GRP119]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: store i32 [[TMP3]], ptr [[X]], align 4, !llvm.access.group [[ACC_GRP119]]
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP119]]
+// CHECK: [[ADD1:%.*]] = add nsw i32 [[TMP4]], 1
+// CHECK: store i32 [[ADD1]], ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP119]]
+// CHECK: br label %[[OMP_INNER_FOR_COND]], !llvm.loop [[LOOP120:![0-9]+]]
+// CHECK: [[OMP_INNER_FOR_END]]:
+// CHECK: store i32 10, ptr [[I]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z21test_simd_lastprivatev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z21test_simd_lastprivatev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: store ptr [[X]], ptr [[A:%.*]], align 8
+// CHECK: store i32 0, ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP122:![0-9]+]]
+// CHECK: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 10
+// CHECK: br i1 [[CMP]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP122]]
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP2]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4, !llvm.access.group [[ACC_GRP122]]
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[I]], align 4, !llvm.access.group [[ACC_GRP122]]
+// CHECK: store i32 [[TMP3]], ptr [[A2:%.*]], align 4, !llvm.access.group [[ACC_GRP122]]
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP122]]
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[TMP4]], 1
+// CHECK: store i32 [[ADD3]], ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP122]]
+// CHECK: br label %[[OMP_INNER_FOR_COND]], !llvm.loop [[LOOP123:![0-9]+]]
+// CHECK: [[OMP_INNER_FOR_END]]:
+// CHECK: store i32 10, ptr [[I]], align 4
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[A2]], align 4
+// CHECK: store i32 [[TMP5]], ptr [[X1]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z32test_private_individual_bindingsv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z32test_private_individual_bindingsv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 0, ptr @_Z32test_private_individual_bindingsv.omp_outlined)
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z32test_private_individual_bindingsv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]]) #{{[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 i32 2, ptr [[A:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z37test_firstprivate_individual_bindingsv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z37test_firstprivate_individual_bindingsv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z37test_firstprivate_individual_bindingsv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z37test_firstprivate_individual_bindingsv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[B:%.*]], align 4
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP3]], 10
+// CHECK: store i32 [[ADD]], ptr [[B]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z20test_static_bindingsv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 0, ptr @_Z20test_static_bindingsv.omp_outlined)
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z20test_static_bindingsv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]]) #{{[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: [[TMP0:%.*]] = load i32, ptr @_ZZ20test_static_bindingsvEDC1a1bE, align 4
+// CHECK: [[TMP1:%.*]] = load i32, ptr getelementptr inbounds nuw (i8, ptr @_ZZ20test_static_bindingsvEDC1a1bE, i64 4), align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP0]], [[TMP1]]
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z14test_shadowingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 @__const._Z14test_shadowingv., i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 0, ptr @_Z14test_shadowingv.omp_outlined)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z14test_shadowingv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]]) #{{[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: [[TMP1:%.*]] = load i32, ptr [[A:%.*]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP1]])
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 @"__const.<captured>.", i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z31test_simd_private_then_parallelv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z31test_simd_private_then_parallelv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP125:![0-9]+]]
+// CHECK: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 10
+// CHECK: br i1 [[CMP]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP125]]
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP2]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4, !llvm.access.group [[ACC_GRP125]]
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[I]], align 4, !llvm.access.group [[ACC_GRP125]]
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[A:%.*]], align 4, !llvm.access.group [[ACC_GRP125]]
+// CHECK: [[ADD1:%.*]] = add nsw i32 [[TMP4]], [[TMP3]]
+// CHECK: store i32 [[ADD1]], ptr [[A]], align 4, !llvm.access.group [[ACC_GRP125]]
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP125]]
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP5]], 1
+// CHECK: store i32 [[ADD2]], ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP125]]
+// CHECK: br label %[[OMP_INNER_FOR_COND]], !llvm.loop [[LOOP126:![0-9]+]]
+// CHECK: [[OMP_INNER_FOR_END]]:
+// CHECK: store i32 10, ptr [[I]], align 4
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z31test_simd_private_then_parallelv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z31test_simd_private_then_parallelv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z19test_linear_bindingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z19test_linear_bindingv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: store i32 0, ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP1]], ptr [[DOTLINEAR_START:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP128:![0-9]+]]
+// CHECK: [[CMP:%.*]] = icmp slt i32 [[TMP2]], 10
+// CHECK: br i1 [[CMP]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP3]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[DOTLINEAR_START]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[MUL1:%.*]] = mul nsw i32 [[TMP5]], 1
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP4]], [[MUL1]]
+// CHECK: store i32 [[ADD2]], ptr [[A:%.*]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[A]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[TMP6]], 1
+// CHECK: store i32 [[ADD3]], ptr [[A]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[A]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: call void @_Z3usei(i32 noundef [[TMP7]]), !llvm.access.group [[ACC_GRP128]]
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP8]], 1
+// CHECK: store i32 [[ADD4]], ptr [[DOTOMP_IV]], align 4, !llvm.access.group [[ACC_GRP128]]
+// CHECK: br label %[[OMP_INNER_FOR_COND]], !llvm.loop [[LOOP129:![0-9]+]]
+// CHECK: [[OMP_INNER_FOR_END]]:
+// CHECK: store i32 10, ptr [[I]], align 4
+// CHECK: [[X5:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[X6:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: store i32 [[TMP9]], ptr [[X6]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z24test_lastprivate_bindingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z24test_lastprivate_bindingv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: store ptr [[X]], ptr [[A:%.*]], align 8
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z24test_lastprivate_bindingv.omp_outlined, ptr [[TMP0]])
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[X1]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z24test_lastprivate_bindingv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// 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]], 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]], 9
+// 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 [ 9, %[[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: [[CMP1:%.*]] = icmp sle i32 [[TMP7]], [[TMP8]]
+// CHECK: br i1 [[CMP1]], 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: [[MUL:%.*]] = mul nsw i32 [[TMP9]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[I]], align 4
+// CHECK: [[MUL2:%.*]] = mul nsw i32 [[TMP10]], 10
+// CHECK: store i32 [[MUL2]], ptr [[A:%.*]], align 4
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[TMP11]], 1
+// CHECK: store i32 [[ADD3]], 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: [[TMP12:%.*]] = load i32, ptr [[DOTOMP_IS_LAST]], align 4
+// CHECK: [[TMP13:%.*]] = icmp ne i32 [[TMP12]], 0
+// CHECK: br i1 [[TMP13]], label %[[DOTOMP_LASTPRIVATE_THEN:.*]], label %[[DOTOMP_LASTPRIVATE_DONE:.*]]
+// CHECK: [[DOTOMP_LASTPRIVATE_THEN]]:
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: store i32 [[TMP14]], ptr [[X]], align 4
+// CHECK: br label %[[DOTOMP_LASTPRIVATE_DONE]]
+// CHECK: [[DOTOMP_LASTPRIVATE_DONE]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z27test_binding_name_collisionv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P1:%.*]], ptr align 4 @__const._Z27test_binding_name_collisionv.p1, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P1]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 0, ptr @_Z27test_binding_name_collisionv.omp_outlined)
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z27test_binding_name_collisionv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]]) #{{[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: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P2:%.*]], ptr align 4 @"__const.<captured>.p2", i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P2]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP1]], 1
+// CHECK: store i32 [[ADD]], ptr [[X]], align 4
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X1]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP2]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z31test_firstprivate_nontrivial_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[P:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[X]], i32 noundef 10)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[P]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[Y]], i32 noundef 20)
+// CHECK: call void @_ZN4PairC1ERKS_(ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[P]])
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z31test_firstprivate_nontrivial_sbv.omp_outlined, ptr [[TMP0]])
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[TMP0]]) #{{[0-9]+}}
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[P]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN14NonTrivialCopyC1Ei(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[THIS:%.*]], i32 noundef [[V:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store i32 [[V]], ptr [[V_ADDR:%.*]], align 4
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP0:%.*]] = load i32, ptr [[V_ADDR]], align 4
+// CHECK: call void @_ZN14NonTrivialCopyC2Ei(ptr noundef nonnull align 4 dereferenceable(8) [[THIS1]], i32 noundef [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN4PairC1ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(16) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: call void @_ZN4PairC2ERKS_(ptr noundef nonnull align 4 dereferenceable(16) [[THIS1]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z31test_firstprivate_nontrivial_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[X]])
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[A]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN14NonTrivialCopyC1ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[OTHER:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[OTHER]], ptr [[OTHER_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP0:%.*]] = load ptr, ptr [[OTHER_ADDR]], align 8
+// CHECK: call void @_ZN14NonTrivialCopyC2ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[THIS1]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN14NonTrivialCopyD1Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: call void @_ZN14NonTrivialCopyD2Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[THIS1]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN4PairD1Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: call void @_ZN4PairD2Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[THIS1]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN14NonTrivialCopyC2Ei(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[THIS:%.*]], i32 noundef [[V:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store i32 [[V]], ptr [[V_ADDR:%.*]], align 4
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP0:%.*]] = load i32, ptr [[V_ADDR]], align 4
+// CHECK: store i32 [[TMP0]], ptr [[VALUE]], align 4
+// CHECK: [[COPY_COUNT:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: store i32 0, ptr [[COPY_COUNT]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN4PairC2ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(16) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X2:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[X]], ptr noundef nonnull align 4 dereferenceable(8) [[X2]])
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[Y3:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[Y]], ptr noundef nonnull align 4 dereferenceable(8) [[Y3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN14NonTrivialCopyC2ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[OTHER:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[OTHER]], ptr [[OTHER_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP0:%.*]] = load ptr, ptr [[OTHER_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[VALUE2:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[VALUE2]], align 4
+// CHECK: store i32 [[TMP1]], ptr [[VALUE]], align 4
+// CHECK: [[COPY_COUNT:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[OTHER_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[COPY_COUNT3:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[COPY_COUNT3]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP3]], 1
+// CHECK: store i32 [[ADD]], ptr [[COPY_COUNT]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN14NonTrivialCopyD2Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN4PairD2Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[Y]]) #{{[0-9]+}}
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[X]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z32test_firstprivate_ref_binding_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[P:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[X]], i32 noundef 10)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[P]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[Y]], i32 noundef 20)
+// CHECK: store ptr [[P]], ptr [[TMP0:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[TMP0]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z32test_firstprivate_ref_binding_sbv.omp_outlined, ptr [[TMP1]])
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[P]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z32test_firstprivate_ref_binding_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP2]], ptr [[OMP_BINDING_REF:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[OMP_BINDING_REF]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[X]])
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[A]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z38test_firstprivate_const_ref_binding_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[P:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[X]], i32 noundef 10)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[P]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[Y]], i32 noundef 20)
+// CHECK: store ptr [[P]], ptr [[TMP0:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[TMP0]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z38test_firstprivate_const_ref_binding_sbv.omp_outlined, ptr [[TMP1]])
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[P]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z38test_firstprivate_const_ref_binding_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP2]], ptr [[OMP_BINDING_REF:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[OMP_BINDING_REF]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[X]])
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[A]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z38test_firstprivate_multiple_bindings_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[P:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[X]], i32 noundef 10)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[P]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[Y]], i32 noundef 20)
+// CHECK: call void @_ZN4PairC1ERKS_(ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[P]])
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z38test_firstprivate_multiple_bindings_sbv.omp_outlined, ptr [[TMP0]])
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[TMP0]]) #{{[0-9]+}}
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[P]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z38test_firstprivate_multiple_bindings_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[X]])
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[B:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[Y]])
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[A]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[VALUE]], align 4
+// CHECK: [[VALUE1:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[B]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[VALUE1]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], [[TMP3]]
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[B]]) #{{[0-9]+}}
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z36test_firstprivate_with_destructor_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR:%.*]], ptr [[P:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN8WithDtorC1Ei(ptr noundef nonnull align 8 dereferenceable(8) [[X]], i32 noundef 100)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR]], ptr [[P]], i32 0, i32 1
+// CHECK: call void @_ZN8WithDtorC1Ei(ptr noundef nonnull align 8 dereferenceable(8) [[Y]], i32 noundef 200)
+// CHECK: call void @_ZN12PairWithDtorC1ERKS_(ptr noundef nonnull align 8 dereferenceable(16) [[TMP0:%.*]], ptr noundef nonnull align 8 dereferenceable(16) [[P]])
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z36test_firstprivate_with_destructor_sbv.omp_outlined, ptr [[TMP0]])
+// CHECK: call void @_ZN12PairWithDtorD1Ev(ptr noundef nonnull align 8 dead_on_return(16) dereferenceable(16) [[TMP0]]) #{{[0-9]+}}
+// CHECK: call void @_ZN12PairWithDtorD1Ev(ptr noundef nonnull align 8 dead_on_return(16) dereferenceable(16) [[P]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN8WithDtorC1Ei(
+// CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(8) [[THIS:%.*]], i32 noundef [[V:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store i32 [[V]], ptr [[V_ADDR:%.*]], align 4
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP0:%.*]] = load i32, ptr [[V_ADDR]], align 4
+// CHECK: call void @_ZN8WithDtorC2Ei(ptr noundef nonnull align 8 dereferenceable(8) [[THIS1]], i32 noundef [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN12PairWithDtorC1ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(16) [[THIS:%.*]], ptr noundef nonnull align 8 dereferenceable(16) [[TMP0:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: call void @_ZN12PairWithDtorC2ERKS_(ptr noundef nonnull align 8 dereferenceable(16) [[THIS1]], ptr noundef nonnull align 8 dereferenceable(16) [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z36test_firstprivate_with_destructor_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 8 dereferenceable(16) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META131:![0-9]+]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: call void @_ZN8WithDtorC1ERKS_(ptr noundef nonnull align 8 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 8 dereferenceable(8) [[X]])
+// CHECK: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_WITHDTOR:%.*]], ptr [[A]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[PTR]], align 8
+// CHECK: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN8WithDtorC1ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(8) [[THIS:%.*]], ptr noundef nonnull align 8 dereferenceable(8) [[OTHER:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[OTHER]], ptr [[OTHER_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP0:%.*]] = load ptr, ptr [[OTHER_ADDR]], align 8
+// CHECK: call void @_ZN8WithDtorC2ERKS_(ptr noundef nonnull align 8 dereferenceable(8) [[THIS1]], ptr noundef nonnull align 8 dereferenceable(8) [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN8WithDtorD1Ev(
+// CHECK-SAME: ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: call void @_ZN8WithDtorD2Ev(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) [[THIS1]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN12PairWithDtorD1Ev(
+// CHECK-SAME: ptr noundef nonnull align 8 dead_on_return(16) dereferenceable(16) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: call void @_ZN12PairWithDtorD2Ev(ptr noundef nonnull align 8 dead_on_return(16) dereferenceable(16) [[THIS1]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN8WithDtorC2Ei(
+// CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(8) [[THIS:%.*]], i32 noundef [[V:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store i32 [[V]], ptr [[V_ADDR:%.*]], align 4
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_WITHDTOR:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[CALL:%.*]] = call noalias noundef nonnull ptr @_Znwm(i64 noundef 4) #{{[0-9]+}}
+// CHECK: [[TMP0:%.*]] = load i32, ptr [[V_ADDR]], align 4
+// CHECK: store i32 [[TMP0]], ptr [[CALL]], align 4
+// CHECK: store ptr [[CALL]], ptr [[PTR]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN12PairWithDtorC2ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(16) [[THIS:%.*]], ptr noundef nonnull align 8 dereferenceable(16) [[TMP0:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META131]]
+// CHECK: [[X2:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: call void @_ZN8WithDtorC1ERKS_(ptr noundef nonnull align 8 dereferenceable(8) [[X]], ptr noundef nonnull align 8 dereferenceable(8) [[X2]])
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META131]]
+// CHECK: [[Y3:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: call void @_ZN8WithDtorC1ERKS_(ptr noundef nonnull align 8 dereferenceable(8) [[Y]], ptr noundef nonnull align 8 dereferenceable(8) [[Y3]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN8WithDtorC2ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(8) [[THIS:%.*]], ptr noundef nonnull align 8 dereferenceable(8) [[OTHER:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[OTHER]], ptr [[OTHER_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_WITHDTOR:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[CALL:%.*]] = call noalias noundef nonnull ptr @_Znwm(i64 noundef 4) #{{[0-9]+}}
+// CHECK: [[TMP0:%.*]] = load ptr, ptr [[OTHER_ADDR]], align 8, !nonnull [[META18]], !align [[META131]]
+// CHECK: [[PTR2:%.*]] = getelementptr inbounds nuw [[STRUCT_WITHDTOR]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[PTR2]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[TMP1]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[CALL]], align 4
+// CHECK: store ptr [[CALL]], ptr [[PTR]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN8WithDtorD2Ev(
+// CHECK-SAME: ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_WITHDTOR:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP0:%.*]] = load ptr, ptr [[PTR]], align 8
+// CHECK: [[ISNULL:%.*]] = icmp eq ptr [[TMP0]], null
+// CHECK: br i1 [[ISNULL]], label %[[DELETE_END:.*]], label %[[DELETE_NOTNULL:.*]]
+// CHECK: [[DELETE_NOTNULL]]:
+// CHECK: call void @_ZdlPvm(ptr noundef [[TMP0]], i64 noundef 4) #{{[0-9]+}}
+// CHECK: br label %[[DELETE_END]]
+// CHECK: [[DELETE_END]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN12PairWithDtorD2Ev(
+// CHECK-SAME: ptr noundef nonnull align 8 dead_on_return(16) dereferenceable(16) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR:%.*]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) [[Y]]) #{{[0-9]+}}
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIRWITHDTOR]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) [[X]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z35test_firstprivate_array_bindings_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*]]:
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[ARR:%.*]], i32 noundef 1)
+// CHECK: [[ARRAYINIT_ELEMENT:%.*]] = getelementptr inbounds [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[ARR]], i64 1
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[ARRAYINIT_ELEMENT]], i32 noundef 2)
+// CHECK: [[ARRAYINIT_ELEMENT1:%.*]] = getelementptr inbounds [[STRUCT_NONTRIVIALCOPY]], ptr [[ARR]], i64 2
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[ARRAYINIT_ELEMENT1]], i32 noundef 3)
+// CHECK: [[ARRAYINIT_BEGIN:%.*]] = getelementptr inbounds [3 x [[STRUCT_NONTRIVIALCOPY]]], 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 [[STRUCT_NONTRIVIALCOPY]], ptr [[ARRAYINIT_BEGIN]], i64 [[ARRAYINIT_INDEX]]
+// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw [3 x [[STRUCT_NONTRIVIALCOPY]]], ptr [[ARR]], i64 0, i64 [[ARRAYINIT_INDEX]]
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[TMP1]], ptr noundef nonnull align 4 dereferenceable(8) [[ARRAYIDX]])
+// CHECK: [[ARRAYINIT_NEXT]] = add nuw i64 [[ARRAYINIT_INDEX]], 1
+// CHECK: [[ARRAYINIT_DONE:%.*]] = icmp eq i64 [[ARRAYINIT_NEXT]], 3
+// 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 @_Z35test_firstprivate_array_bindings_sbv.omp_outlined, ptr [[TMP0]])
+// CHECK: [[ARRAY_BEGIN:%.*]] = getelementptr inbounds [3 x [[STRUCT_NONTRIVIALCOPY]]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [[STRUCT_NONTRIVIALCOPY]], ptr [[ARRAY_BEGIN]], i64 3
+// CHECK: br label %[[ARRAYDESTROY_BODY:.*]]
+// CHECK: [[ARRAYDESTROY_BODY]]:
+// CHECK: [[ARRAYDESTROY_ELEMENTPAST:%.*]] = phi ptr [ [[TMP2]], %[[ARRAYINIT_END]] ], [ [[ARRAYDESTROY_ELEMENT:%.*]], %[[ARRAYDESTROY_BODY]] ]
+// CHECK: [[ARRAYDESTROY_ELEMENT]] = getelementptr inbounds [[STRUCT_NONTRIVIALCOPY]], ptr [[ARRAYDESTROY_ELEMENTPAST]], i64 -1
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[ARRAYDESTROY_ELEMENT]]) #{{[0-9]+}}
+// CHECK: [[ARRAYDESTROY_DONE:%.*]] = icmp eq ptr [[ARRAYDESTROY_ELEMENT]], [[ARRAY_BEGIN]]
+// CHECK: br i1 [[ARRAYDESTROY_DONE]], label %[[ARRAYDESTROY_DONE2:.*]], label %[[ARRAYDESTROY_BODY]]
+// CHECK: [[ARRAYDESTROY_DONE2]]:
+// CHECK: [[ARRAY_BEGIN3:%.*]] = getelementptr inbounds [3 x [[STRUCT_NONTRIVIALCOPY]]], ptr [[ARR]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [[STRUCT_NONTRIVIALCOPY]], ptr [[ARRAY_BEGIN3]], i64 3
+// CHECK: br label %[[ARRAYDESTROY_BODY4:.*]]
+// CHECK: [[ARRAYDESTROY_BODY4]]:
+// CHECK: [[ARRAYDESTROY_ELEMENTPAST5:%.*]] = phi ptr [ [[TMP3]], %[[ARRAYDESTROY_DONE2]] ], [ [[ARRAYDESTROY_ELEMENT6:%.*]], %[[ARRAYDESTROY_BODY4]] ]
+// CHECK: [[ARRAYDESTROY_ELEMENT6]] = getelementptr inbounds [[STRUCT_NONTRIVIALCOPY]], ptr [[ARRAYDESTROY_ELEMENTPAST5]], i64 -1
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[ARRAYDESTROY_ELEMENT6]]) #{{[0-9]+}}
+// CHECK: [[ARRAYDESTROY_DONE7:%.*]] = icmp eq ptr [[ARRAYDESTROY_ELEMENT6]], [[ARRAY_BEGIN3]]
+// CHECK: br i1 [[ARRAYDESTROY_DONE7]], label %[[ARRAYDESTROY_DONE8:.*]], label %[[ARRAYDESTROY_BODY4]]
+// CHECK: [[ARRAYDESTROY_DONE8]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z35test_firstprivate_array_bindings_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(24) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds [3 x [[STRUCT_NONTRIVIALCOPY:%.*]]], ptr [[TMP1]], i64 0, i64 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[ARRAYIDX]])
+// CHECK: [[ARRAYIDX1:%.*]] = getelementptr inbounds [3 x [[STRUCT_NONTRIVIALCOPY]]], ptr [[TMP1]], i64 0, i64 1
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[B:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[ARRAYIDX1]])
+// CHECK: [[ARRAYIDX2:%.*]] = getelementptr inbounds [3 x [[STRUCT_NONTRIVIALCOPY]]], ptr [[TMP1]], i64 0, i64 2
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[C:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[ARRAYIDX2]])
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[A]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[VALUE]], align 4
+// CHECK: [[VALUE3:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[B]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[VALUE3]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], [[TMP3]]
+// CHECK: [[VALUE4:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[C]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[VALUE4]], align 4
+// CHECK: [[ADD5:%.*]] = add nsw i32 [[ADD]], [[TMP4]]
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[C]]) #{{[0-9]+}}
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[B]]) #{{[0-9]+}}
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z34test_firstprivate_nested_struct_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[I1:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER:%.*]], ptr [[O:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN5InnerC1Ei(ptr noundef nonnull align 4 dereferenceable(4) [[I1]], i32 noundef 5)
+// CHECK: [[I2:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER]], ptr [[O]], i32 0, i32 1
+// CHECK: call void @_ZN5InnerC1Ei(ptr noundef nonnull align 4 dereferenceable(4) [[I2]], i32 noundef 10)
+// CHECK: call void @_ZN5OuterC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[O]])
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z34test_firstprivate_nested_struct_sbv.omp_outlined, ptr [[TMP0]])
+// CHECK: call void @_ZN5OuterD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[TMP0]]) #{{[0-9]+}}
+// CHECK: call void @_ZN5OuterD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[O]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5InnerC1Ei(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]], i32 noundef [[V:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store i32 [[V]], ptr [[V_ADDR:%.*]], align 4
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP0:%.*]] = load i32, ptr [[V_ADDR]], align 4
+// CHECK: call void @_ZN5InnerC2Ei(ptr noundef nonnull align 4 dereferenceable(4) [[THIS1]], i32 noundef [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5OuterC1ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: call void @_ZN5OuterC2ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[THIS1]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP1]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z34test_firstprivate_nested_struct_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[I1:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: call void @_ZN5InnerC1ERKS_(ptr noundef nonnull align 4 dereferenceable(4) [[X:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[I1]])
+// CHECK: [[VAL:%.*]] = getelementptr inbounds nuw [[STRUCT_INNER:%.*]], ptr [[X]], i32 0, i32 0
+// CHECK: call void @_ZN5InnerD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) [[X]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5InnerC1ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[O:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[O]], ptr [[O_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[TMP0:%.*]] = load ptr, ptr [[O_ADDR]], align 8
+// CHECK: call void @_ZN5InnerC2ERKS_(ptr noundef nonnull align 4 dereferenceable(4) [[THIS1]], ptr noundef nonnull align 4 dereferenceable(4) [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5InnerD1Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: call void @_ZN5InnerD2Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) [[THIS1]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5OuterD1Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: call void @_ZN5OuterD2Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[THIS1]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5InnerC2Ei(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]], i32 noundef [[V:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store i32 [[V]], ptr [[V_ADDR:%.*]], align 4
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[VAL:%.*]] = getelementptr inbounds nuw [[STRUCT_INNER:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP0:%.*]] = load i32, ptr [[V_ADDR]], align 4
+// CHECK: store i32 [[TMP0]], ptr [[VAL]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5OuterC2ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[I1:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[I12:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: call void @_ZN5InnerC1ERKS_(ptr noundef nonnull align 4 dereferenceable(4) [[I1]], ptr noundef nonnull align 4 dereferenceable(4) [[I12]])
+// CHECK: [[I2:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[I23:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER]], ptr [[TMP2]], i32 0, i32 1
+// CHECK: call void @_ZN5InnerC1ERKS_(ptr noundef nonnull align 4 dereferenceable(4) [[I2]], ptr noundef nonnull align 4 dereferenceable(4) [[I23]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5InnerC2ERKS_(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(4) [[THIS:%.*]], ptr noundef nonnull align 4 dereferenceable(4) [[O:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: store ptr [[O]], ptr [[O_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[VAL:%.*]] = getelementptr inbounds nuw [[STRUCT_INNER:%.*]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: [[TMP0:%.*]] = load ptr, ptr [[O_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[VAL2:%.*]] = getelementptr inbounds nuw [[STRUCT_INNER]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = load i32, ptr [[VAL2]], align 4
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP1]], 2
+// CHECK: store i32 [[MUL]], ptr [[VAL]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5InnerD2Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define linkonce_odr void @_ZN5OuterD2Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[THIS:%.*]]) unnamed_addr #{{[0-9]+}} comdat align 2 {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[THIS]], ptr [[THIS_ADDR:%.*]], align 8
+// CHECK: [[THIS1:%.*]] = load ptr, ptr [[THIS_ADDR]], align 8
+// CHECK: [[I2:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER:%.*]], ptr [[THIS1]], i32 0, i32 1
+// CHECK: call void @_ZN5InnerD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) [[I2]]) #{{[0-9]+}}
+// CHECK: [[I1:%.*]] = getelementptr inbounds nuw [[STRUCT_OUTER]], ptr [[THIS1]], i32 0, i32 0
+// CHECK: call void @_ZN5InnerD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) [[I1]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z46test_firstprivate_ref_binding_both_bindings_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[P:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[X]], i32 noundef 10)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[P]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[Y]], i32 noundef 20)
+// CHECK: store ptr [[P]], ptr [[TMP0:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[TMP0]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z46test_firstprivate_ref_binding_both_bindings_sbv.omp_outlined, ptr [[TMP1]])
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[P]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z46test_firstprivate_ref_binding_both_bindings_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP2]], ptr [[OMP_BINDING_REF:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[OMP_BINDING_REF]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[X]])
+// CHECK: [[TMP4:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP4]], ptr [[OMP_BINDING_REF1:%.*]], align 8
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[OMP_BINDING_REF1]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP5]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[B:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[Y]])
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[A]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[VALUE]], align 4
+// CHECK: [[VALUE2:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[B]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[VALUE2]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP6]], [[TMP7]]
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[B]]) #{{[0-9]+}}
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z52test_firstprivate_const_ref_binding_both_bindings_sbv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[P:%.*]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[X]], i32 noundef 10)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[P]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1Ei(ptr noundef nonnull align 4 dereferenceable(8) [[Y]], i32 noundef 20)
+// CHECK: store ptr [[P]], ptr [[TMP0:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[TMP0]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z52test_firstprivate_const_ref_binding_both_bindings_sbv.omp_outlined, ptr [[TMP1]])
+// CHECK: call void @_ZN4PairD1Ev(ptr noundef nonnull align 4 dead_on_return(16) dereferenceable(16) [[P]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z52test_firstprivate_const_ref_binding_both_bindings_sbv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(16) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP1]], ptr [[TMP:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP2]], ptr [[OMP_BINDING_REF:%.*]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[OMP_BINDING_REF]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[A:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[X]])
+// CHECK: [[TMP4:%.*]] = load ptr, ptr [[TMP]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store ptr [[TMP4]], ptr [[OMP_BINDING_REF1:%.*]], align 8
+// CHECK: [[TMP5:%.*]] = load ptr, ptr [[OMP_BINDING_REF1]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_PAIR]], ptr [[TMP5]], i32 0, i32 1
+// CHECK: call void @_ZN14NonTrivialCopyC1ERKS_(ptr noundef nonnull align 4 dereferenceable(8) [[B:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[Y]])
+// CHECK: [[VALUE:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY:%.*]], ptr [[A]], i32 0, i32 0
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[VALUE]], align 4
+// CHECK: [[VALUE2:%.*]] = getelementptr inbounds nuw [[STRUCT_NONTRIVIALCOPY]], ptr [[B]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[VALUE2]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP6]], [[TMP7]]
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[B]]) #{{[0-9]+}}
+// CHECK: call void @_ZN14NonTrivialCopyD1Ev(ptr noundef nonnull align 4 dead_on_return(8) dereferenceable(8) [[A]]) #{{[0-9]+}}
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z36test_lambda_capture_binding_by_valuev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z36test_lambda_capture_binding_by_valuev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds nuw [[CLASS_ANON:%.*]], ptr [[LAMBDA:%.*]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[TMP1]], align 4
+// CHECK: [[CALL:%.*]] = call noundef i32 @"_ZZ36test_lambda_capture_binding_by_valuevENK3$_0clEv"(ptr noundef nonnull align 4 dereferenceable(4) [[LAMBDA]])
+// CHECK: store i32 [[CALL]], ptr [[RESULT:%.*]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z34test_lambda_capture_binding_by_refv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z34test_lambda_capture_binding_by_refv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds nuw [[CLASS_ANON_22:%.*]], ptr [[LAMBDA:%.*]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: store ptr [[X]], ptr [[TMP1]], align 8
+// CHECK: call void @"_ZZ34test_lambda_capture_binding_by_refvENK3$_0clEv"(ptr noundef nonnull align 8 dereferenceable(8) [[LAMBDA]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z37test_lambda_capture_multiple_bindingsv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z37test_lambda_capture_multiple_bindingsv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds nuw [[CLASS_ANON_23:%.*]], ptr [[LAMBDA:%.*]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[TMP1]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds nuw [[CLASS_ANON_23]], ptr [[LAMBDA]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: store ptr [[Y]], ptr [[TMP3]], align 8
+// CHECK: call void @"_ZZ37test_lambda_capture_multiple_bindingsvENK3$_0clEv"(ptr noundef nonnull align 8 dereferenceable(16) [[LAMBDA]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z28test_lambda_implicit_capturev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z28test_lambda_implicit_capturev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds nuw [[CLASS_ANON_24:%.*]], ptr [[LAMBDA:%.*]], i32 0, i32 0
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[TMP1]], align 4
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds nuw [[CLASS_ANON_24]], ptr [[LAMBDA]], i32 0, i32 1
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: store i32 [[TMP4]], ptr [[TMP3]], align 4
+// CHECK: [[CALL:%.*]] = call noundef i32 @"_ZZ28test_lambda_implicit_capturevENK3$_0clEv"(ptr noundef nonnull align 4 dereferenceable(8) [[LAMBDA]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z24test_parallel_for_linearv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z24test_parallel_for_linearv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z24test_parallel_for_linearv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z24test_parallel_for_linearv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[DOTLINEAR_START:%.*]], align 4
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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: [[TMP3:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[TMP3]], align 4
+// CHECK: call void @__kmpc_barrier(ptr @[[GLOB7:[0-9]+]], i32 [[TMP4]])
+// CHECK: call void @__kmpc_for_static_init_4(ptr @[[GLOB2]], i32 [[TMP4]], i32 34, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP:%.*]] = icmp sgt i32 [[TMP5]], 9
+// CHECK: br i1 [[CMP]], label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
+// CHECK: [[COND_TRUE]]:
+// CHECK: br label %[[COND_END:.*]]
+// CHECK: [[COND_FALSE]]:
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: br label %[[COND_END]]
+// CHECK: [[COND_END]]:
+// CHECK: [[COND:%.*]] = phi i32 [ 9, %[[COND_TRUE]] ], [ [[TMP6]], %[[COND_FALSE]] ]
+// CHECK: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
+// CHECK: store i32 [[TMP7]], ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP1:%.*]] = icmp sle i32 [[TMP8]], [[TMP9]]
+// CHECK: br i1 [[CMP1]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP10]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[DOTLINEAR_START]], align 4
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[MUL2:%.*]] = mul nsw i32 [[TMP12]], 1
+// CHECK: [[ADD3:%.*]] = add nsw i32 [[TMP11]], [[MUL2]]
+// CHECK: store i32 [[ADD3]], ptr [[A:%.*]], align 4
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP13]], 1
+// CHECK: store i32 [[ADD4]], ptr [[A]], align 4
+// CHECK: [[TMP14:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: call void @_Z3usei(i32 noundef [[TMP14]])
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD5:%.*]] = add nsw i32 [[TMP15]], 1
+// CHECK: store i32 [[ADD5]], 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 [[TMP4]])
+// CHECK: [[TMP16:%.*]] = load i32, ptr [[DOTOMP_IS_LAST]], align 4
+// CHECK: [[TMP17:%.*]] = icmp ne i32 [[TMP16]], 0
+// CHECK: br i1 [[TMP17]], label %[[DOTOMP_LINEAR_PU:.*]], label %[[DOTOMP_LINEAR_PU_DONE:.*]]
+// CHECK: [[DOTOMP_LINEAR_PU]]:
+// CHECK: [[X6:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP18:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: store i32 [[TMP18]], ptr [[X6]], align 4
+// CHECK: br label %[[DOTOMP_LINEAR_PU_DONE]]
+// CHECK: [[DOTOMP_LINEAR_PU_DONE]]:
+// CHECK: call void @__kmpc_barrier(ptr @[[GLOB7]], i32 [[TMP4]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z25test_taskloop_lastprivatev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z25test_taskloop_lastprivatev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z25test_taskloop_lastprivatev.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z25test_taskloop_lastprivatev.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_25:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP1]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[TMP3]], align 4
+// CHECK: call void @__kmpc_taskgroup(ptr @[[GLOB1]], i32 [[TMP4]])
+// CHECK: [[TMP5:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP4]], i32 1, i64 88, i64 8, ptr @.omp_task_entry..35)
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_27:%.*]], ptr [[TMP5]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12:%.*]], ptr [[TMP6]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = load ptr, ptr [[TMP7]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP8]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_27]], ptr [[TMP5]], i32 0, i32 1
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP6]], i32 0, i32 5
+// CHECK: store i64 0, ptr [[TMP10]], align 8
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP6]], i32 0, i32 6
+// CHECK: store i64 9, ptr [[TMP11]], align 8
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP6]], i32 0, i32 7
+// CHECK: store i64 1, ptr [[TMP12]], align 8
+// CHECK: [[TMP13:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP6]], i32 0, i32 9
+// CHECK: call void @llvm.memset.p0.i64(ptr align 8 [[TMP13]], i8 0, i64 8, i1 false)
+// CHECK: [[TMP14:%.*]] = load i64, ptr [[TMP12]], align 8
+// CHECK: call void @__kmpc_taskloop(ptr @[[GLOB1]], i32 [[TMP4]], ptr [[TMP5]], i32 1, ptr [[TMP10]], ptr [[TMP11]], i64 [[TMP14]], i32 1, i32 0, i64 0, ptr @.omp_task_dup.)
+// CHECK: call void @__kmpc_end_taskgroup(ptr @[[GLOB1]], i32 [[TMP4]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_privates_map..34(
+// CHECK-SAME: ptr noalias noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds nuw [[STRUCT__KMP_PRIVATES_T_26:%.*]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: [[TMP4:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: store ptr [[TMP3]], ptr [[TMP4]], align 8
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..35(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_27:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_27]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 5
+// CHECK: [[TMP10:%.*]] = load i64, ptr [[TMP9]], align 8
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 6
+// CHECK: [[TMP12:%.*]] = load i64, ptr [[TMP11]], align 8
+// CHECK: [[TMP13:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 7
+// CHECK: [[TMP14:%.*]] = load i64, ptr [[TMP13]], align 8
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 8
+// CHECK: [[TMP16:%.*]] = load i32, ptr [[TMP15]], align 8
+// CHECK: [[TMP17:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12]], ptr [[TMP4]], i32 0, i32 9
+// CHECK: [[TMP18:%.*]] = load ptr, ptr [[TMP17]], align 8
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META132:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META135:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META137:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META139:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META141:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META143:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store ptr [[TMP8]], ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store ptr @.omp_task_privates_map..34, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store i64 [[TMP10]], ptr [[DOTLB__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store i64 [[TMP12]], ptr [[DOTUB__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store i64 [[TMP14]], ptr [[DOTST__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store i32 [[TMP16]], ptr [[DOTLITER__ADDR_I:%.*]], align 4, !noalias [[META143]]
+// CHECK: store ptr [[TMP18]], ptr [[DOTREDUCTIONS__ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META143]]
+// CHECK: [[TMP19:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META143]]
+// CHECK: [[TMP20:%.*]] = load ptr, ptr [[DOTCOPY_FN__ADDR_I]], align 8, !noalias [[META143]]
+// CHECK: [[TMP21:%.*]] = load ptr, ptr [[DOTPRIVATES__ADDR_I]], align 8, !noalias [[META143]]
+// CHECK: call void [[TMP20]](ptr [[TMP21]], ptr [[DOTLASTPRIV_PTR_ADDR_I:%.*]]) #{{[0-9]+}}, !inline_history [[META144:![0-9]+]]
+// CHECK: [[TMP22:%.*]] = load ptr, ptr [[TMP19]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP23:%.*]] = load ptr, ptr [[DOTLASTPRIV_PTR_ADDR_I]], align 8, !noalias [[META143]]
+// CHECK: [[TMP24:%.*]] = load i64, ptr [[DOTLB__ADDR_I]], align 8, !noalias [[META143]]
+// CHECK: [[CONV_I:%.*]] = trunc i64 [[TMP24]] to i32
+// CHECK: store i32 [[CONV_I]], ptr [[DOTOMP_IV_I:%.*]], align 4, !noalias [[META143]]
+// CHECK: [[TMP25:%.*]] = load ptr, ptr [[TMP19]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: br label %[[OMP_INNER_FOR_COND_I:.*]]
+// CHECK: [[OMP_INNER_FOR_COND_I]]:
+// CHECK: [[TMP26:%.*]] = load i32, ptr [[DOTOMP_IV_I]], align 4, !noalias [[META143]]
+// CHECK: [[CONV1_I:%.*]] = sext i32 [[TMP26]] to i64
+// CHECK: [[TMP27:%.*]] = load i64, ptr [[DOTUB__ADDR_I]], align 8, !noalias [[META143]]
+// CHECK: [[CMP_I:%.*]] = icmp ule i64 [[CONV1_I]], [[TMP27]]
+// CHECK: br i1 [[CMP_I]], label %[[OMP_INNER_FOR_BODY_I:.*]], label %[[OMP_INNER_FOR_END_I:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY_I]]:
+// CHECK: [[TMP28:%.*]] = load i32, ptr [[DOTOMP_IV_I]], align 4, !noalias [[META143]]
+// CHECK: store i32 [[TMP28]], ptr [[I_I:%.*]], align 4, !noalias [[META143]]
+// CHECK: [[TMP29:%.*]] = load i32, ptr [[I_I]], align 4, !noalias [[META143]]
+// CHECK: [[MUL2_I:%.*]] = mul nsw i32 [[TMP29]], 10
+// CHECK: store i32 [[MUL2_I]], ptr [[TMP23]], align 4
+// CHECK: [[TMP30:%.*]] = load i32, ptr [[DOTOMP_IV_I]], align 4, !noalias [[META143]]
+// CHECK: [[ADD3_I:%.*]] = add nsw i32 [[TMP30]], 1
+// CHECK: store i32 [[ADD3_I]], ptr [[DOTOMP_IV_I]], align 4, !noalias [[META143]]
+// CHECK: br label %[[OMP_INNER_FOR_COND_I]]
+// CHECK: [[OMP_INNER_FOR_END_I]]:
+// CHECK: [[TMP31:%.*]] = load i32, ptr [[DOTLITER__ADDR_I]], align 4, !noalias [[META143]]
+// CHECK: [[TMP32:%.*]] = icmp ne i32 [[TMP31]], 0
+// CHECK: br i1 [[TMP32]], label %[[DOTOMP_LASTPRIVATE_THEN_I:.*]], label %[[DOTOMP_OUTLINED__33_EXIT:.*]]
+// CHECK: [[DOTOMP_LASTPRIVATE_THEN_I]]:
+// CHECK: [[TMP33:%.*]] = load i32, ptr [[TMP23]], align 4
+// CHECK: store i32 [[TMP33]], ptr [[TMP22]], align 4
+// CHECK: br label %[[DOTOMP_OUTLINED__33_EXIT]]
+// CHECK: [[DOTOMP_OUTLINED__33_EXIT]]:
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define internal void @.omp_task_dup.(
+// CHECK-SAME: ptr noundef [[TMP0:%.*]], ptr noundef [[TMP1:%.*]], i32 noundef [[TMP2:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: store i32 [[TMP2]], ptr [[DOTADDR2:%.*]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_27:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_12:%.*]], ptr [[TMP4]], i32 0, i32 8
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTADDR2]], align 4
+// CHECK: store i32 [[TMP6]], ptr [[TMP5]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_27]], ptr [[TMP3]], i32 0, i32 1
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z16test_task_sharedv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z16test_task_sharedv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z16test_task_sharedv.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z16test_task_sharedv.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds nuw [[STRUCT_ANON_28:%.*]], ptr [[AGG_CAPTURED:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP1]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[TMP3]], align 4
+// CHECK: [[TMP5:%.*]] = call ptr @__kmpc_omp_task_alloc(ptr @[[GLOB1]], i32 [[TMP4]], i32 1, i64 40, i64 8, ptr @.omp_task_entry..37)
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_29:%.*]], ptr [[TMP5]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP6]], i32 0, i32 0
+// CHECK: [[TMP8:%.*]] = load ptr, ptr [[TMP7]], align 8
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP8]], ptr align 8 [[AGG_CAPTURED]], i64 8, i1 false)
+// CHECK: [[TMP9:%.*]] = call i32 @__kmpc_omp_task(ptr @[[GLOB1]], i32 [[TMP4]], ptr [[TMP5]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal noundef i32 @.omp_task_entry..37(
+// CHECK-SAME: i32 noundef [[TMP0:%.*]], ptr noalias noundef [[TMP1:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store i32 [[TMP0]], ptr [[DOTADDR:%.*]], align 4
+// CHECK: store ptr [[TMP1]], ptr [[DOTADDR1:%.*]], align 8
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[DOTADDR]], align 4
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTADDR1]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T_WITH_PRIVATES_29:%.*]], ptr [[TMP3]], i32 0, i32 0
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T:%.*]], ptr [[TMP4]], i32 0, i32 2
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds nuw [[STRUCT_KMP_TASK_T]], ptr [[TMP4]], i32 0, i32 0
+// CHECK: [[TMP7:%.*]] = load ptr, ptr [[TMP6]], align 8
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META145:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META148:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META150:![0-9]+]])
+// CHECK: call void @llvm.experimental.noalias.scope.decl(metadata [[META152:![0-9]+]])
+// CHECK: store i32 [[TMP2]], ptr [[DOTGLOBAL_TID__ADDR_I:%.*]], align 4, !noalias [[META154:![0-9]+]]
+// CHECK: store ptr [[TMP5]], ptr [[DOTPART_ID__ADDR_I:%.*]], align 8, !noalias [[META154]]
+// CHECK: store ptr null, ptr [[DOTPRIVATES__ADDR_I:%.*]], align 8, !noalias [[META154]]
+// CHECK: store ptr null, ptr [[DOTCOPY_FN__ADDR_I:%.*]], align 8, !noalias [[META154]]
+// CHECK: store ptr [[TMP3]], ptr [[DOTTASK_T__ADDR_I:%.*]], align 8, !noalias [[META154]]
+// CHECK: store ptr [[TMP7]], ptr [[__CONTEXT_ADDR_I:%.*]], align 8, !noalias [[META154]]
+// CHECK: [[TMP8:%.*]] = load ptr, ptr [[__CONTEXT_ADDR_I]], align 8, !noalias [[META154]]
+// CHECK: [[TMP9:%.*]] = load ptr, ptr [[TMP8]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: store i32 42, ptr [[TMP9]], align 4
+// CHECK: ret i32 0
+//
+//
+// CHECK-LABEL: define dso_local void @_Z42test_parallel_for_firstprivate_lastprivatev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z42test_parallel_for_firstprivate_lastprivatev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z42test_parallel_for_firstprivate_lastprivatev.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z42test_parallel_for_firstprivate_lastprivatev.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: store i32 [[TMP2]], ptr [[A:%.*]], align 4
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP3:%.*]] = load ptr, ptr [[DOTGLOBAL_TID__ADDR]], align 8
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[TMP3]], align 4
+// CHECK: call void @__kmpc_for_static_init_4(ptr @[[GLOB2]], i32 [[TMP4]], i32 34, ptr [[DOTOMP_IS_LAST]], ptr [[DOTOMP_LB]], ptr [[DOTOMP_UB]], ptr [[DOTOMP_STRIDE]], i32 1, i32 1)
+// CHECK: [[TMP5:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP:%.*]] = icmp sgt i32 [[TMP5]], 9
+// CHECK: br i1 [[CMP]], label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
+// CHECK: [[COND_TRUE]]:
+// CHECK: br label %[[COND_END:.*]]
+// CHECK: [[COND_FALSE]]:
+// CHECK: [[TMP6:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: br label %[[COND_END]]
+// CHECK: [[COND_END]]:
+// CHECK: [[COND:%.*]] = phi i32 [ 9, %[[COND_TRUE]] ], [ [[TMP6]], %[[COND_FALSE]] ]
+// CHECK: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4
+// CHECK: [[TMP7:%.*]] = load i32, ptr [[DOTOMP_LB]], align 4
+// CHECK: store i32 [[TMP7]], ptr [[DOTOMP_IV:%.*]], align 4
+// CHECK: br label %[[OMP_INNER_FOR_COND:.*]]
+// CHECK: [[OMP_INNER_FOR_COND]]:
+// CHECK: [[TMP8:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[TMP9:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4
+// CHECK: [[CMP2:%.*]] = icmp sle i32 [[TMP8]], [[TMP9]]
+// CHECK: br i1 [[CMP2]], label %[[OMP_INNER_FOR_BODY:.*]], label %[[OMP_INNER_FOR_END:.*]]
+// CHECK: [[OMP_INNER_FOR_BODY]]:
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[MUL:%.*]] = mul nsw i32 [[TMP10]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[I]], align 4
+// CHECK: [[MUL3:%.*]] = mul nsw i32 [[TMP11]], 10
+// CHECK: store i32 [[MUL3]], ptr [[A]], align 4
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP12]], 1
+// CHECK: store i32 [[ADD4]], 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 [[TMP4]])
+// CHECK: [[TMP13:%.*]] = load i32, ptr [[DOTOMP_IS_LAST]], align 4
+// CHECK: [[TMP14:%.*]] = icmp ne i32 [[TMP13]], 0
+// CHECK: br i1 [[TMP14]], label %[[DOTOMP_LASTPRIVATE_THEN:.*]], label %[[DOTOMP_LASTPRIVATE_DONE:.*]]
+// CHECK: [[DOTOMP_LASTPRIVATE_THEN]]:
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: store i32 [[TMP15]], ptr [[X1]], align 4
+// CHECK: br label %[[DOTOMP_LASTPRIVATE_DONE]]
+// CHECK: [[DOTOMP_LASTPRIVATE_DONE]]:
+// CHECK: call void @__kmpc_barrier(ptr @[[GLOB7]], i32 [[TMP4]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z29test_private_plus_lastprivatev(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z29test_private_plus_lastprivatev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: store ptr [[Y]], ptr [[B:%.*]], align 8
+// CHECK: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr @[[GLOB1]], i32 1, ptr @_Z29test_private_plus_lastprivatev.omp_outlined, ptr [[TMP0]])
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define internal void @_Z29test_private_plus_lastprivatev.omp_outlined(
+// CHECK-SAME: ptr noalias noundef [[DOTGLOBAL_TID_:%.*]], ptr noalias noundef [[DOTBOUND_TID_:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]]) #{{[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 [[META18]], !align [[META19]]
+// CHECK: store i32 0, ptr [[DOTOMP_LB:%.*]], align 4
+// CHECK: store i32 9, 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: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 1
+// 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]], 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]], 9
+// 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 [ 9, %[[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: [[CMP1:%.*]] = icmp sle i32 [[TMP7]], [[TMP8]]
+// CHECK: br i1 [[CMP1]], 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: [[MUL:%.*]] = mul nsw i32 [[TMP9]], 1
+// CHECK: [[ADD:%.*]] = add nsw i32 0, [[MUL]]
+// CHECK: store i32 [[ADD]], ptr [[I:%.*]], align 4
+// CHECK: [[TMP10:%.*]] = load i32, ptr [[I]], align 4
+// CHECK: store i32 [[TMP10]], ptr [[A:%.*]], align 4
+// CHECK: [[TMP11:%.*]] = load i32, ptr [[A]], align 4
+// CHECK: store i32 [[TMP11]], ptr [[B:%.*]], align 4
+// CHECK: br label %[[OMP_BODY_CONTINUE:.*]]
+// CHECK: [[OMP_BODY_CONTINUE]]:
+// CHECK: br label %[[OMP_INNER_FOR_INC:.*]]
+// CHECK: [[OMP_INNER_FOR_INC]]:
+// CHECK: [[TMP12:%.*]] = load i32, ptr [[DOTOMP_IV]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP12]], 1
+// CHECK: store i32 [[ADD2]], 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: [[TMP13:%.*]] = load i32, ptr [[DOTOMP_IS_LAST]], align 4
+// CHECK: [[TMP14:%.*]] = icmp ne i32 [[TMP13]], 0
+// CHECK: br i1 [[TMP14]], label %[[DOTOMP_LASTPRIVATE_THEN:.*]], label %[[DOTOMP_LASTPRIVATE_DONE:.*]]
+// CHECK: [[DOTOMP_LASTPRIVATE_THEN]]:
+// CHECK: [[TMP15:%.*]] = load i32, ptr [[B]], align 4
+// CHECK: store i32 [[TMP15]], ptr [[Y]], align 4
+// CHECK: br label %[[DOTOMP_LASTPRIVATE_DONE]]
+// CHECK: [[DOTOMP_LASTPRIVATE_DONE]]:
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z32test_target_map_orig_use_bindingv(
+// CHECK-SAME: ) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z32test_target_map_orig_use_bindingv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[P]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[P]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr [[TMP0]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 2
+// CHECK: store ptr null, ptr [[TMP9]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z32test_target_map_orig_use_bindingv_l732(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[P:%.*]], ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #{{[0-9]+}} {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[P]], ptr [[P_ADDR:%.*]], align 8
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[TMP2:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META18]], !align [[META19]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: store i32 1, ptr [[X]], align 4
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP2]], i32 0, i32 0
+// CHECK: store i32 2, ptr [[X1]], align 4
+// CHECK: ret void
+//
diff --git a/clang/test/OpenMP/structured-bindings-messages.cpp b/clang/test/OpenMP/structured-bindings-messages.cpp
new file mode 100644
index 0000000000000..647de0055068c
--- /dev/null
+++ b/clang/test/OpenMP/structured-bindings-messages.cpp
@@ -0,0 +1,565 @@
+// RUN: %clang_cc1 -verify -std=c++20 -triple x86_64-pc-linux-gnu -fopenmp \
+// RUN: -fsyntax-only %s
+
+namespace std {
+ typedef unsigned long size_t;
+
+ // move.
+ template<typename T>
+ constexpr T&& move(T& t) noexcept {
+ return static_cast<T&&>(t);
+ }
+
+ // pair.
+ template <typename T1, typename T2>
+ struct pair {
+ T1 first;
+ T2 second;
+ };
+
+ template <typename T1, typename T2>
+ pair<T1, T2> make_pair(T1 a, T2 b) {
+ return {a, b};
+ }
+
+ // tuple.
+ template <typename... Ts>
+ struct tuple;
+
+ template <typename T, typename... Ts>
+ struct tuple<T, Ts...> {
+ T head;
+ tuple<Ts...> tail;
+ };
+
+ template <>
+ struct tuple<> {};
+
+ template <size_t I, typename T>
+ struct tuple_element;
+
+ template <typename T1, typename T2>
+ struct tuple_element<0, pair<T1, T2>> { using type = T1; };
+
+ template <typename T1, typename T2>
+ struct tuple_element<1, pair<T1, T2>> { using type = T2; };
+
+ template <typename T, typename... Ts>
+ struct tuple_element<0, tuple<T, Ts...>> { using type = T; };
+
+ template <size_t I, typename T, typename... Ts>
+ struct tuple_element<I, tuple<T, Ts...>> {
+ using type = typename tuple_element<I-1, tuple<Ts...>>::type;
+ };
+
+ template <size_t N, typename T>
+ struct tuple_element<0, T[N]> { using type = T; };
+
+ template <typename T>
+ struct tuple_size;
+
+ template <typename T1, typename T2>
+ struct tuple_size<pair<T1, T2>> {
+ static constexpr size_t value = 2;
+ };
+
+ template <typename... Ts>
+ struct tuple_size<tuple<Ts...>> {
+ static constexpr size_t value = sizeof...(Ts);
+ };
+
+ template <typename T, size_t N>
+ struct tuple_size<T[N]> {
+ static constexpr size_t value = N;
+ };
+
+ template <size_t I, typename T1, typename T2>
+ typename tuple_element<I, pair<T1, T2>>::type &
+ get(pair<T1, T2> &p) {
+ if constexpr (I == 0) return p.first;
+ else return p.second;
+ }
+
+ template <size_t I, typename T1, typename T2>
+ typename tuple_element<I, pair<T1, T2>>::type &&
+ get(pair<T1, T2> &&p) {
+ if constexpr (I == 0) return static_cast<T1&&>(p.first);
+ else return static_cast<T2&&>(p.second);
+ }
+
+ template <size_t I, typename T, typename... Ts>
+ auto& get(tuple<T, Ts...> &t) {
+ if constexpr (I == 0) return t.head;
+ else return get<I-1>(t.tail);
+ }
+
+ template <size_t I, typename T, typename... Ts>
+ auto&& get(tuple<T, Ts...> &&t) {
+ if constexpr (I == 0) return static_cast<T&&>(t.head);
+ else return get<I-1>(static_cast<tuple<Ts...>&&>(t.tail));
+ }
+
+ // array.
+ template <typename T, size_t N>
+ struct array {
+ T data[N];
+ T& operator[](size_t i) { return data[i]; }
+ const T& operator[](size_t i) const { return data[i]; }
+ };
+
+ template <size_t I, typename T, size_t N>
+ struct tuple_element<I, array<T, N>> { using type = T; };
+
+ template <typename T, size_t N>
+ struct tuple_size<array<T, N>> {
+ static constexpr size_t value = N;
+ };
+
+ template <size_t I, typename T, size_t N>
+ T& get(array<T, N> &a) { return a.data[I]; }
+
+ template <size_t I, typename T, size_t N>
+ T&& get(array<T, N> &&a) { return static_cast<T&&>(a.data[I]); }
+}
+
+void use(int);
+
+void test_pair() {
+ auto [a, b] = std::make_pair(1, 2);
+ // expected-note at -1{{'a' declared here}}
+ // expected-note at -2{{'b' declared here}}
+#pragma omp parallel
+ {
+ use(a + b);
+ // expected-error at -1{{capturing tuple-like structured binding 'a' is not yet supported in OpenMP}}
+ // expected-error at -2{{capturing tuple-like structured binding 'b' is not yet supported in OpenMP}}
+ }
+}
+
+void test_pair_map() {
+ auto [a, b] = std::make_pair(1, 2);
+ // expected-note at -1{{'a' declared here}}
+ // expected-error at +1{{mapping of tuple-like structured binding is not yet supported}}
+#pragma omp target map(a)
+ {
+ // expected-error at +1{{capturing tuple-like structured binding 'a' is not yet supported in OpenMP}}
+ use(a);
+ }
+}
+
+void test_tuple() {
+ std::tuple<int, int, int> t = {1, 2, 3};
+ auto [x, y, z] = t;
+ // expected-note at -1{{'x' declared here}}
+ // expected-note at -2{{'y' declared here}}
+ // expected-note at -3{{'z' declared here}}
+#pragma omp parallel
+ {
+ use(x + y + z);
+ // expected-error at -1{{capturing tuple-like structured binding 'x' is not yet supported in OpenMP}}
+ // expected-error at -2{{capturing tuple-like structured binding 'y' is not yet supported in OpenMP}}
+ // expected-error at -3{{capturing tuple-like structured binding 'z' is not yet supported in OpenMP}}
+ }
+}
+
+void test_array() {
+ std::array<int, 2> arr = {1, 2};
+ auto [p, q] = arr;
+ // expected-note at -1{{'p' declared here}}
+ // expected-note at -2{{'q' declared here}}
+#pragma omp parallel
+ {
+ use(p + q);
+ // expected-error at -1{{capturing tuple-like structured binding 'p' is not yet supported in OpenMP}}
+ // expected-error at -2{{capturing tuple-like structured binding 'q' is not yet supported in OpenMP}}
+ }
+}
+
+struct Point {
+ int x, y;
+};
+
+struct ArrayStruct {
+ int arr[2];
+};
+
+#pragma omp declare reduction(mysum: int: omp_out += omp_in) initializer(omp_priv = 0)
+
+Point make_point() { return {1, 2}; }
+
+void test_function_call() {
+ auto [a, b] = make_point();
+ // expected-error at +1{{mapping of structured binding initialized from function call is not yet supported}}
+#pragma omp target map(a)
+ {
+ a++;
+ }
+}
+
+void test_brace_init() {
+ auto [a, b] = Point{1, 2};
+// expected-error at +1{{mapping of structured binding initialized from initializer list is not yet supported}}
+#pragma omp target map(a)
+ {
+ a++;
+ }
+}
+
+void test_move() {
+ Point p{1, 2};
+ auto [a, b] = std::move(p);
+ // expected-error at +1{{mapping of structured binding initialized from move expression is not yet supported}}
+#pragma omp target map(a)
+ {
+ a++;
+ }
+}
+
+void test_constructor_with_function_call() {
+ auto [a, b] = Point(make_point());
+ // expected-error at +1{{mapping of structured binding initialized from function call is not yet supported}}
+#pragma omp target map(a)
+ {
+ a++;
+ }
+}
+
+void test_constructor_with_move_arg() {
+ Point p{1, 2};
+ auto [a, b] = Point(std::move(p));
+ // expected-error at +1{{mapping of structured binding initialized from move expression is not yet supported}}
+#pragma omp target map(a)
+ {
+ a++;
+ }
+}
+
+void test_constructor_with_member() {
+ struct Container { Point p; } c{{1, 2}};
+ auto [a, b] = Point(c.p);
+ // expected-error at +1{{mapping of structured binding initialized from temporary object is not yet supported}}
+#pragma omp target map(a)
+ {
+ a++;
+ }
+}
+
+
+void test_conflicting_capture_kinds_map_firstprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // map(a) creates by-ref capture, firstprivate(b) creates by-copy capture
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target map(a) firstprivate(b)
+ {
+ a++;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_map_private() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target map(a) private(b)
+ {
+ a++;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_parallel_shared_firstprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp parallel shared(a) firstprivate(b)
+ {
+ a = b;
+ }
+}
+
+void test_conflicting_capture_kinds_parallel_shared_private() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp parallel private(a) shared(b)
+ {
+ a = 100;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_parallel_default_shared_firstprivate() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp parallel default(shared) firstprivate(a)
+ {
+ a++;
+ b++;
+ }
+}
+
+void test_binding_as_loop_var_for() {
+ Point p{0, 0};
+ auto [a, b] = p;
+#pragma omp for
+ // expected-error at +1{{structured binding 'a' cannot be used as the loop control variable of an OpenMP loop}}
+ for (a = 0; a < 10; ++a)
+ ;
+}
+
+void test_binding_as_loop_var_parallel_for() {
+ Point p{0, 0};
+ auto [a, b] = p;
+#pragma omp parallel for
+ // expected-error at +1{{structured binding 'a' cannot be used as the loop control variable of an OpenMP loop}}
+ for (a = 0; a < 10; ++a)
+ ;
+}
+
+void test_binding_as_loop_var_simd() {
+ Point p{0, 0};
+ auto [a, b] = p;
+#pragma omp simd
+ // expected-error at +1{{structured binding 'a' cannot be used as the loop control variable of an OpenMP loop}}
+ for (a = 0; a < 10; ++a)
+ ;
+}
+
+void test_conflicting_capture_kinds_target_teams() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target teams map(a) firstprivate(b)
+ {
+ a++;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_target_teams_distribute() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target teams distribute map(a) firstprivate(b)
+ for (int i = 0; i < 10; ++i) {
+ a++;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_target_teams_loop() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target teams loop map(a) firstprivate(b)
+ for (int i = 0; i < 10; ++i) {
+ a++;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_target_teams_distribute_parallel_for() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target teams distribute parallel for map(a) firstprivate(b)
+ for (int i = 0; i < 10; ++i) {
+ a++;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_target_teams_distribute_parallel_for_simd() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target teams distribute parallel for simd map(a) firstprivate(b)
+ for (int i = 0; i < 10; ++i) {
+ a++;
+ b++;
+ }
+}
+
+void test_conflicting_capture_kinds_target_teams_distribute_simd() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{bindings from structured binding 'b' require conflicting capture kinds (by-reference vs. by-copy)}}
+#pragma omp target teams distribute simd map(a) firstprivate(b)
+ for (int i = 0; i < 10; ++i) {
+ a++;
+ b++;
+ }
+}
+
+void test_lastprivate_conditional() {
+ Point pt{1, 2};
+ auto [a, b] = pt;
+ // expected-note at -1{{'a' defined here}}
+ // expected-note at -2{{'b' defined here}}
+ // expected-error at +1{{conditional lastprivate on structured bindings is not yet supported}}
+#pragma omp parallel for lastprivate(conditional: a)
+ for (int i = 0; i < 10; ++i) {
+ if (i == 5)
+ a = i;
+ }
+ // expected-error at +1{{conditional lastprivate on structured bindings is not yet supported}}
+#pragma omp parallel for lastprivate(conditional: b)
+ for (int i = 0; i < 10; ++i) {
+ b = i;
+ }
+}
+
+void test_reduction_task() {
+ Point p{0, 0};
+ auto [a, b] = p;
+ // expected-error at +1{{reductions on structured bindings are not yet supported}}
+#pragma omp parallel reduction(task, +:a)
+ {
+ a += 1;
+ }
+}
+
+void test_reduction_inscan() {
+ Point p{0, 0};
+ auto [a, b] = p;
+ // expected-error at +1{{reductions on structured bindings are not yet supported}}
+#pragma omp for reduction(inscan, +:a)
+ for (int i = 0; i < 10; ++i) {
+ // expected-error at +1{{the list item must appear in 'reduction' clause with the 'inscan' modifier of the parent directive}}
+#pragma omp scan inclusive(a)
+ a += i;
+ }
+}
+
+void test_reduction_binding_nontrivial() {
+ struct NonTrivial {
+ int value;
+ NonTrivial() : value(0) {}
+ NonTrivial(int v) : value(v) {}
+ ~NonTrivial() {}
+ NonTrivial& operator+=(int x) { value += x; return *this; }
+ NonTrivial& operator+=(const NonTrivial& other) { value += other.value; return *this; }
+ };
+ struct PairNonTrivial {
+ NonTrivial a;
+ NonTrivial b;
+ };
+
+ PairNonTrivial p{NonTrivial(0), NonTrivial(0)};
+ auto [a, b] = p;
+
+ // expected-error at +1{{reductions on structured bindings are not yet supported}}
+#pragma omp parallel for reduction(+:a)
+ for (int i = 0; i < 10; ++i) {
+ a += i;
+ }
+ use(a.value);
+}
+
+void test_reduction_binding_sum() {
+ Point p{0, 0};
+ auto [a, b] = p;
+
+ // expected-error at +1{{reductions on structured bindings are not yet supported}}
+#pragma omp parallel for reduction(+:a)
+ for (int i = 0; i < 100; ++i) {
+ a += i;
+ }
+ use(a);
+}
+
+void test_reduction_binding_operators() {
+ Point p{1, 100};
+ auto [a, b] = p;
+
+ // expected-error at +1 2{{reductions on structured bindings are not yet supported}}
+#pragma omp parallel for reduction(*:a) reduction(min:b)
+ for (int i = 1; i <= 10; ++i) {
+ a *= 2;
+ if (i < b) b = i;
+ }
+ use(a);
+ use(b);
+}
+
+void test_reduction_binding_max() {
+ Point p{-100, -100};
+ auto [a, b] = p;
+
+ // expected-error at +1 2{{reductions on structured bindings are not yet supported}}
+#pragma omp parallel for reduction(max:a,b)
+ for (int i = 0; i < 100; ++i) {
+ if (i > a) a = i;
+ if (i > b) b = i;
+ }
+ use(a);
+ use(b);
+}
+
+void test_array_reduction() {
+ ArrayStruct s{{1, 2}};
+ auto [arr] = s;
+
+ // expected-error at +1{{reductions on structured bindings are not yet supported}}
+#pragma omp parallel for reduction(+:arr)
+ for (int i = 0; i < 10; ++i) {
+ arr[0] += i;
+ }
+}
+
+void test_udr_reduction() {
+ Point p{0, 0};
+ auto [a, b] = p;
+
+ // expected-error at +1{{reductions on structured bindings are not yet supported}}
+#pragma omp parallel for reduction(mysum:a)
+ for (int i = 0; i < 10; ++i) {
+ a += i;
+ }
+}
+
+void test_linear_clause_parallel_for_simd() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{linear clause with parallel constructs on structured bindings is not yet supported}}
+#pragma omp parallel for simd linear(a : 1)
+ for (int i = 0; i < 10; ++i)
+ b += a;
+ // Note: This also fails for regular variables (pre-existing Clang bug)
+}
+
+void test_linear_clause_parallel_for() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ // expected-error at +1{{linear clause with parallel constructs on structured bindings is not yet supported}}
+#pragma omp parallel for linear(a : 1)
+ for (int i = 0; i < 10; ++i)
+ b += a;
+ // Note: This also fails for regular variables (pre-existing Clang bug)
+}
+
+void test_capture_binding_in_lambda() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ [&] {
+ // expected-error at +1{{capturing in a lambda or block on structured bindings is not yet supported}}
+#pragma omp target map(tofrom: a)
+ { a++; }
+ }();
+}
+
+struct Triple { int x, y, z; };
+
+void test_bindings_only_orig_not_dereferenced() {
+ Triple t{1, 2, 3};
+ auto [a, b, c] = t;
+ // expected-error at +1{{original variable 't' is explicitly mapped but only bindings from it are used in target region; either use 't' directly or map the bindings explicitly}}
+#pragma omp target map(tofrom: t)
+ {
+ a = a + 10;
+ b = b + 20;
+ c = c + 30;
+ }
+}
diff --git a/clang/test/OpenMP/structured-bindings-nontemporal.cpp b/clang/test/OpenMP/structured-bindings-nontemporal.cpp
new file mode 100644
index 0000000000000..37e9405a036ca
--- /dev/null
+++ b/clang/test/OpenMP/structured-bindings-nontemporal.cpp
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 -std=c++20 -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s
+
+// expected-no-diagnostics
+
+struct Point { int x, y; };
+
+// CHECK-LABEL: @_Z21test_simd_nontemporalv
+void test_simd_nontemporal() {
+ Point p{1, 2};
+ auto [a, b] = p;
+ int arr[10];
+
+#pragma omp simd nontemporal(a, b)
+ for (int i = 0; i < 10; ++i) {
+ // CHECK: load i32,{{.*}}!nontemporal
+ // CHECK: load i32,{{.*}}!nontemporal
+ arr[i] = a + b;
+ }
+}
+
+// CHECK-LABEL: @_Z30test_simd_nontemporal_capturedv
+void test_simd_nontemporal_captured() {
+ Point p{3, 4};
+ auto [a, b] = p;
+ int sum = 0;
+
+#pragma omp parallel
+ {
+#pragma omp simd nontemporal(a)
+ for (int i = 0; i < 10; ++i) {
+ // CHECK: load i32,{{.*}}!nontemporal
+ sum += a;
+ }
+ }
+}
diff --git a/clang/test/OpenMP/structured-bindings-target-map-
diff erent-types.cpp b/clang/test/OpenMP/structured-bindings-target-map-
diff erent-types.cpp
new file mode 100644
index 0000000000000..abe55288a0e7c
--- /dev/null
+++ b/clang/test/OpenMP/structured-bindings-target-map-
diff erent-types.cpp
@@ -0,0 +1,336 @@
+// 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 -std=c++20 \
+// RUN: -fopenmp-targets=x86_64-unknown-unknown \
+// RUN: -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s
+
+// expected-no-diagnostics
+
+struct Point { int x, y; };
+
+// Test that
diff erent map clauses on bindings from the same decomposition
+// result in consistent capture behavior. The DecompositionDecl is captured
+// once (due to deduplication), and the capture kind should be determined
+// consistently regardless of which binding's map clause is processed first.
+
+
+void test_
diff erent_map_type() {
+ Point p{1, 2};
+ auto [a, b] = p;
+#pragma omp target map(tofrom:a) map(to:b)
+ {
+ a = a + 1;
+ b = b + 2;
+ }
+}
+
+void test_same_map_type_
diff erent() {
+ Point p{1, 2};
+ auto [a, b] = p;
+
+#pragma omp target map(tofrom:a) map(tofrom:b)
+ {
+ a = a + 1;
+ b = b + 2;
+ }
+}
+
+void test_one_binding_mapped() {
+ Point p{1, 2};
+ auto [a, b] = p;
+
+#pragma omp target map(tofrom:a)
+ {
+ a = a + 1;
+ b = b + 2;
+ }
+}
+
+void test_mixed_map_types_three_way() {
+ struct Triple { int x, y, z; };
+ Triple t{1, 2, 3};
+ auto [a, b, c] = t;
+
+#pragma omp target map(tofrom:a) map(to:b) map(from:c)
+ {
+ a = a + 1;
+ b = b + 2;
+ c = c + 3;
+ }
+}
+
+//.
+// CHECK: @.offload_sizes = private unnamed_addr constant [5 x i64] [i64 8, i64 0, i64 4, i64 4, i64 0]
+// CHECK: @.offload_maptypes = private unnamed_addr constant [5 x i64] [i64 [[#0x223]], i64 [[#0x0]], i64 [[#0x2000000000003]], i64 [[#0x2000000000001]], i64 [[#0x120]]]
+// CHECK: @.offload_sizes.1 = private unnamed_addr constant [5 x i64] [i64 8, i64 0, i64 4, i64 4, i64 0]
+// CHECK: @.offload_maptypes.2 = private unnamed_addr constant [5 x i64] [i64 [[#0x223]], i64 [[#0x0]], i64 [[#0x2000000000003]], i64 [[#0x2000000000003]], i64 [[#0x120]]]
+// CHECK: @.offload_sizes.3 = private unnamed_addr constant [3 x i64] [i64 8, i64 4, i64 0]
+// CHECK: @.offload_maptypes.4 = private unnamed_addr constant [3 x i64] [i64 [[#0x223]], i64 [[#0x3]], i64 [[#0x120]]]
+// CHECK: @.offload_sizes.5 = private unnamed_addr constant [6 x i64] [i64 12, i64 0, i64 4, i64 4, i64 4, i64 0]
+// CHECK: @.offload_maptypes.6 = private unnamed_addr constant [6 x i64] [i64 [[#0x223]], i64 [[#0x0]], i64 [[#0x2000000000003]], i64 [[#0x2000000000001]], i64 [[#0x2000000000002]], i64 [[#0x120]]]
+//.
+// CHECK-LABEL: define dso_local void @_Z23test_
diff erent_map_typev(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z23test_
diff erent_map_typev.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP1:%.*]] = getelementptr i32, ptr [[Y]], i32 1
+// CHECK: [[TMP2:%.*]] = ptrtoaddr ptr [[TMP1]] to i64
+// CHECK: [[TMP3:%.*]] = ptrtoaddr ptr [[X]] to i64
+// CHECK: [[TMP4:%.*]] = sub i64 [[TMP2]], [[TMP3]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[DOTOFFLOAD_SIZES:%.*]], ptr align 8 @.offload_sizes, i64 40, i1 false)
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr [[P]], ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr [[X]], ptr [[TMP9]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [5 x i64], ptr [[DOTOFFLOAD_SIZES]], i32 0, i32 1
+// CHECK: store i64 [[TMP4]], ptr [[TMP10]], align 8
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP11]], align 8
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 2
+// CHECK: store ptr [[P]], ptr [[TMP12]], align 8
+// CHECK: [[TMP13:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 2
+// CHECK: store ptr [[X]], ptr [[TMP13]], align 8
+// CHECK: [[TMP14:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 2
+// CHECK: store ptr null, ptr [[TMP14]], align 8
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 3
+// CHECK: store ptr [[P]], ptr [[TMP15]], align 8
+// CHECK: [[TMP16:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 3
+// CHECK: store ptr [[Y]], ptr [[TMP16]], align 8
+// CHECK: [[TMP17:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 3
+// CHECK: store ptr null, ptr [[TMP17]], align 8
+// CHECK: [[TMP18:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 4
+// CHECK: store ptr null, ptr [[TMP18]], align 8
+// CHECK: [[TMP19:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 4
+// CHECK: store ptr null, ptr [[TMP19]], align 8
+// CHECK: [[TMP20:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 4
+// CHECK: store ptr null, ptr [[TMP20]], align 8
+// CHECK: [[TMP21:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP22:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP23:%.*]] = getelementptr inbounds [5 x i64], ptr [[DOTOFFLOAD_SIZES]], i32 0, i32 0
+// CHECK: [[TMP24:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z23test_
diff erent_map_typev_l19(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #[[ATTR2:[0-9]+]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META10:![0-9]+]], !align [[META11:![0-9]+]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], 1
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: store i32 [[ADD]], ptr [[X1]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP3]], 2
+// CHECK: [[Y3:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: store i32 [[ADD2]], ptr [[Y3]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z28test_same_map_type_
diff erentv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z28test_same_map_type_
diff erentv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[TMP1:%.*]] = getelementptr i32, ptr [[Y]], i32 1
+// CHECK: [[TMP2:%.*]] = ptrtoaddr ptr [[TMP1]] to i64
+// CHECK: [[TMP3:%.*]] = ptrtoaddr ptr [[X]] to i64
+// CHECK: [[TMP4:%.*]] = sub i64 [[TMP2]], [[TMP3]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[DOTOFFLOAD_SIZES:%.*]], ptr align 8 @.offload_sizes.1, i64 40, i1 false)
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr [[P]], ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr [[X]], ptr [[TMP9]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [5 x i64], ptr [[DOTOFFLOAD_SIZES]], i32 0, i32 1
+// CHECK: store i64 [[TMP4]], ptr [[TMP10]], align 8
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP11]], align 8
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 2
+// CHECK: store ptr [[P]], ptr [[TMP12]], align 8
+// CHECK: [[TMP13:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 2
+// CHECK: store ptr [[X]], ptr [[TMP13]], align 8
+// CHECK: [[TMP14:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 2
+// CHECK: store ptr null, ptr [[TMP14]], align 8
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 3
+// CHECK: store ptr [[P]], ptr [[TMP15]], align 8
+// CHECK: [[TMP16:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 3
+// CHECK: store ptr [[Y]], ptr [[TMP16]], align 8
+// CHECK: [[TMP17:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 3
+// CHECK: store ptr null, ptr [[TMP17]], align 8
+// CHECK: [[TMP18:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 4
+// CHECK: store ptr null, ptr [[TMP18]], align 8
+// CHECK: [[TMP19:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 4
+// CHECK: store ptr null, ptr [[TMP19]], align 8
+// CHECK: [[TMP20:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 4
+// CHECK: store ptr null, ptr [[TMP20]], align 8
+// CHECK: [[TMP21:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP22:%.*]] = getelementptr inbounds [5 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP23:%.*]] = getelementptr inbounds [5 x i64], ptr [[DOTOFFLOAD_SIZES]], i32 0, i32 0
+// CHECK: [[TMP24:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z28test_same_map_type_
diff erentv_l30(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #[[ATTR2]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META10]], !align [[META11]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], 1
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: store i32 [[ADD]], ptr [[X1]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP3]], 2
+// CHECK: [[Y3:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: store i32 [[ADD2]], ptr [[Y3]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z23test_one_binding_mappedv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[P:%.*]], ptr align 4 @__const._Z23test_one_binding_mappedv.p, i64 8, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[P]], i64 8, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[TMP1:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP1]], align 8
+// CHECK: [[TMP2:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP2]], align 8
+// CHECK: [[TMP3:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP3]], align 8
+// CHECK: [[TMP4:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr [[P]], ptr [[TMP4]], align 8
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr [[X]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 2
+// CHECK: store ptr null, ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 2
+// CHECK: store ptr null, ptr [[TMP9]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds [3 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z23test_one_binding_mappedv_l41(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(8) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #[[ATTR2]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META10]], !align [[META11]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], 1
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: store i32 [[ADD]], ptr [[X1]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP3]], 2
+// CHECK: [[Y3:%.*]] = getelementptr inbounds nuw [[STRUCT_POINT]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: store i32 [[ADD2]], ptr [[Y3]], align 4
+// CHECK: ret void
+//
+//
+// CHECK-LABEL: define dso_local void @_Z30test_mixed_map_types_three_wayv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[T:%.*]], ptr align 4 @__const._Z30test_mixed_map_types_three_wayv.t, i64 12, i1 false)
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP0:%.*]], ptr align 4 [[T]], i64 12, i1 false)
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE:%.*]], ptr [[TMP0]], i32 0, i32 0
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP0]], i32 0, i32 1
+// CHECK: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP0]], i32 0, i32 2
+// CHECK: [[TMP1:%.*]] = getelementptr i32, ptr [[Z]], i32 1
+// CHECK: [[TMP2:%.*]] = ptrtoaddr ptr [[TMP1]] to i64
+// CHECK: [[TMP3:%.*]] = ptrtoaddr ptr [[X]] to i64
+// CHECK: [[TMP4:%.*]] = sub i64 [[TMP2]], [[TMP3]]
+// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[DOTOFFLOAD_SIZES:%.*]], ptr align 8 @.offload_sizes.5, i64 48, i1 false)
+// CHECK: [[TMP5:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_BASEPTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP5]], align 8
+// CHECK: [[TMP6:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_PTRS:%.*]], i32 0, i32 0
+// CHECK: store ptr [[TMP0]], ptr [[TMP6]], align 8
+// CHECK: [[TMP7:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_MAPPERS:%.*]], i64 0, i64 0
+// CHECK: store ptr null, ptr [[TMP7]], align 8
+// CHECK: [[TMP8:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 1
+// CHECK: store ptr [[T]], ptr [[TMP8]], align 8
+// CHECK: [[TMP9:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 1
+// CHECK: store ptr [[X]], ptr [[TMP9]], align 8
+// CHECK: [[TMP10:%.*]] = getelementptr inbounds [6 x i64], ptr [[DOTOFFLOAD_SIZES]], i32 0, i32 1
+// CHECK: store i64 [[TMP4]], ptr [[TMP10]], align 8
+// CHECK: [[TMP11:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 1
+// CHECK: store ptr null, ptr [[TMP11]], align 8
+// CHECK: [[TMP12:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 2
+// CHECK: store ptr [[T]], ptr [[TMP12]], align 8
+// CHECK: [[TMP13:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 2
+// CHECK: store ptr [[X]], ptr [[TMP13]], align 8
+// CHECK: [[TMP14:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 2
+// CHECK: store ptr null, ptr [[TMP14]], align 8
+// CHECK: [[TMP15:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 3
+// CHECK: store ptr [[T]], ptr [[TMP15]], align 8
+// CHECK: [[TMP16:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 3
+// CHECK: store ptr [[Y]], ptr [[TMP16]], align 8
+// CHECK: [[TMP17:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 3
+// CHECK: store ptr null, ptr [[TMP17]], align 8
+// CHECK: [[TMP18:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 4
+// CHECK: store ptr [[T]], ptr [[TMP18]], align 8
+// CHECK: [[TMP19:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 4
+// CHECK: store ptr [[Z]], ptr [[TMP19]], align 8
+// CHECK: [[TMP20:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 4
+// CHECK: store ptr null, ptr [[TMP20]], align 8
+// CHECK: [[TMP21:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 5
+// CHECK: store ptr null, ptr [[TMP21]], align 8
+// CHECK: [[TMP22:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 5
+// CHECK: store ptr null, ptr [[TMP22]], align 8
+// CHECK: [[TMP23:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_MAPPERS]], i64 0, i64 5
+// CHECK: store ptr null, ptr [[TMP23]], align 8
+// CHECK: [[TMP24:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_BASEPTRS]], i32 0, i32 0
+// CHECK: [[TMP25:%.*]] = getelementptr inbounds [6 x ptr], ptr [[DOTOFFLOAD_PTRS]], i32 0, i32 0
+// CHECK: [[TMP26:%.*]] = getelementptr inbounds [6 x i64], ptr [[DOTOFFLOAD_SIZES]], i32 0, i32 0
+// CHECK: [[TMP27:%.*]] = getelementptr inbounds nuw [[STRUCT___TGT_KERNEL_ARGUMENTS:%.*]], ptr [[KERNEL_ARGS:%.*]], i32 0, i32 0
+//
+//
+// CHECK-LABEL: define internal void @{{__omp_offloading_[0-9a-z]+_[0-9a-z]+}}__Z30test_mixed_map_types_three_wayv_l53(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(12) [[TMP0:%.*]], ptr noalias noundef [[DYN_PTR:%.*]]) #[[ATTR2]] {
+// CHECK: [[ENTRY:.*:]]
+// CHECK: store ptr [[TMP0]], ptr [[DOTADDR:%.*]], align 8
+// CHECK: store ptr [[DYN_PTR]], ptr [[DYN_PTR_ADDR:%.*]], align 8
+// CHECK: [[TMP1:%.*]] = load ptr, ptr [[DOTADDR]], align 8, !nonnull [[META10]], !align [[META11]]
+// CHECK: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE:%.*]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// CHECK: [[ADD:%.*]] = add nsw i32 [[TMP2]], 1
+// CHECK: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP1]], i32 0, i32 0
+// CHECK: store i32 [[ADD]], ptr [[X1]], align 4
+// CHECK: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: [[TMP3:%.*]] = load i32, ptr [[Y]], align 4
+// CHECK: [[ADD2:%.*]] = add nsw i32 [[TMP3]], 2
+// CHECK: [[Y3:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP1]], i32 0, i32 1
+// CHECK: store i32 [[ADD2]], ptr [[Y3]], align 4
+// CHECK: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP1]], i32 0, i32 2
+// CHECK: [[TMP4:%.*]] = load i32, ptr [[Z]], align 4
+// CHECK: [[ADD4:%.*]] = add nsw i32 [[TMP4]], 3
+// CHECK: [[Z5:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE]], ptr [[TMP1]], i32 0, i32 2
+// CHECK: store i32 [[ADD4]], ptr [[Z5]], align 4
+// CHECK: ret void
+//
diff --git a/clang/test/OpenMP/structured-bindings-template-instantiation.cpp b/clang/test/OpenMP/structured-bindings-template-instantiation.cpp
new file mode 100644
index 0000000000000..99425b56d381a
--- /dev/null
+++ b/clang/test/OpenMP/structured-bindings-template-instantiation.cpp
@@ -0,0 +1,511 @@
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=51 -x c++ -std=c++20 \
+// RUN: -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s
+
+// RUN: %clang_cc1 -verify -std=c++20 -fopenmp -triple x86_64-pc-linux-gnu \
+// RUN: -ast-print %s | FileCheck %s --check-prefix=AST
+
+// expected-no-diagnostics
+
+// Test template instantiation with structured bindings in OpenMP regions.
+// This verifies that skipping duplicate captures (when both bindings from
+// the same decomposition are used) doesn't break template instantiation.
+
+void use(int);
+
+struct Point {
+ int x, y;
+};
+
+struct Point3D {
+ int x, y, z;
+};
+
+// CHECK-LABEL: define {{.*}} @_Z28test_template_single_bindingI5PointEvT_(
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr @{{[0-9]+}}, i32 1, ptr @{{.*}}.omp_outlined, ptr
+//
+// AST: void use(int);
+// AST: struct Point {
+// AST: int x;
+// AST: int y;
+// AST: };
+// AST: struct Point3D {
+// AST: int x;
+// AST: int y;
+// AST: int z;
+// AST: };
+// AST: template <typename T> void test_template_single_binding(T p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel
+// AST: {
+// AST: use(a);
+// AST: }
+// AST: }
+// AST: template<> void test_template_single_binding<Point>(Point p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel
+// AST: {
+// AST: use(a);
+// AST: }
+// AST: }
+template<typename T>
+void test_template_single_binding(T p) {
+ auto [a, b] = p;
+#pragma omp parallel
+ {
+ use(a);
+ }
+}
+
+// CHECK-LABEL: define {{.*}}@_Z26test_template_two_bindingsI5PointEvT_
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr {{.*}}, ptr
+//
+// AST: template <typename T> void test_template_two_bindings(T p) {
+// AST: auto [a, b] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = a + b;
+// AST: }
+// AST: }
+// AST: template<> void test_template_two_bindings<Point>(Point p) {
+// AST: auto [a, b] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = a + b;
+// AST: }
+// AST: }
+template<typename T>
+void test_template_two_bindings(T p) {
+ auto [a, b] = p;
+ int result = 0;
+#pragma omp parallel reduction(+:result)
+ {
+ result = a + b;
+ }
+}
+
+// CHECK-LABEL: define {{.*}}@_Z28test_template_three_bindingsI7Point3DEiT_
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr {{.*}}, ptr
+//
+// AST: template <typename T> int test_template_three_bindings(T p) {
+// AST: auto [x, y, z] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = x + y + z;
+// AST: }
+// AST: return result;
+// AST: }
+// AST: template<> int test_template_three_bindings<Point3D>(Point3D p) {
+// AST: auto [x, y, z] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = x + y + z;
+// AST: }
+// AST: return result;
+// AST: }
+template<typename T>
+int test_template_three_bindings(T p) {
+ auto [x, y, z] = p;
+
+ int result = 0;
+#pragma omp parallel reduction(+:result)
+ {
+ result = x + y + z;
+ }
+ return result;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z28test_template_reuse_bindingsI5PointEiT_
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr {{.*}}, ptr
+//
+// AST: template <typename T> int test_template_reuse_bindings(T p) {
+// AST: auto [a, b] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = a + b + a * 2 + b * 3;
+// AST: }
+// AST: return result;
+// AST: }
+// AST: template<> int test_template_reuse_bindings<Point>(Point p) {
+// AST: auto [a, b] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = a + b + a * 2 + b * 3;
+// AST: }
+// AST: return result;
+// AST: }
+template<typename T>
+int test_template_reuse_bindings(T p) {
+ auto [a, b] = p;
+ int result = 0;
+#pragma omp parallel reduction(+:result)
+ {
+ result = a + b + a * 2 + b * 3;
+ }
+ return result;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z20test_template_nestedI5PointEiT_
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr {{.*}}, ptr
+//
+// AST: template <typename T> int test_template_nested(T p) {
+// AST: auto [a, b] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel
+// AST: {
+// AST: #pragma omp critical
+// AST: {
+// AST: result += a + b;
+// AST: }
+// AST: }
+// AST: return result;
+// AST: }
+// AST: template<> int test_template_nested<Point>(Point p) {
+// AST: auto [a, b] = p;
+// AST: int result = 0;
+// AST: #pragma omp parallel
+// AST: {
+// AST: #pragma omp critical
+// AST: {
+// AST: result += a + b;
+// AST: }
+// AST: }
+// AST: return result;
+// AST: }
+template<typename T>
+int test_template_nested(T p) {
+ auto [a, b] = p;
+ int result = 0;
+#pragma omp parallel
+ {
+#pragma omp critical
+ {
+ result += a + b;
+ }
+ }
+ return result;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z30test_template_multiple_regionsI5PointEvT_
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr {{.*}}, ptr
+//
+// AST: template <typename T> void test_template_multiple_regions(T p) {
+// AST: auto [a, b] = p;
+// AST: int result1 = 0, result2 = 0;
+// AST: #pragma omp parallel reduction(+: result1)
+// AST: {
+// AST: result1 = a;
+// AST: }
+// AST: }
+// AST: template<> void test_template_multiple_regions<Point>(Point p) {
+// AST: auto [a, b] = p;
+// AST: int result1 = 0, result2 = 0;
+// AST: #pragma omp parallel reduction(+: result1)
+// AST: {
+// AST: result1 = a;
+// AST: }
+// AST: }
+template<typename T>
+void test_template_multiple_regions(T p) {
+ auto [a, b] = p;
+ int result1 = 0, result2 = 0;
+#pragma omp parallel reduction(+:result1)
+ {
+ result1 = a;
+ }
+}
+
+typedef unsigned int size_t;
+// CHECK-LABEL: define {{.*}}@_Z19test_template_arrayIiLj2EEiRAT0__T_
+// CHECK: call void {{.*}}@__kmpc_fork_call(ptr {{.*}}, i32 2, ptr {{.*}}, ptr
+//
+// AST: typedef unsigned int size_t;
+// AST: template <typename T, size_t N> int test_template_array(T (&arr)[N]) {
+// AST: auto [a, b] = arr;
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = a + b;
+// AST: }
+// AST: return result;
+// AST: }
+// AST: template<> int test_template_array<int, 2U>(int (&arr)[2]) {
+// AST: auto [a, b] = {arr[*]};
+// AST: int result = 0;
+// AST: #pragma omp parallel reduction(+: result)
+// AST: {
+// AST: result = a + b;
+// AST: }
+// AST: return result;
+// AST: }
+template<typename T, size_t N>
+int test_template_array(T (&arr)[N]) {
+ auto [a, b] = arr;
+ int result = 0;
+#pragma omp parallel reduction(+:result)
+ {
+ result = a + b;
+ }
+ return result;
+}
+
+template<typename T>
+struct Pair {
+ T first;
+ T second;
+};
+
+// CHECK-LABEL: define {{.*}}@_Z28test_dependent_decompositionI{{.*}}
+// AST: template <typename T> void test_dependent_decomposition(Pair<T> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel shared(a,b)
+// AST: {
+// AST: a = a + T(1);
+// AST: b = b + T(2);
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_decomposition<int>(Pair<int> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel shared(a,b)
+// AST: {
+// AST: a = a + int(1);
+// AST: b = b + int(2);
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_decomposition<double>(Pair<double> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel shared(a,b)
+// AST: {
+// AST: a = a + double(1);
+// AST: b = b + double(2);
+// AST: }
+// AST: }
+
+template<typename T>
+void test_dependent_decomposition(Pair<T> p) {
+ auto [a, b] = p;
+
+#pragma omp parallel shared(a, b)
+ {
+ a = a + T(1);
+ b = b + T(2);
+ }
+}
+
+// CHECK-LABEL: define {{.*}}@_Z24test_dependent_reductionI{{.*}}
+// AST: template <typename T> T test_dependent_reduction(Pair<T> p) {
+// AST: auto [a, b] = p;
+// AST: T sum = T(0);
+// AST: #pragma omp parallel for reduction(+: sum)
+// AST: for (int i = 0; i < 10; ++i) {
+// AST: sum = sum + a + b;
+// AST: }
+// AST: return sum;
+// AST: }
+// AST: template<> int test_dependent_reduction<int>(Pair<int> p) {
+// AST: auto [a, b] = p;
+// AST: int sum = int(0);
+// AST: #pragma omp parallel for reduction(+: sum)
+// AST: for (int i = 0; i < 10; ++i) {
+// AST: sum = sum + a + b;
+// AST: }
+// AST: return sum;
+// AST: }
+// AST: template<> double test_dependent_reduction<double>(Pair<double> p) {
+// AST: auto [a, b] = p;
+// AST: double sum = double(0);
+// AST: #pragma omp parallel for reduction(+: sum)
+// AST: for (int i = 0; i < 10; ++i) {
+// AST: sum = sum + a + b;
+// AST: }
+// AST: return sum;
+// AST: }
+template<typename T>
+T test_dependent_reduction(Pair<T> p) {
+ auto [a, b] = p;
+ T sum = T(0);
+
+#pragma omp parallel for reduction(+:sum)
+ for (int i = 0; i < 10; ++i) {
+ sum = sum + a + b;
+ }
+ return sum;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z27test_dependent_firstprivateI{{.*}}
+// AST: template <typename T> void test_dependent_firstprivate(Pair<T> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel firstprivate(a,b)
+// AST: {
+// AST: T local = a + b;
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_firstprivate<int>(Pair<int> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel firstprivate(a,b)
+// AST: {
+// AST: int local = a + b;
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_firstprivate<double>(Pair<double> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp parallel firstprivate(a,b)
+// AST: {
+// AST: double local = a + b;
+// AST: }
+// AST: }
+template<typename T>
+void test_dependent_firstprivate(Pair<T> p) {
+ auto [a, b] = p;
+
+#pragma omp parallel firstprivate(a, b)
+ {
+ T local = a + b;
+ }
+}
+
+// CHECK-LABEL: define {{.*}}@_Z19test_dependent_taskI{{.*}}
+// AST: template <typename T> void test_dependent_task(Pair<T> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp task shared(a)
+// AST: {
+// AST: a = a + T(10);
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_task<int>(Pair<int> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp task shared(a)
+// AST: {
+// AST: a = a + int(10);
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_task<double>(Pair<double> p) {
+// AST: auto [a, b] = p;
+// AST: #pragma omp task shared(a)
+// AST: {
+// AST: a = a + double(10);
+// AST: }
+// AST: }
+template<typename T>
+void test_dependent_task(Pair<T> p) {
+ auto [a, b] = p;
+
+#pragma omp task shared(a)
+ {
+ a = a + T(10);
+ }
+}
+
+template<typename T>
+struct Triple {
+ T x, y, z;
+};
+
+// CHECK-LABEL: define {{.*}}@_Z30test_dependent_partial_captureIiEv6TripleIT_E
+// AST: template <typename T> void test_dependent_partial_capture(Triple<T> p) {
+// AST: auto [a, b, c] = p;
+// AST: #pragma omp parallel firstprivate(a)
+// AST: {
+// AST: T result = a + b + c;
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_partial_capture<int>(Triple<int> p) {
+// AST: auto [a, b, c] = p;
+// AST: #pragma omp parallel firstprivate(a)
+// AST: {
+// AST: int result = a + b + c;
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_partial_capture<double>(Triple<double> p) {
+// AST: auto [a, b, c] = p;
+// AST: #pragma omp parallel firstprivate(a)
+// AST: {
+// AST: double result = a + b + c;
+// AST: }
+// AST: }
+template<typename T>
+void test_dependent_partial_capture(Triple<T> p) {
+ auto [a, b, c] = p;
+#pragma omp parallel firstprivate(a)
+ {
+ T result = a + b + c;
+ }
+}
+
+// CHECK-LABEL: define {{.*}}@_Z32test_dependent_multiple_capturesI{{.*}}
+// AST: template <typename T> void test_dependent_multiple_captures(Pair<T> p1, Pair<T> p2) {
+// AST: auto [a, b] = p1;
+// AST: auto [c, d] = p2;
+// AST: #pragma omp parallel shared(a,b,c,d)
+// AST: {
+// AST: T result = a + b + c + d;
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_multiple_captures<int>(Pair<int> p1, Pair<int> p2) {
+// AST: auto [a, b] = p1;
+// AST: auto [c, d] = p2;
+// AST: #pragma omp parallel shared(a,b,c,d)
+// AST: {
+// AST: int result = a + b + c + d;
+// AST: }
+// AST: }
+// AST: template<> void test_dependent_multiple_captures<double>(Pair<double> p1, Pair<double> p2) {
+// AST: auto [a, b] = p1;
+// AST: auto [c, d] = p2;
+// AST: #pragma omp parallel shared(a,b,c,d)
+// AST: {
+// AST: double result = a + b + c + d;
+// AST: }
+// AST: }
+template<typename T>
+void test_dependent_multiple_captures(Pair<T> p1, Pair<T> p2) {
+ auto [a, b] = p1;
+ auto [c, d] = p2;
+
+#pragma omp parallel shared(a, b, c, d)
+ {
+ T result = a + b + c + d;
+ }
+}
+
+void instantiate_tests() {
+ Point p1{1, 2};
+ Point3D p2{1, 2, 3};
+ int arr[2] = {1, 2};
+ test_template_single_binding(p1);
+ test_template_two_bindings(p1);
+ test_template_three_bindings(p2);
+ test_template_reuse_bindings(p1);
+ test_template_nested(p1);
+ test_template_multiple_regions(p1);
+ test_template_array(arr);
+
+ Pair<int> pi{1, 2};
+ test_dependent_decomposition(pi);
+ test_dependent_reduction(pi);
+ test_dependent_firstprivate(pi);
+ test_dependent_task(pi);
+
+ Pair<int> pi2{3, 4};
+ test_dependent_multiple_captures(pi, pi2);
+
+ Triple<int> ti{1, 2, 3};
+ test_dependent_partial_capture(ti);
+
+ Pair<double> pd{1.5, 2.5};
+ test_dependent_decomposition(pd);
+ test_dependent_reduction(pd);
+ test_dependent_firstprivate(pd);
+ test_dependent_task(pd);
+
+ Pair<double> pd2{3.5, 4.5};
+ test_dependent_multiple_captures(pd, pd2);
+
+ Triple<double> td{1.5, 2.5, 3.5};
+ test_dependent_partial_capture(td);
+}
diff --git a/clang/test/SemaCXX/decomposition-openmp.cpp b/clang/test/SemaCXX/decomposition-openmp.cpp
index 2185f3db83d4e..78135d90a561b 100644
--- a/clang/test/SemaCXX/decomposition-openmp.cpp
+++ b/clang/test/SemaCXX/decomposition-openmp.cpp
@@ -1,10 +1,17 @@
+// 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
+
// 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;
};
}
@@ -15,18 +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); };
}
}
-// FIXME: OpenMP should support capturing structured bindings
void h() {
int i[2] = {};
- auto [a, b] = i; // expected-note 2{{declared here}}
+ // cxx17-note at +2{{'a' declared here}}
+ // cxx17-note at +1{{'b' declared here}}
+ auto [a, b] = i;
#pragma omp parallel
{
- // expected-error at +1 2{{capturing a structured binding is not yet supported in OpenMP}}
+ // 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);
}
}
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 8ab5948f65ffd..59aac4c507772 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -5305,6 +5305,9 @@ void EmitClangAttrTextNodeDump(const RecordKeeper &Records, raw_ostream &OS) {
if (!R.getValueAsBit("ASTNode"))
continue;
+ if (R.getValueAsBit("HasCustomTextNodeDump"))
+ continue;
+
// If the attribute has a semantically-meaningful name (which is determined
// by whether there is a Spelling enumeration for it), then write out the
// spelling used for the attribute.
More information about the cfe-commits
mailing list