From 6549dafb0e68783bdc233f075847997b7c9528d3 Mon Sep 17 00:00:00 2001 From: ergawy Date: Sun, 17 Nov 2024 23:22:20 -0600 Subject: [PATCH] [mlir][OpenMP] - MLIR to LLVMIR translation support for delayed privatization of allocatables in `omp.target` ops This PR adds support to translate the `private` clause from MLIR to LLVMIR when used on allocatables in the context of an `omp.target` op. --- .../OpenMP/MapsForPrivatizedSymbols.cpp | 9 +- mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 9 ++ .../OpenMP/OpenMPToLLVMIRTranslation.cpp | 134 ++++++++++++++++-- .../openmp-target-multiple-private.mlir | 80 +++++++++++ .../openmp-target-private-allocatable.mlir | 64 +++++++++ .../Target/LLVMIR/openmp-target-private.mlir | 89 ++++++++++++ mlir/test/Target/LLVMIR/openmp-todo.mlir | 18 --- 7 files changed, 368 insertions(+), 35 deletions(-) create mode 100644 mlir/test/Target/LLVMIR/openmp-target-multiple-private.mlir create mode 100644 mlir/test/Target/LLVMIR/openmp-target-private-allocatable.mlir diff --git a/flang/lib/Optimizer/OpenMP/MapsForPrivatizedSymbols.cpp b/flang/lib/Optimizer/OpenMP/MapsForPrivatizedSymbols.cpp index d2c814cc958ddf5..c990bebcabde427 100644 --- a/flang/lib/Optimizer/OpenMP/MapsForPrivatizedSymbols.cpp +++ b/flang/lib/Optimizer/OpenMP/MapsForPrivatizedSymbols.cpp @@ -49,13 +49,6 @@ class MapsForPrivatizedSymbolsPass : public flangomp::impl::MapsForPrivatizedSymbolsPassBase< MapsForPrivatizedSymbolsPass> { - bool privatizerNeedsMap(omp::PrivateClauseOp &privatizer) { - Region &allocRegion = privatizer.getAllocRegion(); - Value blockArg0 = allocRegion.getArgument(0); - if (blockArg0.use_empty()) - return false; - return true; - } omp::MapInfoOp createMapInfo(Location loc, Value var, fir::FirOpBuilder &builder) { uint64_t mapTypeTo = static_cast< @@ -134,7 +127,7 @@ class MapsForPrivatizedSymbolsPass omp::PrivateClauseOp privatizer = SymbolTable::lookupNearestSymbolFrom( targetOp, privatizerName); - if (!privatizerNeedsMap(privatizer)) { + if (!privatizer.needsMap()) { privVarMapIdx.push_back(-1); continue; } diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td index f6c7f19fffddf9e..11ccc5855eb66a6 100644 --- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td +++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td @@ -135,6 +135,15 @@ def PrivateClauseOp : OpenMP_Op<"private", [IsolatedFromAbove, RecipeInterface]> auto ®ion = getDeallocRegion(); return region.empty() ? nullptr : region.getArgument(0); } + + /// needsMap returns true if the value being privatized should additionally + /// be mapped to the target region using a MapInfoOp. This is most common + /// when an allocatable is privatized. In such cases, the descriptor is used + /// in privatization and needs to be mapped on to the device. + bool needsMap() { + Value blockArg0 = getAllocRegion().getArgument(0); + return !blockArg0.use_empty(); + } }]; let hasRegionVerifier = 1; diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp index 35b0633a04a3522..8ad18ed82585767 100644 --- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp +++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp @@ -300,10 +300,6 @@ static LogicalResult checkImplementationStatus(Operation &op) { if (privatizer.getDataSharingType() == omp::DataSharingClauseType::FirstPrivate) result = todo("firstprivate"); - - if (!privatizer.getDeallocRegion().empty()) - result = op.emitError("not yet implemented: privatization of " - "structures in omp.target operation"); } } checkThreadLimit(op, result); @@ -3810,6 +3806,43 @@ createDeviceArgumentAccessor(MapInfoData &mapData, llvm::Argument &arg, return builder.saveIP(); } +/// Return the llvm::Value * corresponding to the `privateVar` that +/// is being privatized. It isn't always as simple as looking up +/// moduleTranslation with privateVar. For instance, in case of +/// an allocatable, the descriptor for the allocatable is privatized. +/// This descriptor is mapped using an MapInfoOp. So, this function +/// will return a pointer to the llvm::Value corresponding to the +/// block argument for the mapped descriptor. +static llvm::Value * +findHostAssociatedValue(Value privateVar, omp::TargetOp targetOp, + llvm::DenseMap &mappedPrivateVars, + llvm::IRBuilderBase &builder, + LLVM::ModuleTranslation &moduleTranslation) { + if (mappedPrivateVars.contains(privateVar)) { + int blockArgIndex = mappedPrivateVars[privateVar]; + Value blockArg = targetOp.getRegion().getArgument(blockArgIndex); + Type privVarType = privateVar.getType(); + Type blockArgType = blockArg.getType(); + assert(isa(blockArgType) && + "A block argument corresponding to a mapped var should have " + "!llvm.ptr type"); + + if (privVarType == blockArg.getType()) + return moduleTranslation.lookupValue(blockArg); + + if (!isa(privVarType)) { + // This typically happens when the privatized type is lowered from + // boxchar and gets lowered to !llvm.struct<(ptr, i64)>. That is the + // struct/pair is passed by value. But, mapped values are passed only as + // pointers, so before we privatize, we must load the pointer. + llvm::Value *load = + builder.CreateLoad(moduleTranslation.convertType(privVarType), + moduleTranslation.lookupValue(blockArg)); + return load; + } + } + return moduleTranslation.lookupValue(privateVar); +} static LogicalResult convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation) { @@ -3821,6 +3854,19 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, bool isTargetDevice = ompBuilder->Config.isTargetDevice(); auto parentFn = opInst.getParentOfType(); auto &targetRegion = targetOp.getRegion(); + // Holds the private vars that have been mapped along with + // the block argument that corresponds to the MapInfoOp + // corresponding to the private var in question. + // So, for instance + // + // %10 = omp.map.info var_ptr(%6#0 : !fir.ref>>, ..) + // omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1) + // + // Then, %10 has been created so that the descriptor can be used by the + // privatizer + // @box.privatizer on the device side. Here we'd record {%6#0, 0} in the + // mappedPrivateVars map. + llvm::DenseMap mappedPrivateVars; DataLayout dl = DataLayout(opInst.getParentOfType()); SmallVector mapVars = targetOp.getMapVars(); ArrayRef mapBlockArgs = @@ -3832,6 +3878,55 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, bool isOffloadEntry = isTargetDevice || !ompBuilder->Config.TargetTriples.empty(); + // For some private variables, the MapsForPrivatizedVariablesPass + // creates MapInfoOp instances. Go through the private variables and + // the mapped variables so that during codegeneration we are able + // to quickly look up the corresponding map variable, if any for each + // private variable. + if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) { + auto argIface = llvm::cast(*targetOp); + OperandRange privateVars = targetOp.getPrivateVars(); + std::optional privateSyms = targetOp.getPrivateSyms(); + std::optional privateMapIndices = + targetOp.getPrivateMapsAttr(); + + for (auto [privVarIdx, privVarSymPair] : + llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) { + auto privVar = std::get<0>(privVarSymPair); + auto privSym = std::get<1>(privVarSymPair); + + SymbolRefAttr privatizerName = llvm::cast(privSym); + omp::PrivateClauseOp privatizer = + findPrivatizer(targetOp, privatizerName); + + if (!privatizer.needsMap()) + continue; + + mlir::Value mappedValue = + targetOp.getMappedValueForPrivateVar(privVarIdx); + assert(mappedValue); + + // The MapInfoOp defining the map var isn't really needed later. + // So, we don't store it in any datastructure. Instead, we just + // do some sanity checks on it right now. + auto mapInfoOp = mappedValue.getDefiningOp(); + Type varType = mapInfoOp.getVarType(); + + // Check #1: Check that the type of the private variable matches + // the type of the variable being mapped. + if (!isa(privVar.getType())) + assert( + varType == privVar.getType() && + "Type of private var doesn't match the type of the mapped value"); + + // Ok, only 1 sanity check for now. + // Record the index of the block argument corresponding to this + // mapvar. + mappedPrivateVars.insert({privVar, argIface.getMapBlockArgsStart() + + (*privateMapIndices)[privVarIdx]}); + } + } + using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy; auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP) -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy { @@ -3858,9 +3953,10 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, moduleTranslation.lookupValue(mapInfoOp.getVarPtr()); moduleTranslation.mapValue(arg, mapOpValue); } - // Do privatization after moduleTranslation has already recorded // mapped values. + SmallVector llvmPrivateVars; + SmallVector privateCleanupRegions; if (!targetOp.getPrivateVars().empty()) { builder.restoreIP(allocaIP); @@ -3876,11 +3972,13 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, omp::PrivateClauseOp privatizer = findPrivatizer(&opInst, privSym); assert(privatizer.getDataSharingType() != omp::DataSharingClauseType::FirstPrivate && - privatizer.getDeallocRegion().empty() && "unsupported privatizer"); - moduleTranslation.mapValue(privatizer.getAllocMoldArg(), - moduleTranslation.lookupValue(privVar)); Region &allocRegion = privatizer.getAllocRegion(); + BlockArgument allocRegionArg = allocRegion.getArgument(0); + moduleTranslation.mapValue( + allocRegionArg, + findHostAssociatedValue(privVar, targetOp, mappedPrivateVars, + builder, moduleTranslation)); SmallVector yieldedValues; if (failed(inlineConvertOmpRegions( allocRegion, "omp.targetop.privatizer", builder, @@ -3889,7 +3987,12 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, "failed to inline `alloc` region of `omp.private`"); } assert(yieldedValues.size() == 1); - moduleTranslation.mapValue(privBlockArg, yieldedValues.front()); + llvm::Value *llvmReplacementValue = yieldedValues.front(); + moduleTranslation.mapValue(privBlockArg, llvmReplacementValue); + if (!privatizer.getDeallocRegion().empty()) { + llvmPrivateVars.push_back(llvmReplacementValue); + privateCleanupRegions.push_back(&privatizer.getDeallocRegion()); + } moduleTranslation.forgetMapping(allocRegion); builder.restoreIP(builder.saveIP()); } @@ -3901,6 +4004,19 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, return exitBlock.takeError(); builder.SetInsertPoint(*exitBlock); + if (!llvmPrivateVars.empty()) { + assert(llvmPrivateVars.size() == privateCleanupRegions.size() && + "Number of private variables needing cleanup not equal to number" + "of privatizers with dealloc regions"); + if (failed(inlineOmpRegionCleanup( + privateCleanupRegions, llvmPrivateVars, moduleTranslation, + builder, "omp.targetop.private.cleanup", + /*shouldLoadCleanupRegionArg=*/false))) { + return llvm::createStringError( + "failed to inline `dealloc` region of `omp.private` " + "op in the target region"); + } + } return builder.saveIP(); }; diff --git a/mlir/test/Target/LLVMIR/openmp-target-multiple-private.mlir b/mlir/test/Target/LLVMIR/openmp-target-multiple-private.mlir new file mode 100644 index 000000000000000..c632a0ee42f8a3b --- /dev/null +++ b/mlir/test/Target/LLVMIR/openmp-target-multiple-private.mlir @@ -0,0 +1,80 @@ +// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s + +llvm.func @dealloc_foo_0(!llvm.ptr) + +omp.private {type = private} @box.heap_privatizer0 : !llvm.ptr alloc { +^bb0(%arg0: !llvm.ptr): + %0 = llvm.mlir.constant(1 : i32) : i32 + %7 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> : (i32) -> !llvm.ptr + omp.yield(%7 : !llvm.ptr) +} dealloc { +^bb0(%arg0: !llvm.ptr): + llvm.call @dealloc_foo_0(%arg0) : (!llvm.ptr) -> () + omp.yield +} + +llvm.func @alloc_foo_1(!llvm.ptr) +llvm.func @dealloc_foo_1(!llvm.ptr) + +omp.private {type = private} @box.heap_privatizer1 : !llvm.ptr alloc { +^bb0(%arg0: !llvm.ptr): + %0 = llvm.mlir.constant(1 : i32) : i32 + %7 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> : (i32) -> !llvm.ptr + llvm.call @alloc_foo_1(%arg0) : (!llvm.ptr) -> () + omp.yield(%7 : !llvm.ptr) +} dealloc { +^bb0(%arg0: !llvm.ptr): + llvm.call @dealloc_foo_1(%arg0) : (!llvm.ptr) -> () + omp.yield +} + +llvm.func @target_allocatable_(%arg0: !llvm.ptr {fir.bindc_name = "lb"}, %arg1: !llvm.ptr {fir.bindc_name = "ub"}, %arg2: !llvm.ptr {fir.bindc_name = "l"}) attributes {fir.internal_name = "_QPtarget_allocatable"} { + %6 = llvm.mlir.constant(1 : i64) : i64 + %7 = llvm.alloca %6 x i32 {bindc_name = "mapped_var"} : (i64) -> !llvm.ptr + %13 = llvm.alloca %6 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {bindc_name = "alloc_var0"} : (i64) -> !llvm.ptr + %14 = llvm.alloca %6 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {bindc_name = "alloc_var1"} : (i64) -> !llvm.ptr + %53 = omp.map.info var_ptr(%7 : !llvm.ptr, i32) map_clauses(implicit, exit_release_or_enter_alloc) capture(ByCopy) -> !llvm.ptr {name = "mapped_var"} + %54 = omp.map.info var_ptr(%13 : !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>) map_clauses(to) capture(ByRef) -> !llvm.ptr + %55 = omp.map.info var_ptr(%14 : !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>) map_clauses(to) capture(ByRef) -> !llvm.ptr + omp.target map_entries(%53 -> %arg3, %54 -> %arg4, %55 ->%arg5 : !llvm.ptr, !llvm.ptr, !llvm.ptr) private(@box.heap_privatizer0 %13 -> %arg6 [map_idx=1], @box.heap_privatizer1 %14 -> %arg7 [map_idx=2]: !llvm.ptr, !llvm.ptr) { + llvm.call @use_private_var0(%arg6) : (!llvm.ptr) -> () + llvm.call @use_private_var1(%arg7) : (!llvm.ptr) -> () + omp.terminator + } + llvm.return +} + + +llvm.func @use_private_var0(!llvm.ptr) -> () +llvm.func @use_private_var1(!llvm.ptr) -> () + +// The first set of checks ensure that we are calling the offloaded function +// with the right arguments, especially the second argument which needs to +// be a memory reference to the descriptor for the privatized allocatable +// CHECK: define void @target_allocatable_ +// CHECK-NOT: define internal void +// CHECK: %[[DESC_ALLOC0:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }, i64 1 +// CHECK: %[[DESC_ALLOC1:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }, i64 1 +// CHECK: call void @__omp_offloading_[[OFFLOADED_FUNCTION:.*]](ptr {{[^,]+}}, +// CHECK-SAME: ptr %[[DESC_ALLOC0]], ptr %[[DESC_ALLOC1]]) + +// CHECK: define internal void @__omp_offloading_[[OFFLOADED_FUNCTION]] +// CHECK-SAME: (ptr {{[^,]+}}, ptr %[[DESCRIPTOR_ARG0:[^,]+]], +// CHECK-SAME: ptr %[[DESCRIPTOR_ARG1:.*]]) { + +// `var0` privatrizer `alloc` +// CHECK: %[[PRIV_DESC0:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 } + +// `var1` privatrizer `alloc` +// CHECK: %[[PRIV_DESC1:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 } +// CHECK: call void @alloc_foo_1(ptr %[[DESCRIPTOR_ARG1]]) + +// target op body +// CHECK: call void @use_private_var0(ptr %[[PRIV_DESC0]] +// CHECK: call void @use_private_var1(ptr %[[PRIV_DESC1]] + +// `var0` privatrizer `dealloc` +// CHECK: call void @dealloc_foo_0(ptr %[[PRIV_DESC0]]) + +// `var1` privatrizer `dealloc` +// CHECK: call void @dealloc_foo_1(ptr %[[PRIV_DESC1]]) diff --git a/mlir/test/Target/LLVMIR/openmp-target-private-allocatable.mlir b/mlir/test/Target/LLVMIR/openmp-target-private-allocatable.mlir new file mode 100644 index 000000000000000..88b4a6a63c7eb96 --- /dev/null +++ b/mlir/test/Target/LLVMIR/openmp-target-private-allocatable.mlir @@ -0,0 +1,64 @@ +// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s + +llvm.func @alloc_foo_1(!llvm.ptr) +llvm.func @dealloc_foo_1(!llvm.ptr) + +omp.private {type = private} @box.heap_privatizer : !llvm.ptr alloc { +^bb0(%arg0: !llvm.ptr): + %0 = llvm.mlir.constant(1 : i32) : i32 + %7 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> : (i32) -> !llvm.ptr + llvm.call @alloc_foo_1(%arg0) : (!llvm.ptr) -> () + omp.yield(%7 : !llvm.ptr) +} dealloc { +^bb0(%arg0: !llvm.ptr): + llvm.call @dealloc_foo_1(%arg0) : (!llvm.ptr) -> () + omp.yield +} + +llvm.func @target_allocatable_(%arg0: !llvm.ptr {fir.bindc_name = "lb"}, %arg1: !llvm.ptr {fir.bindc_name = "ub"}, %arg2: !llvm.ptr {fir.bindc_name = "l"}) attributes {fir.internal_name = "_QPtarget_allocatable"} { + %0 = llvm.mlir.constant(1 : i32) : i32 + %1 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {alignment = 8 : i64} : (i32) -> !llvm.ptr + %3 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {alignment = 8 : i64} : (i32) -> !llvm.ptr + %4 = llvm.mlir.constant(1 : i64) : i64 + %5 = llvm.alloca %4 x f32 {bindc_name = "real_var"} : (i64) -> !llvm.ptr + %7 = llvm.alloca %4 x i32 {bindc_name = "mapped_var"} : (i64) -> !llvm.ptr + %9 = llvm.alloca %4 x !llvm.struct<(f32, f32)> {bindc_name = "comp_var"} : (i64) -> !llvm.ptr + %11 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {alignment = 8 : i64} : (i32) -> !llvm.ptr + %13 = llvm.alloca %4 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {bindc_name = "alloc_var"} : (i64) -> !llvm.ptr + %39 = llvm.load %arg2 : !llvm.ptr -> i64 + %52 = llvm.alloca %39 x f32 {bindc_name = "real_arr"} : (i64) -> !llvm.ptr + %53 = omp.map.info var_ptr(%7 : !llvm.ptr, i32) map_clauses(implicit, exit_release_or_enter_alloc) capture(ByCopy) -> !llvm.ptr {name = "mapped_var"} + %54 = omp.map.info var_ptr(%13 : !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>) map_clauses(to) capture(ByRef) -> !llvm.ptr + omp.target map_entries(%53 -> %arg3, %54 -> %arg4 : !llvm.ptr, !llvm.ptr) private(@box.heap_privatizer %13 -> %arg5 [map_idx=1] : !llvm.ptr) { + llvm.call @use_private_var(%arg5) : (!llvm.ptr) -> () + omp.terminator + } + llvm.return +} + +llvm.func @use_private_var(!llvm.ptr) -> () + +llvm.func @_FortranAAssign(!llvm.ptr, !llvm.ptr, !llvm.ptr, i32) -> !llvm.struct<()> attributes {fir.runtime, sym_visibility = "private"} + +// The first set of checks ensure that we are calling the offloaded function +// with the right arguments, especially the second argument which needs to +// be a memory reference to the descriptor for the privatized allocatable +// CHECK: define void @target_allocatable_ +// CHECK-NOT: define internal void +// CHECK: %[[DESC_ALLOC:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }, i64 1 +// CHECK: call void @__omp_offloading_[[OFFLOADED_FUNCTION:.*]](ptr {{[^,]+}}, +// CHECK-SAME: ptr %[[DESC_ALLOC]]) + +// The second set of checks ensure that to allocate memory for the +// allocatable, we are, in fact, using the memory reference of the descriptor +// passed as the second argument to the offloaded function. +// CHECK: define internal void @__omp_offloading_[[OFFLOADED_FUNCTION]] +// CHECK-SAME: (ptr {{[^,]+}}, ptr %[[DESCRIPTOR_ARG:.*]]) { +// CHECK: %[[DESC_TO_DEALLOC:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 } +// CHECK: call void @alloc_foo_1(ptr %[[DESCRIPTOR_ARG]]) + + +// CHECK: call void @use_private_var(ptr %[[DESC_TO_DEALLOC]] + +// Now, check the deallocation of the private var. +// CHECK: call void @dealloc_foo_1(ptr %[[DESC_TO_DEALLOC]]) diff --git a/mlir/test/Target/LLVMIR/openmp-target-private.mlir b/mlir/test/Target/LLVMIR/openmp-target-private.mlir index e41b18f593efe87..40bccd324a75a89 100644 --- a/mlir/test/Target/LLVMIR/openmp-target-private.mlir +++ b/mlir/test/Target/LLVMIR/openmp-target-private.mlir @@ -94,3 +94,92 @@ llvm.func @target_op_private_multi_block(%arg0: !llvm.ptr) { // CHECK: %[[PRIV_ALLOC:.*]] = alloca float, i32 %[[ONE]], align 4 // CHECK: %[[PHI_ALLOCA:.*]] = phi ptr [ %[[PRIV_ALLOC]], {{.*}} ] // CHECK: %[[RESULT:.*]] = load float, ptr %[[PHI_ALLOCA]], align 4 + +// Descriptors are needed for CHARACTER arrays and their type is +// !fir.boxchar. When such arrays are used in the private construct, the +// privatizer takes a !fir.boxchar as input. This type is lowered to +// !llvm.struct<(ptr, i64)>. This is unique because with other types of data, +// typically, the privatizer funtion takes a !llvm.ptr. Now, on the host side, +// we map the descriptor using the map clause of the omp.target op. map clauses +// take only !llvm.ptr types. This means, we have a case where the descriptor is +// mapped by its pointer whereas the privatizer function expects the descriptor +// by value. So, we have this test to ensure that the compiler correctly loads +// from the mapped pointer before passing that to the privatizer function. +omp.private {type = private} @_QFtarget_boxcharEchar_var_private_boxchar_c8xU : !llvm.struct<(ptr, i64)> alloc { +^bb0(%arg0: !llvm.struct<(ptr, i64)>): + %0 = llvm.extractvalue %arg0[0] : !llvm.struct<(ptr, i64)> + %1 = llvm.extractvalue %arg0[1] : !llvm.struct<(ptr, i64)> + %2 = llvm.mlir.constant(1 : i64) : i64 + %3 = llvm.alloca %1 x i8 {bindc_name = "char_var", pinned} : (i64) -> !llvm.ptr + %4 = llvm.mlir.undef : !llvm.struct<(ptr, i64)> + %5 = llvm.insertvalue %3, %4[0] : !llvm.struct<(ptr, i64)> + %6 = llvm.insertvalue %1, %5[1] : !llvm.struct<(ptr, i64)> + omp.yield(%6 : !llvm.struct<(ptr, i64)>) +} +llvm.func @target_boxchar_(%arg0: !llvm.ptr {fir.bindc_name = "l"}) attributes {fir.internal_name = "_QPtarget_boxchar"} { + %0 = llvm.mlir.constant(1 : i64) : i64 + %1 = llvm.alloca %0 x i32 {bindc_name = "mapped_var"} : (i64) -> !llvm.ptr + %3 = llvm.alloca %0 x !llvm.struct<(ptr, i64)> : (i64) -> !llvm.ptr + %4 = llvm.mlir.constant(0 : i64) : i64 + %5 = llvm.load %arg0 : !llvm.ptr -> i64 + %6 = llvm.icmp "sgt" %5, %4 : i64 + %7 = llvm.select %6, %5, %4 : i1, i64 + %9 = llvm.alloca %7 x i8 {bindc_name = "char_var"} : (i64) -> !llvm.ptr + %10 = llvm.mlir.undef : !llvm.struct<(ptr, i64)> + %11 = llvm.insertvalue %9, %10[0] : !llvm.struct<(ptr, i64)> + %12 = llvm.insertvalue %7, %11[1] : !llvm.struct<(ptr, i64)> + %13 = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(implicit, exit_release_or_enter_alloc) capture(ByCopy) -> !llvm.ptr {name = "mapped_var"} + llvm.store %12, %3 : !llvm.struct<(ptr, i64)>, !llvm.ptr + %14 = omp.map.info var_ptr(%3 : !llvm.ptr, !llvm.struct<(ptr, i64)>) map_clauses(to) capture(ByRef) -> !llvm.ptr + omp.target map_entries(%13 -> %arg1, %14 -> %arg2 : !llvm.ptr, !llvm.ptr) private(@_QFtarget_boxcharEchar_var_private_boxchar_c8xU %12 -> %arg3 [map_idx=1] : !llvm.struct<(ptr, i64)>) { + %15 = llvm.mlir.constant(0 : index) : i64 + %16 = llvm.mlir.constant(32 : i8) : i8 + %17 = llvm.mlir.constant(1 : index) : i64 + %18 = llvm.mlir.constant(false) : i1 + %19 = llvm.mlir.constant(5 : index) : i64 + %20 = llvm.mlir.constant(5 : i32) : i32 + %21 = llvm.extractvalue %arg3[0] : !llvm.struct<(ptr, i64)> + %22 = llvm.extractvalue %arg3[1] : !llvm.struct<(ptr, i64)> + llvm.store %20, %arg1 : i32, !llvm.ptr + %23 = llvm.mlir.addressof @_QQclX68656C6C6F : !llvm.ptr + %24 = llvm.icmp "slt" %22, %19 : i64 + %25 = llvm.select %24, %22, %19 : i1, i64 + llvm.call @llvm.memmove.p0.p0.i64(%21, %23, %25, %18) : (!llvm.ptr, !llvm.ptr, i64, i1) -> () + %26 = llvm.sub %22, %17 : i64 + %27 = llvm.mlir.undef : !llvm.array<1 x i8> + %28 = llvm.insertvalue %16, %27[0] : !llvm.array<1 x i8> + %29 = llvm.sub %26, %25 : i64 + %30 = llvm.add %29, %17 : i64 + llvm.br ^bb1(%25, %30 : i64, i64) + ^bb1(%31: i64, %32: i64): // 2 preds: ^bb0, ^bb2 + %33 = llvm.icmp "sgt" %32, %15 : i64 + llvm.cond_br %33, ^bb2, ^bb3 + ^bb2: // pred: ^bb1 + %34 = llvm.getelementptr %21[%31] : (!llvm.ptr, i64) -> !llvm.ptr, !llvm.array<1 x i8> + llvm.store %28, %34 : !llvm.array<1 x i8>, !llvm.ptr + %35 = llvm.add %31, %17 : i64 + %36 = llvm.sub %32, %17 : i64 + llvm.br ^bb1(%35, %36 : i64, i64) + ^bb3: // pred: ^bb1 + omp.terminator + } + llvm.return +} +llvm.mlir.global linkonce constant @_QQclX68656C6C6F() comdat(@__llvm_comdat::@_QQclX68656C6C6F) {addr_space = 0 : i32} : !llvm.array<5 x i8> { + %0 = llvm.mlir.constant("hello") : !llvm.array<5 x i8> + llvm.return %0 : !llvm.array<5 x i8> +} +llvm.comdat @__llvm_comdat { + llvm.comdat_selector @_QQclX68656C6C6F any +} +llvm.func @llvm.memmove.p0.p0.i64(!llvm.ptr, !llvm.ptr, i64, i1) attributes {sym_visibility = "private"} + + + +// CHECK: define internal void @__omp_offloading_{{.*}}(ptr %{{[^,]+}}, ptr %[[MAPPED_ARG:.*]]) { +// CHECK: %[[BOXCHAR:.*]] = load { ptr, i64 }, ptr %[[MAPPED_ARG]] +// CHECK: %[[BOXCHAR_PTR:.*]] = extractvalue { ptr, i64 } %[[BOXCHAR]], 0 +// CHECK: %[[BOXCHAR_i64:.*]] = extractvalue { ptr, i64 } %[[BOXCHAR]], 1 +// CHECK: %[[MEM_ALLOC:.*]] = alloca i8, i64 %[[BOXCHAR_i64]] +// CHECK: %[[PRIV_BOXCHAR0:.*]] = insertvalue { ptr, i64 } undef, ptr %[[MEM_ALLOC]], 0 +// CHECK: %[[PRIV_BOXCHAR1:.*]] = insertvalue { ptr, i64 } %[[PRIV_BOXCHAR0]], i64 %[[BOXCHAR_i64]], 1 diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir index de797ea2aa3649b..6885c4cebf4f0b0 100644 --- a/mlir/test/Target/LLVMIR/openmp-todo.mlir +++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir @@ -346,24 +346,6 @@ llvm.func @target_firstprivate(%x : !llvm.ptr) { // ----- -omp.private {type = private} @x.privatizer : !llvm.ptr alloc { -^bb0(%arg0: !llvm.ptr): - omp.yield(%arg0 : !llvm.ptr) -} dealloc { -^bb0(%arg0: !llvm.ptr): - omp.yield -} -llvm.func @target_struct_privatization(%x : !llvm.ptr) { - // expected-error@below {{not yet implemented: privatization of structures in omp.target operation}} - // expected-error@below {{LLVM Translation failed for operation: omp.target}} - omp.target private(@x.privatizer %x -> %arg0 : !llvm.ptr) { - omp.terminator - } - llvm.return -} - -// ----- - llvm.func @target_thread_limit(%x : i32) { // expected-error@below {{not yet implemented: Unhandled clause thread_limit in omp.target operation}} // expected-error@below {{LLVM Translation failed for operation: omp.target}}