Skip to content

Commit ab1db26

Browse files
authored
[flang][hlfir] Fixed some finalization/deallocation issues. (#67047)
This set of commits resolves some of the issues with elemental calls producing results that may require finalization, and also some memory leak issues due to the missing deallocation of allocatable components of the temporary buffers created by the bufferization pass. - [flang][runtime] Expose Finalize API for derived types. - [flang][hlfir] Add 'finalize' attribute for DestroyOp. - [flang][hlfir] Postpone result finalization for elemental calls. The results of elemental calls generated inside hlfir.elemental must not be finalized/destructed before they are copied into the resulting array. The finalization must be done on the array as a whole (e.g. there might be different scalar and array finalization routines). The finalization work is left to the hlfir.destroy corresponding to this hlfir.elemental. - [flang][hlfir] Tighten requirements on hlfir.end_associate operand. If component deallocation might be required for the operand of hlfir.end_associate, we have to be able to get the variable shape/params to create a descriptor for calling the runtime. This commit adds verification that we can do so. - [flang][hlfir] Lower argument clean-ups using valid hlfir.end_associate. The operand must be a Fortran entity, when allocatable component deallocation may be required. - [flang][hlfir] Properly clean-up temporary buffers in bufferization pass. This commit combines changes for proper finalization and component deallocation of the temporary buffers. The finalization part relates to hlfir.destroy operations with 'finalize' attribute. The component deallocation might be invoked for both hlfir.destroy and hlfir.end_associate, if the operand is of a derived type with allocatable component(s). The changes are mostly in one function, so I decided not to split them. - [flang][hlfir] Disable optimizations for hlfir.elemental requiring finalization. If hlfir.elemental is coupled with hlfir.destroy with 'finalize' attribute, the temporary array result of hlfir.elemental needs to be created for the purpose of finalization. We cannot do certain optimizations on such hlfir.elemental operations. I was not able to come up with a test for the OptimizedBufferization pass, but I put the check there as well.
1 parent b38f31a commit ab1db26

29 files changed

+680
-69
lines changed

flang/include/flang/Lower/ConvertCall.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ namespace Fortran::lower {
2828
/// the call and return the result. This function deals with explicit result
2929
/// allocation and lowering if needed. It also deals with passing the host
3030
/// link to internal procedures.
31+
/// \p isElemental must be set to true if elemental call is being produced.
32+
/// It is only used for HLFIR.
3133
fir::ExtendedValue genCallOpAndResult(
3234
mlir::Location loc, Fortran::lower::AbstractConverter &converter,
3335
Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
3436
Fortran::lower::CallerInterface &caller, mlir::FunctionType callSiteType,
35-
std::optional<mlir::Type> resultType);
37+
std::optional<mlir::Type> resultType, bool isElemental = false);
3638

3739
/// If \p arg is the address of a function with a denoted host-association tuple
3840
/// argument, then return the host-associations tuple value of the current

flang/include/flang/Optimizer/Builder/HLFIRTools.h

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,37 +35,6 @@ class ElementalOpInterface;
3535
class ElementalAddrOp;
3636
class YieldElementOp;
3737

38-
/// Is this an SSA value type for the value of a Fortran procedure
39-
/// designator ?
40-
inline bool isFortranProcedureValue(mlir::Type type) {
41-
return type.isa<fir::BoxProcType>() ||
42-
(type.isa<mlir::TupleType>() &&
43-
fir::isCharacterProcedureTuple(type, /*acceptRawFunc=*/false));
44-
}
45-
46-
/// Is this an SSA value type for the value of a Fortran expression?
47-
inline bool isFortranValueType(mlir::Type type) {
48-
return type.isa<hlfir::ExprType>() || fir::isa_trivial(type) ||
49-
isFortranProcedureValue(type);
50-
}
51-
52-
/// Is this the value of a Fortran expression in an SSA value form?
53-
inline bool isFortranValue(mlir::Value value) {
54-
return isFortranValueType(value.getType());
55-
}
56-
57-
/// Is this a Fortran variable?
58-
/// Note that by "variable", it must be understood that the mlir::Value is
59-
/// a memory value of a storage that can be reason about as a Fortran object
60-
/// (its bounds, shape, and type parameters, if any, are retrievable).
61-
/// This does not imply that the mlir::Value points to a variable from the
62-
/// original source or can be legally defined: temporaries created to store
63-
/// expression values are considered to be variables, and so are PARAMETERs
64-
/// global constant address.
65-
inline bool isFortranEntity(mlir::Value value) {
66-
return isFortranValue(value) || isFortranVariableType(value.getType());
67-
}
68-
6938
/// Is this a Fortran variable for which the defining op carrying the Fortran
7039
/// attributes is visible?
7140
inline bool isFortranVariableWithAttributes(mlir::Value value) {
@@ -442,6 +411,13 @@ hlfir::ElementalOp cloneToElementalOp(mlir::Location loc,
442411
fir::FirOpBuilder &builder,
443412
hlfir::ElementalAddrOp elementalAddrOp);
444413

414+
/// Return true, if \p elemental must produce a temporary array,
415+
/// for example, for the purpose of finalization. Note that such
416+
/// ElementalOp's must be optimized with caution. For example,
417+
/// completely inlining such ElementalOp into another one
418+
/// would be incorrect.
419+
bool elementalOpMustProduceTemp(hlfir::ElementalOp elemental);
420+
445421
} // namespace hlfir
446422

447423
#endif // FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H

flang/include/flang/Optimizer/Builder/Runtime/Derived.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ void genDerivedTypeInitialize(fir::FirOpBuilder &builder, mlir::Location loc,
3131
void genDerivedTypeDestroy(fir::FirOpBuilder &builder, mlir::Location loc,
3232
mlir::Value box);
3333

34+
/// Generate call to derived type finalization runtime routine
35+
/// to finalize \p box.
36+
void genDerivedTypeFinalize(fir::FirOpBuilder &builder, mlir::Location loc,
37+
mlir::Value box);
38+
3439
/// Generate call to derived type destruction runtime routine to
3540
/// destroy \p box without finalization
3641
void genDerivedTypeDestroyWithoutFinalization(fir::FirOpBuilder &builder,

flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,37 @@ inline bool isPolymorphicType(mlir::Type type) {
7878
return fir::isPolymorphicType(type);
7979
}
8080

81+
/// Is this an SSA value type for the value of a Fortran procedure
82+
/// designator ?
83+
inline bool isFortranProcedureValue(mlir::Type type) {
84+
return type.isa<fir::BoxProcType>() ||
85+
(type.isa<mlir::TupleType>() &&
86+
fir::isCharacterProcedureTuple(type, /*acceptRawFunc=*/false));
87+
}
88+
89+
/// Is this an SSA value type for the value of a Fortran expression?
90+
inline bool isFortranValueType(mlir::Type type) {
91+
return type.isa<hlfir::ExprType>() || fir::isa_trivial(type) ||
92+
isFortranProcedureValue(type);
93+
}
94+
95+
/// Is this the value of a Fortran expression in an SSA value form?
96+
inline bool isFortranValue(mlir::Value value) {
97+
return isFortranValueType(value.getType());
98+
}
99+
100+
/// Is this a Fortran variable?
101+
/// Note that by "variable", it must be understood that the mlir::Value is
102+
/// a memory value of a storage that can be reason about as a Fortran object
103+
/// (its bounds, shape, and type parameters, if any, are retrievable).
104+
/// This does not imply that the mlir::Value points to a variable from the
105+
/// original source or can be legally defined: temporaries created to store
106+
/// expression values are considered to be variables, and so are PARAMETERs
107+
/// global constant address.
108+
inline bool isFortranEntity(mlir::Value value) {
109+
return isFortranValue(value) || isFortranVariableType(value.getType());
110+
}
111+
81112
bool isFortranScalarNumericalType(mlir::Type);
82113
bool isFortranNumericalArrayObject(mlir::Type);
83114
bool isFortranNumericalOrLogicalArrayObject(mlir::Type);
@@ -94,6 +125,13 @@ bool isPolymorphicObject(mlir::Type);
94125
mlir::Value genExprShape(mlir::OpBuilder &builder, const mlir::Location &loc,
95126
const hlfir::ExprType &expr);
96127

128+
/// Return true iff `ty` may have allocatable component.
129+
/// TODO: this actually belongs to FIRType.cpp, but the method's implementation
130+
/// depends on HLFIRDialect component. FIRType.cpp itself is part of FIRDialect
131+
/// that cannot depend on HLFIRBuilder (there will be a cyclic dependency).
132+
/// This has to be cleaned up, when HLFIR is the default.
133+
bool mayHaveAllocatableComponent(mlir::Type ty);
134+
97135
} // namespace hlfir
98136

99137
#endif // FORTRAN_OPTIMIZER_HLFIR_HLFIRDIALECT_H

flang/include/flang/Optimizer/HLFIR/HLFIROps.td

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,8 @@ def hlfir_EndAssociateOp : hlfir_Op<"end_associate", [MemoryEffects<[MemFree]>]>
705705

706706
let description = [{
707707
Mark the end of life of a variable associated to an expression.
708+
If the expression has a derived type that may contain allocatable
709+
components, the variable operand must be a Fortran entity.
708710
}];
709711

710712
let arguments = (ins AnyRefOrBoxLike:$var,
@@ -715,6 +717,7 @@ def hlfir_EndAssociateOp : hlfir_Op<"end_associate", [MemoryEffects<[MemFree]>]>
715717
}];
716718

717719
let builders = [OpBuilder<(ins "hlfir::AssociateOp":$associate)>];
720+
let hasVerifier = 1;
718721
}
719722

720723
def hlfir_AsExprOp : hlfir_Op<"as_expr",
@@ -981,6 +984,11 @@ def hlfir_DestroyOp : hlfir_Op<"destroy", [MemoryEffects<[MemFree]>]> {
981984
Mark the last use of an hlfir.expr. This will be the point at which the
982985
buffer of an hlfir.expr, if any, will be deallocated if it was heap
983986
allocated.
987+
If "finalize" attribute is set, the hlfir.expr value will be finalized
988+
before the deallocation. Note that this implies that the hlfir.expr
989+
is placed into a memory buffer, so that the library runtime
990+
can be called on it. The element type of the hlfir.expr must be
991+
derived type in this case.
984992
It is not required to create an hlfir.destroy operation for and hlfir.expr
985993
created inside an hlfir.elemental and returned in the hlfir.yield_element.
986994
The last use of such expression is implicit and an hlfir.destroy could
@@ -995,9 +1003,22 @@ def hlfir_DestroyOp : hlfir_Op<"destroy", [MemoryEffects<[MemFree]>]> {
9951003
in bufferization instead.
9961004
}];
9971005

998-
let arguments = (ins hlfir_ExprType:$expr);
1006+
let arguments = (ins
1007+
hlfir_ExprType:$expr,
1008+
UnitAttr:$finalize
1009+
);
1010+
1011+
let assemblyFormat = [{
1012+
$expr (`finalize` $finalize^)? attr-dict `:` qualified(type($expr))
1013+
}];
1014+
1015+
let extraClassDeclaration = [{
1016+
bool mustFinalizeExpr() {
1017+
return getFinalize();
1018+
}
1019+
}];
9991020

1000-
let assemblyFormat = "$expr attr-dict `:` qualified(type($expr))";
1021+
let hasVerifier = 1;
10011022
}
10021023

10031024
def hlfir_CopyInOp : hlfir_Op<"copy_in", [MemoryEffects<[MemAlloc]>]> {

flang/include/flang/Runtime/derived-api.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ void RTNAME(Initialize)(
3737
// storage.
3838
void RTNAME(Destroy)(const Descriptor &);
3939

40+
// Finalizes the object and its components.
41+
void RTNAME(Finalize)(
42+
const Descriptor &, const char *sourceFile = nullptr, int sourceLine = 0);
43+
4044
/// Deallocates any allocatable/automatic components.
4145
/// Does not deallocate the descriptor's storage.
4246
/// Does not perform any finalization.

flang/lib/Lower/ConvertCall.cpp

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fir::ExtendedValue Fortran::lower::genCallOpAndResult(
148148
mlir::Location loc, Fortran::lower::AbstractConverter &converter,
149149
Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
150150
Fortran::lower::CallerInterface &caller, mlir::FunctionType callSiteType,
151-
std::optional<mlir::Type> resultType) {
151+
std::optional<mlir::Type> resultType, bool isElemental) {
152152
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
153153
using PassBy = Fortran::lower::CallerInterface::PassEntityBy;
154154
// Handle cases where caller must allocate the result or a fir.box for it.
@@ -435,7 +435,13 @@ fir::ExtendedValue Fortran::lower::genCallOpAndResult(
435435
std::optional<Fortran::evaluate::DynamicType> retTy =
436436
caller.getCallDescription().proc().GetType();
437437
bool cleanupWithDestroy = false;
438-
if (!fir::isPointerType(funcType.getResults()[0]) && retTy &&
438+
// With HLFIR lowering, isElemental must be set to true
439+
// if we are producing an elemental call. In this case,
440+
// the elemental results must not be destroyed, instead,
441+
// the resulting array result will be finalized/destroyed
442+
// as needed by hlfir.destroy.
443+
if (!isElemental && !fir::isPointerType(funcType.getResults()[0]) &&
444+
retTy &&
439445
(retTy->category() == Fortran::common::TypeCategory::Derived ||
440446
retTy->IsPolymorphic() || retTy->IsUnlimitedPolymorphic())) {
441447
if (retTy->IsPolymorphic() || retTy->IsUnlimitedPolymorphic()) {
@@ -692,6 +698,14 @@ struct PreparedDummyArgument {
692698
cleanups.emplace_back(
693699
CallCleanUp{CallCleanUp::ExprAssociate{tempVar, wasCopied}});
694700
}
701+
void pushExprAssociateCleanUp(hlfir::AssociateOp associate) {
702+
mlir::Value hlfirBase = associate.getBase();
703+
mlir::Value firBase = associate.getFirBase();
704+
cleanups.emplace_back(CallCleanUp{CallCleanUp::ExprAssociate{
705+
hlfir::mayHaveAllocatableComponent(hlfirBase.getType()) ? hlfirBase
706+
: firBase,
707+
associate.getMustFreeStrorageFlag()}});
708+
}
695709

696710
mlir::Value dummy;
697711
// NOTE: the clean-ups are executed in reverse order.
@@ -896,8 +910,7 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
896910
loc, builder, hlfir::Entity{copy}, storageType, "adapt.valuebyref");
897911
entity = hlfir::Entity{associate.getBase()};
898912
// Register the temporary destruction after the call.
899-
preparedDummy.pushExprAssociateCleanUp(
900-
associate.getFirBase(), associate.getMustFreeStrorageFlag());
913+
preparedDummy.pushExprAssociateCleanUp(associate);
901914
} else if (mustDoCopyInOut) {
902915
// Copy-in non contiguous variables.
903916
assert(entity.getType().isa<fir::BaseBoxType>() &&
@@ -924,8 +937,7 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
924937
hlfir::AssociateOp associate = hlfir::genAssociateExpr(
925938
loc, builder, entity, storageType, "adapt.valuebyref");
926939
entity = hlfir::Entity{associate.getBase()};
927-
preparedDummy.pushExprAssociateCleanUp(associate.getFirBase(),
928-
associate.getMustFreeStrorageFlag());
940+
preparedDummy.pushExprAssociateCleanUp(associate);
929941
if (mustSetDynamicTypeToDummyType) {
930942
// Rebox the actual argument to the dummy argument's type, and make
931943
// sure that we pass a contiguous entity (i.e. make copy-in,
@@ -1201,7 +1213,8 @@ genUserCall(Fortran::lower::PreparedActualArguments &loweredActuals,
12011213
// arguments.
12021214
fir::ExtendedValue result = Fortran::lower::genCallOpAndResult(
12031215
loc, callContext.converter, callContext.symMap, callContext.stmtCtx,
1204-
caller, callSiteType, callContext.resultType);
1216+
caller, callSiteType, callContext.resultType,
1217+
callContext.isElementalProcWithArrayArgs());
12051218

12061219
/// Clean-up associations and copy-in.
12071220
for (auto cleanUp : callCleanUps)
@@ -1687,9 +1700,14 @@ class ElementalCallBuilder {
16871700
mlir::Value elemental =
16881701
hlfir::genElementalOp(loc, builder, elementType, shape, typeParams,
16891702
genKernel, !mustBeOrdered, polymorphicMold);
1703+
// If the function result requires finalization, then it has to be done
1704+
// for the array result of the elemental call. We have to communicate
1705+
// this via the DestroyOp's attribute.
1706+
bool mustFinalizeExpr = impl().resultMayRequireFinalization(callContext);
16901707
fir::FirOpBuilder *bldr = &builder;
1691-
callContext.stmtCtx.attachCleanup(
1692-
[=]() { bldr->create<hlfir::DestroyOp>(loc, elemental); });
1708+
callContext.stmtCtx.attachCleanup([=]() {
1709+
bldr->create<hlfir::DestroyOp>(loc, elemental, mustFinalizeExpr);
1710+
});
16931711
return hlfir::EntityWithAttributes{elemental};
16941712
}
16951713

@@ -1743,6 +1761,26 @@ class ElementalUserCallBuilder
17431761
return {};
17441762
}
17451763

1764+
bool resultMayRequireFinalization(CallContext &callContext) const {
1765+
std::optional<Fortran::evaluate::DynamicType> retTy =
1766+
caller.getCallDescription().proc().GetType();
1767+
if (!retTy)
1768+
return false;
1769+
1770+
if (retTy->IsPolymorphic() || retTy->IsUnlimitedPolymorphic())
1771+
fir::emitFatalError(
1772+
callContext.loc,
1773+
"elemental function call with [unlimited-]polymorphic result");
1774+
1775+
if (retTy->category() == Fortran::common::TypeCategory::Derived) {
1776+
const Fortran::semantics::DerivedTypeSpec &typeSpec =
1777+
retTy->GetDerivedTypeSpec();
1778+
return Fortran::semantics::IsFinalizable(typeSpec);
1779+
}
1780+
1781+
return false;
1782+
}
1783+
17461784
private:
17471785
Fortran::lower::CallerInterface &caller;
17481786
mlir::FunctionType callSiteType;
@@ -1804,6 +1842,14 @@ class ElementalIntrinsicCallBuilder
18041842
return {};
18051843
}
18061844

1845+
bool resultMayRequireFinalization(
1846+
[[maybe_unused]] CallContext &callContext) const {
1847+
// FIXME: need access to the CallerInterface's return type
1848+
// to check if the result may need finalization (e.g. the result
1849+
// of MERGE).
1850+
return false;
1851+
}
1852+
18071853
private:
18081854
const Fortran::evaluate::SpecificIntrinsic *intrinsic;
18091855
const fir::IntrinsicArgumentLoweringRules *argLowering;

flang/lib/Lower/HlfirIntrinsics.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ std::optional<hlfir::EntityWithAttributes> Fortran::lower::lowerHlfirIntrinsic(
330330
const Fortran::lower::PreparedActualArguments &loweredActuals,
331331
const fir::IntrinsicArgumentLoweringRules *argLowering,
332332
mlir::Type stmtResultType) {
333+
// If the result is of a derived type that may need finalization,
334+
// we have to use DestroyOp with 'finalize' attribute for the result
335+
// of the intrinsic operation.
333336
if (name == "sum")
334337
return HlfirSumLowering{builder, loc}.lower(loweredActuals, argLowering,
335338
stmtResultType);
@@ -348,6 +351,7 @@ std::optional<hlfir::EntityWithAttributes> Fortran::lower::lowerHlfirIntrinsic(
348351
if (name == "dot_product")
349352
return HlfirDotProductLowering{builder, loc}.lower(
350353
loweredActuals, argLowering, stmtResultType);
354+
// FIXME: the result may need finalization.
351355
if (name == "transpose")
352356
return HlfirTransposeLowering{builder, loc}.lower(
353357
loweredActuals, argLowering, stmtResultType);

flang/lib/Optimizer/Builder/HLFIRTools.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,3 +1021,12 @@ hlfir::cloneToElementalOp(mlir::Location loc, fir::FirOpBuilder &builder,
10211021
elementalAddrOp.getShape(), typeParams,
10221022
genKernel, !elementalAddrOp.isOrdered());
10231023
}
1024+
1025+
bool hlfir::elementalOpMustProduceTemp(hlfir::ElementalOp elemental) {
1026+
for (mlir::Operation *useOp : elemental->getUsers())
1027+
if (auto destroy = mlir::dyn_cast<hlfir::DestroyOp>(useOp))
1028+
if (destroy.mustFinalizeExpr())
1029+
return true;
1030+
1031+
return false;
1032+
}

flang/lib/Optimizer/Builder/Runtime/Derived.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ void fir::runtime::genDerivedTypeDestroy(fir::FirOpBuilder &builder,
3737
builder.create<fir::CallOp>(loc, func, args);
3838
}
3939

40+
void fir::runtime::genDerivedTypeFinalize(fir::FirOpBuilder &builder,
41+
mlir::Location loc, mlir::Value box) {
42+
auto func = fir::runtime::getRuntimeFunc<mkRTKey(Finalize)>(loc, builder);
43+
auto fTy = func.getFunctionType();
44+
auto sourceFile = fir::factory::locationToFilename(builder, loc);
45+
auto sourceLine =
46+
fir::factory::locationToLineNo(builder, loc, fTy.getInput(2));
47+
auto args = fir::runtime::createArguments(builder, loc, fTy, box, sourceFile,
48+
sourceLine);
49+
builder.create<fir::CallOp>(loc, func, args);
50+
}
51+
4052
void fir::runtime::genDerivedTypeDestroyWithoutFinalization(
4153
fir::FirOpBuilder &builder, mlir::Location loc, mlir::Value box) {
4254
auto func = fir::runtime::getRuntimeFunc<mkRTKey(DestroyWithoutFinalization)>(

flang/lib/Optimizer/HLFIR/IR/HLFIRDialect.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,8 @@ mlir::Value hlfir::genExprShape(mlir::OpBuilder &builder,
207207
fir::ShapeOp shape = builder.create<fir::ShapeOp>(loc, shapeTy, extents);
208208
return shape.getResult();
209209
}
210+
211+
bool hlfir::mayHaveAllocatableComponent(mlir::Type ty) {
212+
return fir::isPolymorphicType(ty) || fir::isUnlimitedPolymorphicType(ty) ||
213+
fir::isRecordWithAllocatableMember(hlfir::getFortranElementType(ty));
214+
}

0 commit comments

Comments
 (0)