Skip to content

Commit 63c3ff8

Browse files
committed
[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.
1 parent 88e1520 commit 63c3ff8

File tree

7 files changed

+563
-35
lines changed

7 files changed

+563
-35
lines changed

flang/lib/Optimizer/OpenMP/MapsForPrivatizedSymbols.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,6 @@ class MapsForPrivatizedSymbolsPass
5050
: public flangomp::impl::MapsForPrivatizedSymbolsPassBase<
5151
MapsForPrivatizedSymbolsPass> {
5252

53-
bool privatizerNeedsMap(omp::PrivateClauseOp &privatizer) {
54-
Region &allocRegion = privatizer.getAllocRegion();
55-
Value blockArg0 = allocRegion.getArgument(0);
56-
if (blockArg0.use_empty())
57-
return false;
58-
return true;
59-
}
6053
omp::MapInfoOp createMapInfo(Location loc, Value var,
6154
fir::FirOpBuilder &builder) {
6255
uint64_t mapTypeTo = static_cast<
@@ -135,7 +128,7 @@ class MapsForPrivatizedSymbolsPass
135128
omp::PrivateClauseOp privatizer =
136129
SymbolTable::lookupNearestSymbolFrom<omp::PrivateClauseOp>(
137130
targetOp, privatizerName);
138-
if (!privatizerNeedsMap(privatizer)) {
131+
if (!privatizer.needsMap()) {
139132
privVarMapIdx.push_back(-1);
140133
continue;
141134
}

mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,17 @@ def PrivateClauseOp : OpenMP_Op<"private", [IsolatedFromAbove, RecipeInterface]>
135135
auto &region = getDeallocRegion();
136136
return region.empty() ? nullptr : region.getArgument(0);
137137
}
138+
/// privatizer is a PrivateClauseOp that privatizes an MLIR value.
139+
/// privatizerNeedsMap returns true if the value being privatized in an
140+
/// omp.target p should additionally be mapped to the target region
141+
/// using a MapInfoOp. This is most common when an allocatable is privatized.
142+
/// In such cases, the descriptor is use in privatization and needs to be
143+
/// mapped on to the device.
144+
bool needsMap() {
145+
Value blockArg0 = getAllocRegion().getArgument(0);
146+
return !blockArg0.use_empty();
147+
}
148+
138149
}];
139150

140151
let hasRegionVerifier = 1;

mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp

Lines changed: 141 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,6 @@ static LogicalResult checkImplementationStatus(Operation &op) {
305305
if (privatizer.getDataSharingType() ==
306306
omp::DataSharingClauseType::FirstPrivate)
307307
result = todo("firstprivate");
308-
309-
if (!privatizer.getDeallocRegion().empty())
310-
result = op.emitError("not yet implemented: privatization of "
311-
"structures in omp.target operation");
312308
}
313309
}
314310
checkThreadLimit(op, result);
@@ -3813,7 +3809,57 @@ createDeviceArgumentAccessor(MapInfoData &mapData, llvm::Argument &arg,
38133809

38143810
return builder.saveIP();
38153811
}
3812+
/// privatizer is a PrivateClauseOp that privatizes an MLIR value.
3813+
/// privatizerNeedsMap returns true if the value being privatized in an
3814+
/// omp.target p should additionally be mapped to the target region
3815+
/// using a MapInfoOp. This is most common when an allocatable is privatized.
3816+
/// In such cases, the descriptor is use in privatization and needs to be
3817+
/// mapped on to the device.
3818+
// static bool privatizerNeedsMap(omp::PrivateClauseOp &privatizer) {
3819+
// Region &allocRegion = privatizer.getAllocRegion();
3820+
// Value blockArg0 = allocRegion.getArgument(0);
3821+
// return !blockArg0.use_empty();
3822+
// }
3823+
3824+
/// Return the llvm::Value * corresponding to the privateVar that
3825+
/// is being privatized. It isn't always as simple as looking up
3826+
/// moduleTranslation with privateVar. For instance, in case of
3827+
/// an allocatable, the descriptor for the allocatable is privatized.
3828+
/// This descriptor is mapped using an MapInfoOp. So, this function
3829+
/// will return a pointer to the llvm::Value corresponding to the
3830+
/// block argument for the mapped descriptor.
3831+
static llvm::Value *
3832+
findHostAssociatedValue(Value privateVar, omp::TargetOp targetOp,
3833+
llvm::DenseMap<Value, int> &mappedPrivateVars,
3834+
llvm::IRBuilderBase &builder,
3835+
LLVM::ModuleTranslation &moduleTranslation) {
3836+
if (mappedPrivateVars.contains(privateVar)) {
3837+
int blockArgIndex = mappedPrivateVars[privateVar];
3838+
Value blockArg = targetOp.getRegion().getArgument(blockArgIndex);
3839+
mlir::Type privVarType = privateVar.getType();
3840+
mlir::Type blockArgType = blockArg.getType();
3841+
assert(isa<LLVM::LLVMPointerType>(blockArgType) &&
3842+
"A block argument corresponding to a mapped var should have "
3843+
"!llvm.ptr type");
3844+
3845+
if (privVarType == blockArg.getType()) {
3846+
llvm::Value *v = moduleTranslation.lookupValue(blockArg);
3847+
return v;
3848+
}
38163849

3850+
if (!isa<LLVM::LLVMPointerType>(privVarType)) {
3851+
// This typically happens when the privatized type is lowered from
3852+
// boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
3853+
// struct/pair is passed by value. But, mapped values are passed only as
3854+
// pointers, so before we privatize, we must load the pointer.
3855+
llvm::Value *load =
3856+
builder.CreateLoad(moduleTranslation.convertType(privVarType),
3857+
moduleTranslation.lookupValue(blockArg));
3858+
return load;
3859+
}
3860+
}
3861+
return moduleTranslation.lookupValue(privateVar);
3862+
}
38173863
static LogicalResult
38183864
convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38193865
LLVM::ModuleTranslation &moduleTranslation) {
@@ -3825,6 +3871,19 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38253871
bool isTargetDevice = ompBuilder->Config.isTargetDevice();
38263872
auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
38273873
auto &targetRegion = targetOp.getRegion();
3874+
// Holds the private vars that have been mapped along with
3875+
// the block argument that corresponds to the MapInfoOp
3876+
// corresponding to the private var in question.
3877+
// So, for instance
3878+
//
3879+
// %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
3880+
// omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
3881+
//
3882+
// Then, %10 has been created so that the descriptor can be used by the
3883+
// privatizer
3884+
// @box.privatizer on the device side. Here we'd record {%6#0, 0} in the
3885+
// mappedPrivateVars map.
3886+
llvm::DenseMap<Value, int> mappedPrivateVars;
38283887
DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
38293888
SmallVector<Value> mapVars = targetOp.getMapVars();
38303889
ArrayRef<BlockArgument> mapBlockArgs =
@@ -3836,6 +3895,58 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38363895
bool isOffloadEntry =
38373896
isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
38383897

3898+
// For some private variables, the MapsForPrivatizedVariablesPass
3899+
// creates MapInfoOp instances. Go through the private variables and
3900+
// the mapped variables so that during codegeneration we are able
3901+
// to quickly look up the corresponding map variable, if any for each
3902+
// private variable.
3903+
if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
3904+
auto argIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
3905+
OperandRange privateVars = targetOp.getPrivateVars();
3906+
std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
3907+
std::optional<ArrayAttr> privateMapIdices = targetOp.getPrivateMapsAttr();
3908+
3909+
for (auto [privVarIdx, privVarSymPair] :
3910+
llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) {
3911+
auto privVar = std::get<0>(privVarSymPair);
3912+
auto privSym = std::get<1>(privVarSymPair);
3913+
3914+
SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
3915+
omp::PrivateClauseOp privatizer =
3916+
findPrivatizer(targetOp, privatizerName);
3917+
3918+
if (!privatizer.needsMap())
3919+
continue;
3920+
3921+
assert(privateMapIdices.has_value() &&
3922+
privateMapIdices->size() == privateVars.size());
3923+
int64_t mapInfoOpIdx =
3924+
llvm::cast<IntegerAttr>((*privateMapIdices)[privVarIdx]).getInt();
3925+
3926+
assert(mapInfoOpIdx >= 0 &&
3927+
static_cast<size_t>(mapInfoOpIdx) < mapVars.size());
3928+
3929+
// The MapInfoOp defining the map var isn't really needed later.
3930+
// So, we don't store it in any datastructure. Instead, we just
3931+
// do some sanity checks on it right now.
3932+
auto mapInfoOp = mapVars[mapInfoOpIdx].getDefiningOp<omp::MapInfoOp>();
3933+
Type varType = mapInfoOp.getVarType();
3934+
3935+
// Check #1: Check that the type of the private variable matches
3936+
// the type of the variable being mapped.
3937+
if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
3938+
assert(
3939+
varType == privVar.getType() &&
3940+
"Type of private var doesn't match the type of the mapped value");
3941+
3942+
// Ok, only 1 sanity check for now.
3943+
// Record the index of the block argument corresponding to this
3944+
// mapvar.
3945+
mappedPrivateVars.insert(
3946+
{privVar, argIface.getMapBlockArgsStart() + mapInfoOpIdx});
3947+
}
3948+
}
3949+
38393950
using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
38403951
auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP)
38413952
-> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
@@ -3862,9 +3973,10 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38623973
moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
38633974
moduleTranslation.mapValue(arg, mapOpValue);
38643975
}
3865-
38663976
// Do privatization after moduleTranslation has already recorded
38673977
// mapped values.
3978+
SmallVector<llvm::Value *> llvmPrivateVars;
3979+
SmallVector<Region *> privateCleanupRegions;
38683980
if (!targetOp.getPrivateVars().empty()) {
38693981
builder.restoreIP(allocaIP);
38703982

@@ -3880,11 +3992,13 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38803992
omp::PrivateClauseOp privatizer = findPrivatizer(&opInst, privSym);
38813993
assert(privatizer.getDataSharingType() !=
38823994
omp::DataSharingClauseType::FirstPrivate &&
3883-
privatizer.getDeallocRegion().empty() &&
38843995
"unsupported privatizer");
3885-
moduleTranslation.mapValue(privatizer.getAllocMoldArg(),
3886-
moduleTranslation.lookupValue(privVar));
38873996
Region &allocRegion = privatizer.getAllocRegion();
3997+
BlockArgument allocRegionArg = allocRegion.getArgument(0);
3998+
moduleTranslation.mapValue(
3999+
allocRegionArg,
4000+
findHostAssociatedValue(privVar, targetOp, mappedPrivateVars,
4001+
builder, moduleTranslation));
38884002
SmallVector<llvm::Value *, 1> yieldedValues;
38894003
if (failed(inlineConvertOmpRegions(
38904004
allocRegion, "omp.targetop.privatizer", builder,
@@ -3893,7 +4007,12 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38934007
"failed to inline `alloc` region of `omp.private`");
38944008
}
38954009
assert(yieldedValues.size() == 1);
3896-
moduleTranslation.mapValue(privBlockArg, yieldedValues.front());
4010+
llvm::Value *llvmReplacementValue = yieldedValues.front();
4011+
moduleTranslation.mapValue(privBlockArg, llvmReplacementValue);
4012+
if (!privatizer.getDeallocRegion().empty()) {
4013+
llvmPrivateVars.push_back(llvmReplacementValue);
4014+
privateCleanupRegions.push_back(&privatizer.getDeallocRegion());
4015+
}
38974016
moduleTranslation.forgetMapping(allocRegion);
38984017
builder.restoreIP(builder.saveIP());
38994018
}
@@ -3905,6 +4024,19 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
39054024
return exitBlock.takeError();
39064025

39074026
builder.SetInsertPoint(*exitBlock);
4027+
if (!llvmPrivateVars.empty()) {
4028+
assert(llvmPrivateVars.size() == privateCleanupRegions.size() &&
4029+
"Number of private variables needing cleanup not equal to number"
4030+
"of privatizers with dealloc regions");
4031+
if (failed(inlineOmpRegionCleanup(
4032+
privateCleanupRegions, llvmPrivateVars, moduleTranslation,
4033+
builder, "omp.targetop.private.cleanup",
4034+
/*shouldLoadCleanupRegionArg=*/false))) {
4035+
return llvm::createStringError(
4036+
"failed to inline `dealloc` region of `omp.private` "
4037+
"op in the target region");
4038+
}
4039+
}
39084040
return builder.saveIP();
39094041
};
39104042

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
2+
3+
llvm.func @free(!llvm.ptr)
4+
llvm.func @malloc(i64) -> !llvm.ptr
5+
omp.private {type = private} @box.heap_privatizer0 : !llvm.ptr alloc {
6+
^bb0(%arg0: !llvm.ptr):
7+
%0 = llvm.mlir.constant(1 : i32) : i32
8+
%10 = llvm.getelementptr %arg0[0, 1] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
9+
%1 = llvm.load %10 : !llvm.ptr -> i64
10+
%7 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> : (i32) -> !llvm.ptr
11+
%17 = llvm.call @malloc(%1) {fir.must_be_heap = true, in_type = i32} : (i64) -> !llvm.ptr
12+
%22 = llvm.mlir.undef : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
13+
%37 = llvm.insertvalue %17, %22[0] : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
14+
llvm.store %37, %7 : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>, !llvm.ptr
15+
omp.yield(%7 : !llvm.ptr)
16+
} dealloc {
17+
^bb0(%arg0: !llvm.ptr):
18+
%6 = llvm.mlir.constant(0 : i64) : i64
19+
%8 = llvm.getelementptr %arg0[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
20+
%9 = llvm.load %8 : !llvm.ptr -> !llvm.ptr
21+
llvm.call @free(%9) : (!llvm.ptr) -> ()
22+
omp.yield
23+
}
24+
omp.private {type = private} @box.heap_privatizer1 : !llvm.ptr alloc {
25+
^bb0(%arg0: !llvm.ptr):
26+
%0 = llvm.mlir.constant(1 : i32) : i32
27+
%10 = llvm.getelementptr %arg0[0, 1] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
28+
%1 = llvm.load %10 : !llvm.ptr -> i64
29+
%7 = llvm.alloca %0 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> : (i32) -> !llvm.ptr
30+
%17 = llvm.call @malloc(%1) {fir.must_be_heap = true, in_type = i32} : (i64) -> !llvm.ptr
31+
%22 = llvm.mlir.undef : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
32+
%37 = llvm.insertvalue %17, %22[0] : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
33+
llvm.store %37, %7 : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>, !llvm.ptr
34+
omp.yield(%7 : !llvm.ptr)
35+
} dealloc {
36+
^bb0(%arg0: !llvm.ptr):
37+
%6 = llvm.mlir.constant(0 : i64) : i64
38+
%8 = llvm.getelementptr %arg0[0, 0] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
39+
%9 = llvm.load %8 : !llvm.ptr -> !llvm.ptr
40+
llvm.call @free(%9) : (!llvm.ptr) -> ()
41+
omp.yield
42+
}
43+
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"} {
44+
%6 = llvm.mlir.constant(1 : i64) : i64
45+
%7 = llvm.alloca %6 x i32 {bindc_name = "mapped_var"} : (i64) -> !llvm.ptr
46+
%13 = llvm.alloca %6 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {bindc_name = "alloc_var0"} : (i64) -> !llvm.ptr
47+
%14 = llvm.alloca %6 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {bindc_name = "alloc_var1"} : (i64) -> !llvm.ptr
48+
%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"}
49+
%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
50+
%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
51+
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) {
52+
%64 = llvm.mlir.constant(1 : i32) : i32
53+
%65 = llvm.alloca %64 x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {alignment = 8 : i64} : (i32) -> !llvm.ptr
54+
%67 = llvm.alloca %64 x i32 : (i32) -> !llvm.ptr
55+
%66 = llvm.mlir.constant(19 : i32) : i32
56+
%69 = llvm.mlir.constant(10 : i32) : i32
57+
llvm.store %64, %arg3 : i32, !llvm.ptr
58+
llvm.store %69, %67 : i32, !llvm.ptr
59+
%75 = llvm.mlir.undef : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
60+
%90 = llvm.insertvalue %67, %75[0] : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
61+
llvm.store %90, %65 : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>, !llvm.ptr
62+
%91 = llvm.mlir.zero : !llvm.ptr
63+
%92 = llvm.call @_FortranAAssign(%arg6, %65, %91, %66) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, i32) -> !llvm.struct<()>
64+
%93 = llvm.call @_FortranAAssign(%arg7, %65, %91, %66) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, i32) -> !llvm.struct<()>
65+
omp.terminator
66+
}
67+
llvm.return
68+
}
69+
70+
71+
llvm.func @_FortranAAssign(!llvm.ptr, !llvm.ptr, !llvm.ptr, i32) -> !llvm.struct<()> attributes {fir.runtime, sym_visibility = "private"}
72+
73+
// The first set of checks ensure that we are calling the offloaded function
74+
// with the right arguments, especially the second argument which needs to
75+
// be a memory reference to the descriptor for the privatized allocatable
76+
// CHECK: define void @target_allocatable_
77+
// CHECK-NOT: define internal void
78+
// CHECK: %[[DESC_ALLOC0:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }, i64 1
79+
// CHECK: %[[DESC_ALLOC1:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }, i64 1
80+
// CHECK: call void @__omp_offloading_[[OFFLOADED_FUNCTION:.*]](ptr {{[^,]+}},
81+
// CHECK-SAME: ptr %[[DESC_ALLOC0]], ptr %[[DESC_ALLOC1]])
82+
83+
// CHECK: define internal void @__omp_offloading_[[OFFLOADED_FUNCTION]]
84+
// CHECK-SAME: (ptr {{[^,]+}}, ptr %[[DESCRIPTOR_ARG0:[^,]+]],
85+
// CHECK-SAME: ptr %[[DESCRIPTOR_ARG1:.*]]) {
86+
// CHECK: %[[I0:.*]] = getelementptr { ptr, i64, i32, i8, i8, i8, i8 },
87+
// CHECK-SAME: ptr %[[DESCRIPTOR_ARG0]], i32 0, i32 1
88+
// CHECK: %[[MALLOC_ARG0:.*]] = load i64, ptr %[[I0]]
89+
// CHECK: %[[PRIV_DESC0:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }
90+
// CHECK: %[[HEAP_PTR0:.*]] = call ptr @malloc(i64 %[[MALLOC_ARG0]])
91+
// CHECK: %[[TMP0:.*]] = insertvalue { ptr, i64, i32, i8, i8, i8, i8 }
92+
// CHECK-SAME: undef, ptr %[[HEAP_PTR0]], 0
93+
// CHECK: store { ptr, i64, i32, i8, i8, i8, i8 } %[[TMP0]], ptr %[[PRIV_DESC0]]
94+
95+
// CHECK: %[[I1:.*]] = getelementptr { ptr, i64, i32, i8, i8, i8, i8 },
96+
// CHECK-SAME: ptr %[[DESCRIPTOR_ARG1]], i32 0, i32 1
97+
// CHECK: %[[MALLOC_ARG1:.*]] = load i64, ptr %[[I1]]
98+
// CHECK: %[[PRIV_DESC1:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }
99+
// CHECK: %[[HEAP_PTR1:.*]] = call ptr @malloc(i64 %[[MALLOC_ARG1]])
100+
// CHECK: %[[TMP1:.*]] = insertvalue { ptr, i64, i32, i8, i8, i8, i8 }
101+
// CHECK-SAME: undef, ptr %[[HEAP_PTR1]], 0
102+
// CHECK: store { ptr, i64, i32, i8, i8, i8, i8 } %[[TMP1]], ptr %[[PRIV_DESC1]]
103+
104+
// CHECK: call {} @_FortranAAssign(ptr %[[PRIV_DESC0]]
105+
// CHECK: call {} @_FortranAAssign(ptr %[[PRIV_DESC1]]
106+
107+
// CHECK: %[[PTR0:.*]] = getelementptr { ptr, i64, i32, i8, i8, i8, i8 },
108+
// CHECK-SAME: ptr %[[PRIV_DESC0]], i32 0, i32 0
109+
// CHECK: %[[HEAP_MEMREF0:.*]] = load ptr, ptr %[[PTR0]]
110+
// CHECK: call void @free(ptr %[[HEAP_MEMREF0]])
111+
// CHECK: %[[PTR1:.*]] = getelementptr { ptr, i64, i32, i8, i8, i8, i8 },
112+
// CHECK-SAME: ptr %[[PRIV_DESC1]], i32 0, i32 0
113+
// CHECK: %[[HEAP_MEMREF1:.*]] = load ptr, ptr %[[PTR1]]
114+
// CHECK: call void @free(ptr %[[HEAP_MEMREF1]])

0 commit comments

Comments
 (0)