https://github.com/tblah updated https://github.com/llvm/llvm-project/pull/125307
>From afa9026eefb6c9cd613ed021a92e159f93c3667c Mon Sep 17 00:00:00 2001 From: Tom Eccles <tom.ecc...@arm.com> Date: Fri, 24 Jan 2025 17:32:41 +0000 Subject: [PATCH 1/2] [mlir][OpenMP] Pack task private variables into a heap-allocated context struct 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. --- .../OpenMP/OpenMPToLLVMIRTranslation.cpp | 204 +++++++++++++++--- mlir/test/Target/LLVMIR/openmp-llvm.mlir | 5 +- .../LLVMIR/openmp-task-privatization.mlir | 82 +++++++ 3 files changed, 254 insertions(+), 37 deletions(-) create mode 100644 mlir/test/Target/LLVMIR/openmp-task-privatization.mlir diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp index 8a9a69cefad8ee1..5c4deab492c8390 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,97 @@ buildDependData(std::optional<ArrayAttr> dependKinds, OperandRange dependVars, } } +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) + : builder{builder}, moduleTranslation{moduleTranslation} {} + + /// 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. + void + generateTaskContextStruct(MutableArrayRef<omp::PrivateClauseOp> privateDecls); + + /// Create GEPs to access each member of the structure representing a private + /// variable, adding them to llvmPrivateVars. + 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; + + /// 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( + MutableArrayRef<omp::PrivateClauseOp> privateDecls) { + if (privateDecls.empty()) + return; + privateVarTypes.reserve(privateDecls.size()); + + for (omp::PrivateClauseOp &privOp : privateDecls) { + 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); + for (auto [i, eleTy] : llvm::enumerate(privateVarTypes)) { + llvm::Value *iVal = builder.getInt32(i); + llvm::Value *gep = builder.CreateGEP(structTy, structPtr, {zero, iVal}); + llvmPrivateVars.push_back(gep); + } +} + +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, @@ -1744,6 +1835,7 @@ convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder, SmallVector<mlir::Value> mlirPrivateVars; SmallVector<llvm::Value *> llvmPrivateVars; SmallVector<omp::PrivateClauseOp> privateDecls; + TaskContextStructManager taskStructMgr{builder, moduleTranslation}; mlirPrivateVars.reserve(privateBlockArgs.size()); llvmPrivateVars.reserve(privateBlockArgs.size()); collectPrivatizationDecls(taskOp, privateDecls); @@ -1796,27 +1888,50 @@ 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"); - - // builder.SetInsertPoint(initBlock->getTerminator()); - auto err = + // Create task variable structure + llvm::SmallVector<llvm::Value *> privateVarAllocations; + taskStructMgr.generateTaskContextStruct(privateDecls); + // 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)) { + 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() @@ -1826,6 +1941,22 @@ convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder, InsertPointTy codegenIP) -> llvm::Error { // translate the body of the task: builder.restoreIP(codegenIP); + + // 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)) { + // 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 +1968,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 503ddf10c80cdcb..9943198d890cec7 100644 --- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir +++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir @@ -2846,12 +2846,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 000000000000000..39089fb87e0ecf6 --- /dev/null +++ b/mlir/test/Target/LLVMIR/openmp-task-privatization.mlir @@ -0,0 +1,82 @@ +// 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:.*]], ptr null, i32 1) to i64)) +// CHECK: %[[VAL_7:.*]] = getelementptr { i32, i32 }, ptr %[[VAL_5]], i32 0, i32 0 +// CHECK: %[[VAL_8:.*]] = getelementptr { i32, i32 }, ptr %[[VAL_5]], i32 0, i32 1 +// CHECK: br label %omp.private.copy1 +// CHECK: omp.private.copy1: +// CHECK: %[[VAL_10:.*]] = load i32, ptr %[[VAL_1]], align 4 +// CHECK: store i32 %[[VAL_10]], ptr %[[VAL_8]], align 4 +// CHECK: br label %omp.private.copy +// CHECK: omp.private.copy: +// CHECK: br label %omp.task.start +// CHECK: omp.task.start: +// CHECK: br label %codeRepl +// CHECK: codeRepl: +// CHECK: %[[GEP_OMP_TASK_CONTEXT_PTR:.*]] = getelementptr { ptr }, ptr %[[STRUCT_ARG]], i32 0, i32 0 +// CHECK: store ptr %[[VAL_5]], ptr %[[GEP_OMP_TASK_CONTEXT_PTR]], align 8 +// CHECK: %[[VAL_14:.*]] = call i32 @__kmpc_global_thread_num(ptr @1) +// CHECK: %[[VAL_15:.*]] = call ptr @__kmpc_omp_task_alloc(ptr @1, i32 %[[VAL_14]], i32 1, i64 40, i64 8, ptr @task_privatization_test..omp_par) +// CHECK: %[[ALLOCATED_TASK_STRUCT:.*]] = load ptr, ptr %[[VAL_15]], align 8 +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %[[ALLOCATED_TASK_STRUCT]], ptr align 1 %[[STRUCT_ARG]], i64 8, i1 false) +// CHECK: %[[VAL_16:.*]] = call i32 @__kmpc_omp_task(ptr @1, i32 %[[VAL_14]], ptr %[[VAL_15]]) +// CHECK: br label %[[VAL_17:.*]] +// CHECK: task.exit: +// CHECK: ret void + +// CHECK-LABEL: define internal void @task_privatization_test..omp_par( +// CHECK-SAME: i32 %[[GLOBAL_TID_VAL:.*]], ptr %[[OMP_TASK_CONTEXT_PTR_PTR_PTR_PTR:.*]]) +// CHECK: task.alloca: +// CHECK: %[[OMP_TASK_CONEXT_PTR_PTR_PTR:.*]] = load ptr, ptr %[[OMP_TASK_CONTEXT_PTR_PTR_PTR_PTR]], align 8 +// CHECK: %[[OMP_TASK_CONTEXT_PTR_PTR:.*]] = getelementptr { ptr }, ptr %[[OMP_TASK_CONTEXT_PTR_PTR_PTR:.*]], i32 0, i32 0 +// CHECK: %[[OMP_TASK_CONTEXT_PTR:.*]] = load ptr, ptr %[[OMP_TASK_CONTEXT_PTR_PTR:.*]], align 8 +// CHECK: br label %[[VAL_18:.*]] +// CHECK: task.body: ; preds = %[[VAL_19:.*]] +// CHECK: %[[VAL_20:.*]] = getelementptr { i32, i32 }, ptr %[[OMP_TASK_CONTEXT_PTR]], i32 0, i32 0 +// CHECK: %[[VAL_22:.*]] = getelementptr { i32, i32 }, ptr %[[OMP_TASK_CONTEXT_PTR]], i32 0, i32 1 +// CHECK: br label %[[VAL_23:.*]] +// CHECK: omp.task.region: ; preds = %[[VAL_18]] +// CHECK: %[[VAL_24:.*]] = load i32, ptr %[[VAL_22]], align 4 +// CHECK: store i32 %[[VAL_24]], ptr %[[VAL_20]], align 4 +// CHECK: br label %[[VAL_25:.*]] +// CHECK: omp.region.cont: ; preds = %[[VAL_23]] +// CHECK: tail call void @free(ptr %[[OMP_TASK_CONTEXT_PTR]]) +// CHECK: br label %[[VAL_26:.*]] +// CHECK: task.exit.exitStub: ; preds = %[[VAL_25]] +// CHECK: ret void + >From f8a0b7181c86046f2d833eff97c4cd88db656285 Mon Sep 17 00:00:00 2001 From: Tom Eccles <tom.ecc...@arm.com> Date: Mon, 3 Feb 2025 11:43:32 +0000 Subject: [PATCH 2/2] Update after code review on previous patch --- mlir/test/Target/LLVMIR/openmp-llvm.mlir | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir index 9943198d890cec7..ab50e49f870421a 100644 --- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir +++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir @@ -2825,10 +2825,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 _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits