Skip to content

Commit 9e83dcf

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 686a291 commit 9e83dcf

File tree

7 files changed

+555
-36
lines changed

7 files changed

+555
-36
lines changed

flang/lib/Optimizer/OpenMP/MapsForPrivatizedSymbols.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,6 @@ class MapsForPrivatizedSymbolsPass
4949
: public flangomp::impl::MapsForPrivatizedSymbolsPassBase<
5050
MapsForPrivatizedSymbolsPass> {
5151

52-
bool privatizerNeedsMap(omp::PrivateClauseOp &privatizer) {
53-
Region &allocRegion = privatizer.getAllocRegion();
54-
Value blockArg0 = allocRegion.getArgument(0);
55-
if (blockArg0.use_empty())
56-
return false;
57-
return true;
58-
}
5952
omp::MapInfoOp createMapInfo(Location loc, Value var,
6053
fir::FirOpBuilder &builder) {
6154
uint64_t mapTypeTo = static_cast<
@@ -132,9 +125,9 @@ class MapsForPrivatizedSymbolsPass
132125
omp::PrivateClauseOp privatizer =
133126
SymbolTable::lookupNearestSymbolFrom<omp::PrivateClauseOp>(
134127
targetOp, privatizerName);
135-
if (!privatizerNeedsMap(privatizer)) {
128+
if (!privatizer.needsMap())
136129
continue;
137-
}
130+
138131
builder.setInsertionPoint(targetOp);
139132
Location loc = targetOp.getLoc();
140133
omp::MapInfoOp mapInfoOp = createMapInfo(loc, privVar, builder);

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: 132 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);
@@ -3819,7 +3815,57 @@ createDeviceArgumentAccessor(MapInfoData &mapData, llvm::Argument &arg,
38193815

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

3856+
if (!isa<LLVM::LLVMPointerType>(privVarType)) {
3857+
// This typically happens when the privatized type is lowered from
3858+
// boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
3859+
// struct/pair is passed by value. But, mapped values are passed only as
3860+
// pointers, so before we privatize, we must load the pointer.
3861+
llvm::Value *load =
3862+
builder.CreateLoad(moduleTranslation.convertType(privVarType),
3863+
moduleTranslation.lookupValue(blockArg));
3864+
return load;
3865+
}
3866+
}
3867+
return moduleTranslation.lookupValue(privateVar);
3868+
}
38233869
static LogicalResult
38243870
convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38253871
LLVM::ModuleTranslation &moduleTranslation) {
@@ -3831,6 +3877,19 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38313877
bool isTargetDevice = ompBuilder->Config.isTargetDevice();
38323878
auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
38333879
auto &targetRegion = targetOp.getRegion();
3880+
// Holds the private vars that have been mapped along with
3881+
// the block argument that corresponds to the MapInfoOp
3882+
// corresponding to the private var in question.
3883+
// So, for instance
3884+
//
3885+
// %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
3886+
// omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
3887+
//
3888+
// Then, %10 has been created so that the descriptor can be used by the
3889+
// privatizer
3890+
// @box.privatizer on the device side. Here we'd record {%6#0, 0} in the
3891+
// mappedPrivateVars map.
3892+
llvm::DenseMap<Value, int> mappedPrivateVars;
38343893
DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
38353894
SmallVector<Value> mapVars = targetOp.getMapVars();
38363895
ArrayRef<BlockArgument> mapBlockArgs =
@@ -3842,6 +3901,49 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38423901
bool isOffloadEntry =
38433902
isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
38443903

3904+
// For some private variables, the MapsForPrivatizedVariablesPass
3905+
// creates MapInfoOp instances. Go through the private variables and
3906+
// the mapped variables so that during codegeneration we are able
3907+
// to quickly look up the corresponding map variable, if any for each
3908+
// private variable.
3909+
if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
3910+
auto argIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
3911+
unsigned lastMapBlockArgsIdx =
3912+
argIface.getMapBlockArgsStart() + argIface.numMapBlockArgs() - 1;
3913+
OperandRange privateVars = targetOp.getPrivateVars();
3914+
std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
3915+
auto reverseIt = mapVars.rbegin();
3916+
for (auto [privVar, privSym] :
3917+
llvm::reverse(llvm::zip_equal(privateVars, *privateSyms))) {
3918+
SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
3919+
omp::PrivateClauseOp privatizer =
3920+
findPrivatizer(targetOp, privatizerName);
3921+
if (!privatizer.needsMap())
3922+
continue;
3923+
3924+
// The MapInfoOp defining the map var isn't really needed later.
3925+
// So, we don't store it in any datastructure. Instead, we just
3926+
// do some sanity checks on it right now.
3927+
omp::MapInfoOp mapInfoOp =
3928+
llvm::cast<omp::MapInfoOp>((*reverseIt).getDefiningOp());
3929+
Type varType = mapInfoOp.getVarType();
3930+
3931+
// Check #1: Check that the type of the private variable matches
3932+
// the type of the variable being mapped.
3933+
if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
3934+
assert(
3935+
varType == privVar.getType() &&
3936+
"Type of private var doesn't match the type of the mapped value");
3937+
3938+
// Ok, only 1 sanity check for now.
3939+
// Record the index of the block argument corresponding to this
3940+
// mapvar.
3941+
mappedPrivateVars.insert({privVar, lastMapBlockArgsIdx});
3942+
lastMapBlockArgsIdx--;
3943+
reverseIt++;
3944+
}
3945+
}
3946+
38453947
using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
38463948
auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP)
38473949
-> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
@@ -3868,9 +3970,10 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38683970
moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
38693971
moduleTranslation.mapValue(arg, mapOpValue);
38703972
}
3871-
38723973
// Do privatization after moduleTranslation has already recorded
38733974
// mapped values.
3975+
SmallVector<llvm::Value *> llvmPrivateVars;
3976+
SmallVector<Region *> privateCleanupRegions;
38743977
if (!targetOp.getPrivateVars().empty()) {
38753978
builder.restoreIP(allocaIP);
38763979

@@ -3886,11 +3989,13 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38863989
omp::PrivateClauseOp privatizer = findPrivatizer(&opInst, privSym);
38873990
assert(privatizer.getDataSharingType() !=
38883991
omp::DataSharingClauseType::FirstPrivate &&
3889-
privatizer.getDeallocRegion().empty() &&
38903992
"unsupported privatizer");
3891-
moduleTranslation.mapValue(privatizer.getAllocMoldArg(),
3892-
moduleTranslation.lookupValue(privVar));
38933993
Region &allocRegion = privatizer.getAllocRegion();
3994+
BlockArgument allocRegionArg = allocRegion.getArgument(0);
3995+
moduleTranslation.mapValue(
3996+
allocRegionArg,
3997+
findHostAssociatedValue(privVar, targetOp, mappedPrivateVars,
3998+
builder, moduleTranslation));
38943999
SmallVector<llvm::Value *, 1> yieldedValues;
38954000
if (failed(inlineConvertOmpRegions(
38964001
allocRegion, "omp.targetop.privatizer", builder,
@@ -3899,7 +4004,12 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
38994004
"failed to inline `alloc` region of `omp.private`");
39004005
}
39014006
assert(yieldedValues.size() == 1);
3902-
moduleTranslation.mapValue(privBlockArg, yieldedValues.front());
4007+
llvm::Value *llvmReplacementValue = yieldedValues.front();
4008+
moduleTranslation.mapValue(privBlockArg, llvmReplacementValue);
4009+
if (!privatizer.getDeallocRegion().empty()) {
4010+
llvmPrivateVars.push_back(llvmReplacementValue);
4011+
privateCleanupRegions.push_back(&privatizer.getDeallocRegion());
4012+
}
39034013
moduleTranslation.forgetMapping(allocRegion);
39044014
builder.restoreIP(builder.saveIP());
39054015
}
@@ -3911,6 +4021,19 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
39114021
return exitBlock.takeError();
39124022

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

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, @box.heap_privatizer1 %14 -> %arg7 : !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)