[llvm-branch-commits] [mlir] WIP: [mlir][OpenMP] Pack task private variables into a heap-allocated context struct (PR #125307)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Feb 4 09:15:53 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-openmp
Author: Tom Eccles (tblah)
<details>
<summary>Changes</summary>
See RFC:
https://discourse.llvm.org/t/rfc-openmp-supporting-delayed-task-execution-with-firstprivate-variables/83084
The aim here is to ensure that tasks which are not executed for a while after they are created do not try to reference any data which are now out of scope. This is done by packing the data referred to by the task into a heap allocated structure (freed at the end of the task).
I decided to create the task context structure in
OpenMPToLLVMIRTranslation instead of adapting how it is done CodeExtractor (via OpenMPIRBuilder] because CodeExtractor is (at least in theory) generic code which could have other unrelated uses.
---
Patch is 22.85 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/125307.diff
3 Files Affected:
- (modified) mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (+233-34)
- (modified) mlir/test/Target/LLVMIR/openmp-llvm.mlir (+8-5)
- (added) mlir/test/Target/LLVMIR/openmp-task-privatization.mlir (+81)
``````````diff
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 8a9a69cefad8ee..1d36181a2fa8fa 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -13,6 +13,7 @@
#include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
#include "mlir/Analysis/TopologicalSortUtils.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
#include "mlir/IR/IRMapping.h"
@@ -24,10 +25,12 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Frontend/OpenMP/OMPConstants.h"
#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/ReplaceConstant.h"
#include "llvm/Support/FileSystem.h"
@@ -1331,19 +1334,16 @@ findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder,
/// Initialize a single (first)private variable. You probably want to use
/// allocateAndInitPrivateVars instead of this.
-static llvm::Error
-initPrivateVar(llvm::IRBuilderBase &builder,
- LLVM::ModuleTranslation &moduleTranslation,
- omp::PrivateClauseOp &privDecl, Value mlirPrivVar,
- BlockArgument &blockArg, llvm::Value *llvmPrivateVar,
- llvm::SmallVectorImpl<llvm::Value *> &llvmPrivateVars,
- llvm::BasicBlock *privInitBlock,
- llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
+/// This returns the private variable which has been initialized. This
+/// variable should be mapped before constructing the body of the Op.
+static llvm::Expected<llvm::Value *> initPrivateVar(
+ llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
+ omp::PrivateClauseOp &privDecl, Value mlirPrivVar, BlockArgument &blockArg,
+ llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock,
+ llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
Region &initRegion = privDecl.getInitRegion();
if (initRegion.empty()) {
- moduleTranslation.mapValue(blockArg, llvmPrivateVar);
- llvmPrivateVars.push_back(llvmPrivateVar);
- return llvm::Error::success();
+ return llvmPrivateVar;
}
// map initialization region block arguments
@@ -1363,17 +1363,15 @@ initPrivateVar(llvm::IRBuilderBase &builder,
assert(phis.size() == 1 && "expected one allocation to be yielded");
- // prefer the value yielded from the init region to the allocated private
- // variable in case the region is operating on arguments by-value (e.g.
- // Fortran character boxes).
- moduleTranslation.mapValue(blockArg, phis[0]);
- llvmPrivateVars.push_back(phis[0]);
-
// clear init region block argument mapping in case it needs to be
// re-created with a different source for another use of the same
// reduction decl
moduleTranslation.forgetMapping(initRegion);
- return llvm::Error::success();
+
+ // Prefer the value yielded from the init region to the allocated private
+ // variable in case the region is operating on arguments by-value (e.g.
+ // Fortran character boxes).
+ return phis[0];
}
/// Allocate and initialize delayed private variables. Returns the basic block
@@ -1415,11 +1413,13 @@ static llvm::Expected<llvm::BasicBlock *> allocateAndInitPrivateVars(
llvm::Value *llvmPrivateVar = builder.CreateAlloca(
llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
- llvm::Error err = initPrivateVar(
+ llvm::Expected<llvm::Value *> privateVarOrError = initPrivateVar(
builder, moduleTranslation, privDecl, mlirPrivVar, blockArg,
- llvmPrivateVar, llvmPrivateVars, privInitBlock, mappedPrivateVars);
- if (err)
+ llvmPrivateVar, privInitBlock, mappedPrivateVars);
+ if (auto err = privateVarOrError.takeError())
return err;
+ llvmPrivateVars.push_back(privateVarOrError.get());
+ moduleTranslation.mapValue(blockArg, privateVarOrError.get());
}
return afterAllocas;
}
@@ -1730,6 +1730,126 @@ buildDependData(std::optional<ArrayAttr> dependKinds, OperandRange dependVars,
}
}
+static bool privatizerReadsSourceVariable(omp::PrivateClauseOp &priv) {
+ if (priv.getDataSharingType() == omp::DataSharingClauseType::FirstPrivate)
+ return true;
+
+ Region &initRegion = priv.getInitRegion();
+ if (initRegion.empty())
+ return false;
+
+ BlockArgument sourceVariable = priv.getInitMoldArg();
+ if (!sourceVariable)
+ return false;
+ return !sourceVariable.use_empty();
+}
+
+namespace {
+/// TaskContextStructManager takes care of creating and freeing a structure
+/// containing information needed by the task body to execute.
+class TaskContextStructManager {
+public:
+ TaskContextStructManager(llvm::IRBuilderBase &builder,
+ LLVM::ModuleTranslation &moduleTranslation,
+ MutableArrayRef<omp::PrivateClauseOp> privateDecls)
+ : builder{builder}, moduleTranslation{moduleTranslation},
+ privateDecls{privateDecls} {}
+
+ /// Creates a heap allocated struct containing space for each private
+ /// variable. Returns nullptr if there are is no struct needed. Invariant:
+ /// privateVarTypes, privateDecls, and the elements of the structure should
+ /// all have the same order (although privateDecls which do not read from the
+ /// mold argument are skipped).
+ void generateTaskContextStruct();
+
+ /// Create GEPs to access each member of the structure representing a private
+ /// variable, adding them to llvmPrivateVars. Null values are added where
+ /// private decls were skipped so that the ordering continues to match the
+ /// private decls.
+ void createGEPsToPrivateVars(SmallVectorImpl<llvm::Value *> &llvmPrivateVars);
+
+ /// De-allocate the task context structure.
+ void freeStructPtr();
+
+ llvm::Value *getStructPtr() { return structPtr; }
+
+private:
+ llvm::IRBuilderBase &builder;
+ LLVM::ModuleTranslation &moduleTranslation;
+ MutableArrayRef<omp::PrivateClauseOp> privateDecls;
+
+ /// The type of each member of the structure, in order.
+ SmallVector<llvm::Type *> privateVarTypes;
+
+ /// A pointer to the structure containing context for this task.
+ llvm::Value *structPtr = nullptr;
+ /// The type of the structure
+ llvm::Type *structTy = nullptr;
+};
+} // namespace
+
+void TaskContextStructManager::generateTaskContextStruct() {
+ if (privateDecls.empty())
+ return;
+ privateVarTypes.reserve(privateDecls.size());
+
+ for (omp::PrivateClauseOp &privOp : privateDecls) {
+ // Skip private variables which can safely be allocated and initialised
+ // inside of the task
+ if (!privatizerReadsSourceVariable(privOp))
+ continue;
+ Type mlirType = privOp.getType();
+ privateVarTypes.push_back(moduleTranslation.convertType(mlirType));
+ }
+
+ structTy = llvm::StructType::get(moduleTranslation.getLLVMContext(),
+ privateVarTypes);
+
+ llvm::DataLayout dataLayout =
+ builder.GetInsertBlock()->getModule()->getDataLayout();
+ llvm::Type *intPtrTy = builder.getIntPtrTy(dataLayout);
+ llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(structTy);
+
+ // Heap allocate the structure
+ structPtr = builder.CreateMalloc(intPtrTy, structTy, allocSize,
+ /*ArraySize=*/nullptr, /*MallocF=*/nullptr,
+ "omp.task.context_ptr");
+}
+
+void TaskContextStructManager::createGEPsToPrivateVars(
+ SmallVectorImpl<llvm::Value *> &llvmPrivateVars) {
+ if (!structPtr) {
+ assert(privateVarTypes.empty());
+ return;
+ }
+
+ // Create GEPs for each struct member and initialize llvmPrivateVars to point
+ llvmPrivateVars.reserve(privateVarTypes.size());
+ llvm::Value *zero = builder.getInt32(0);
+ unsigned i = 0;
+ for (auto privDecl : privateDecls) {
+ if (!privatizerReadsSourceVariable(privDecl)) {
+ // Handle this inside of the task so we don't pass unnessecary vars in
+ llvmPrivateVars.push_back(nullptr);
+ continue;
+ }
+ llvm::Value *iVal = builder.getInt32(i);
+ llvm::Value *gep = builder.CreateGEP(structTy, structPtr, {zero, iVal});
+ llvmPrivateVars.push_back(gep);
+ i += 1;
+ }
+}
+
+void TaskContextStructManager::freeStructPtr() {
+ if (!structPtr)
+ return;
+
+ llvm::IRBuilderBase::InsertPointGuard guard{builder};
+ // Ensure we don't put the call to free() after the terminator
+ builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
+ builder.CreateFree(structPtr);
+}
+
/// Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
static LogicalResult
convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
@@ -1747,6 +1867,8 @@ convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
mlirPrivateVars.reserve(privateBlockArgs.size());
llvmPrivateVars.reserve(privateBlockArgs.size());
collectPrivatizationDecls(taskOp, privateDecls);
+ TaskContextStructManager taskStructMgr{builder, moduleTranslation,
+ privateDecls};
for (mlir::Value privateVar : taskOp.getPrivateVars())
mlirPrivateVars.push_back(privateVar);
@@ -1796,27 +1918,54 @@ convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
// Allocate and initialize private variables
// TODO: package private variables up in a structure
builder.SetInsertPoint(initBlock->getTerminator());
- for (auto [privDecl, mlirPrivVar, blockArg] :
- llvm::zip_equal(privateDecls, mlirPrivateVars, privateBlockArgs)) {
- llvm::Type *llvmAllocType =
- moduleTranslation.convertType(privDecl.getType());
- // Allocations:
- builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
- llvm::Value *llvmPrivateVar = builder.CreateAlloca(
- llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
+ // Create task variable structure
+ llvm::SmallVector<llvm::Value *> privateVarAllocations;
+ taskStructMgr.generateTaskContextStruct();
+ // GEPs so that we can initialize the variables. Don't use these GEPs inside
+ // of the body otherwise it will be the GEP not the struct which is fowarded
+ // to the outlined function. GEPs forwarded in this way are passed in a
+ // stack-allocated (by OpenMPIRBuilder) structure which is not safe for tasks
+ // which may not be executed until after the current stack frame goes out of
+ // scope.
+ taskStructMgr.createGEPsToPrivateVars(privateVarAllocations);
+
+ for (auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] :
+ llvm::zip_equal(privateDecls, mlirPrivateVars, privateBlockArgs,
+ privateVarAllocations)) {
+ if (!llvmPrivateVarAlloc)
+ // to be handled inside the task
+ continue;
- // builder.SetInsertPoint(initBlock->getTerminator());
- auto err =
+ llvm::Expected<llvm::Value *> privateVarOrErr =
initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
- blockArg, llvmPrivateVar, llvmPrivateVars, initBlock);
- if (err)
+ blockArg, llvmPrivateVarAlloc, initBlock);
+ if (auto err = privateVarOrErr.takeError())
return handleError(std::move(err), *taskOp.getOperation());
+
+ llvm::IRBuilderBase::InsertPointGuard guard(builder);
+ builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
+
+ // TODO: this is a bit of a hack for Fortran character boxes
+ if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
+ !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
+ builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
+ // Load it so we have the value pointed to by the GEP
+ llvmPrivateVarAlloc = builder.CreateLoad(privateVarOrErr.get()->getType(),
+ llvmPrivateVarAlloc);
+ }
+ assert(llvmPrivateVarAlloc->getType() ==
+ moduleTranslation.convertType(blockArg.getType()));
+
+ // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body callback
+ // so that OpenMPIRBuilder doesn't try to pass each GEP address through a
+ // stack allocated structure.
}
// firstprivate copy region
if (failed(initFirstPrivateVars(builder, moduleTranslation, mlirPrivateVars,
- llvmPrivateVars, privateDecls, copyBlock)))
+ privateVarAllocations, privateDecls,
+ copyBlock)))
return llvm::failure();
// Set up for call to createTask()
@@ -1824,8 +1973,55 @@ convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
auto bodyCB = [&](InsertPointTy allocaIP,
InsertPointTy codegenIP) -> llvm::Error {
+ // Save the alloca insertion point on ModuleTranslation stack for use in
+ // nested regions.
+ LLVM::ModuleTranslation::SaveStack<OpenMPAllocaStackFrame> frame(
+ moduleTranslation, allocaIP);
+
// translate the body of the task:
builder.restoreIP(codegenIP);
+
+ llvm::BasicBlock *privInitBlock = nullptr;
+ for (auto [blockArg, privDecl, mlirPrivVar] :
+ llvm::zip_equal(privateBlockArgs, privateDecls, mlirPrivateVars)) {
+ if (privatizerReadsSourceVariable(privDecl))
+ // This is handled before the task executes
+ continue;
+
+ auto codegenInsertionPt = builder.saveIP();
+ llvm::Type *llvmAllocType =
+ moduleTranslation.convertType(privDecl.getType());
+ builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
+ llvm::Value *llvmPrivateVar = builder.CreateAlloca(
+ llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
+
+ llvm::Expected<llvm::Value *> privateVarOrError =
+ initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
+ blockArg, llvmPrivateVar, privInitBlock);
+ if (auto err = privateVarOrError.takeError())
+ return err;
+ moduleTranslation.mapValue(blockArg, privateVarOrError.get());
+ builder.restoreIP(codegenInsertionPt);
+ }
+
+ // Find and map the addresses of each variable within the task context
+ // structure
+ taskStructMgr.createGEPsToPrivateVars(llvmPrivateVars);
+ for (auto [blockArg, llvmPrivateVar] :
+ llvm::zip_equal(privateBlockArgs, llvmPrivateVars)) {
+ if (!llvmPrivateVar)
+ // This was handled above
+ continue;
+ // Fix broken pass-by-value case for Fortran character boxes
+ if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
+ llvmPrivateVar = builder.CreateLoad(
+ moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
+ }
+ assert(llvmPrivateVar->getType() ==
+ moduleTranslation.convertType(blockArg.getType()));
+ moduleTranslation.mapValue(blockArg, llvmPrivateVar);
+ }
+
auto continuationBlockOrError = convertOmpOpRegions(
taskOp.getRegion(), "omp.task.region", builder, moduleTranslation);
if (failed(handleError(continuationBlockOrError, *taskOp)))
@@ -1837,6 +2033,9 @@ convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
llvmPrivateVars, privateDecls)))
return llvm::make_error<PreviouslyReportedError>();
+ // Free heap allocated task context structure at the end of the task.
+ taskStructMgr.freeStructPtr();
+
return llvm::Error::success();
};
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 503ddf10c80cdc..ba05d97c9a4eda 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2795,9 +2795,10 @@ llvm.func @par_task_(%arg0: !llvm.ptr {fir.bindc_name = "a"}) {
// CHECK: call i32 @__kmpc_omp_task({{.*}}, ptr %[[TASK_ALLOC]])
// CHECK: define internal void @[[task_outlined_fn]](i32 %[[GLOBAL_TID_VAL:.*]], ptr %[[STRUCT_ARG:.*]])
// CHECK: %[[LOADED_STRUCT_PTR:.*]] = load ptr, ptr %[[STRUCT_ARG]], align 8
-// CHECK: %[[GEP_STRUCTARG:.*]] = getelementptr { ptr, ptr }, ptr %[[LOADED_STRUCT_PTR]], i32 0, i32 0
+// CHECK: %[[GEP_STRUCTARG:.*]] = getelementptr { ptr }, ptr %[[LOADED_STRUCT_PTR]], i32 0, i32 0
// CHECK: %[[LOADGEP_STRUCTARG:.*]] = load ptr, ptr %[[GEP_STRUCTARG]], align 8
-// CHECK: call void ({{.*}}) @__kmpc_fork_call({{.*}}, ptr @[[parallel_outlined_fn:.+]], ptr %[[LOADGEP_STRUCTARG]])
+// CHEKC: %[[NEW_STRUCTARG:.*]] = alloca { ptr }, align 8
+// CHECK: call void ({{.*}}) @__kmpc_fork_call({{.*}}, ptr @[[parallel_outlined_fn:.+]],
// CHECK: define internal void @[[parallel_outlined_fn]]
// -----
@@ -2825,10 +2826,11 @@ llvm.func @task(%arg0 : !llvm.ptr) {
// CHECK-LABEL: @task
// CHECK-SAME: (ptr %[[ARG:.*]])
// CHECK: %[[STRUCT_ARG:.*]] = alloca { ptr }, align 8
-// CHECK: %[[OMP_PRIVATE_ALLOC:.*]] = alloca i32, align 4
// ...
// CHECK: br label %omp.private.init
// CHECK: omp.private.init:
+// CHECK: %[[OMP_TASK_CONTEXT_PTR:.*]] = tail call ptr @malloc(
+// CHECK: %[[OMP_PRIVATE_ALLOC:.*]] = getelementptr { i32 }, ptr %[[OMP_TASK_CONTEXT_PTR]]
// CHECK: br label %omp.private.copy1
// CHECK: omp.private.copy1:
// CHECK: %[[LOADED:.*]] = load i32, ptr %[[ARG]], align 4
@@ -2846,12 +2848,13 @@ llvm.func @task(%arg0 : !llvm.ptr) {
// CHECK: %[[VAL_14:.*]] = load ptr, ptr %[[VAL_13]], align 8
// CHECK: br label %task.body
// CHECK: task.body: ; preds = %task.alloca
+// CHECK: %[[VAL_15:.*]] = getelementptr { i32 }, ptr %[[VAL_14]], i32 0, i32 0
// CHECK: br label %omp.task.region
// CHECK: omp.task.region: ; preds = %task.body
-// CHECK: call void @foo(ptr %[[VAL_14]])
+// CHECK: call void @foo(ptr %[[VAL_15]])
// CHECK: br label %omp.region.cont
// CHECK: omp.region.cont: ; preds = %omp.task.region
-// CHECK: call void @destroy(ptr %[[VAL_14]])
+// CHECK: call void @destroy(ptr %[[VAL_15]])
// CHECK: br label %task.exit.exitStub
// CHECK: task.exit.exitStub: ; preds = %omp.region.cont
// CHECK: ret void
diff --git a/mlir/test/Target/LLVMIR/openmp-task-privatization.mlir b/mlir/test/Target/LLVMIR/openmp-task-privatization.mlir
new file mode 100644
index 00000000000000..af469460f3cee0
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-task-privatization.mlir
@@ -0,0 +1,81 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+omp.private {type = private} @privatizer : i32
+
+omp.private {type = firstprivate} @firstprivatizer : i32 copy {
+^bb0(%arg0: !llvm.ptr, %arg1: !llvm.ptr):
+ %0 = llvm.load %arg0 : !llvm.ptr -> i32
+ llvm.store %0, %arg1 : i32, !llvm.ptr
+ omp.yield(%arg1 : !llvm.ptr)
+}
+
+llvm.func @task_privatization_test() {
+ %c0 = llvm.mlir.constant(0: i32) : i32
+ %c1 = llvm.mlir.constant(1: i32) : i32
+ %0 = llvm.alloca %c1 x i32 : (i32) -> !llvm.ptr
+ %1 = llvm.alloca %c1 x i32 : (i32) -> !llvm.ptr
+ llvm.store %c0, %0 : i32, !llvm.ptr
+ llvm.store %c1, %1 : i32, !llvm.ptr
+
+ omp.task private(@privatizer %0 -> %arg0, @firstprivatizer %1 -> %arg1 : !llvm.ptr, !llvm.ptr) {
+ %2 = llvm.load %arg1 : !llvm.ptr -> i32
+ llvm.store %2, %arg0 : i32, !llvm.ptr
+ omp.terminator
+ }
+ llvm.return
+}
+
+// CHECK: define void @task_privatization_test()
+// CHECK: %[[STRUCT_ARG:.*]] = alloca { ptr }, align 8
+// CHECK: %[[VAL_0:.*]] = alloca i32, align 4
+// CHECK: %[[VAL_1:.*]] = alloca i32, align 4
+// CHECK: store i32 0, ptr %[[VAL_0]], align 4
+// CHECK: store i32 1, ptr %[[VAL_1]], align 4
+// CHECK: br label %entry
+// CHECK: entry:
+// CHECK: br label %omp.private.init
+// CHECK: omp.private.init:
+// CHECK: %[[VAL_5:.*]] = tail call ptr @malloc(i64 ptrtoint (ptr getelementptr ([[STRUCT_KMP_PRIVATES_T:.*]]...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/125307
More information about the llvm-branch-commits
mailing list