Skip to content

Commit 49c4043

Browse files
committed
[flang] Finalize allocatable components in expression results
An expression result is a "data entity" (F'2023 3.41), and thus can be "finalizable" (3.71); however, they are not finalized in general because there is no item in the list of events that trigger finalization (7.5.6.3p2) that applies, apart from two relevant exceptions. One of them we handle correctly (allocatable function results) we handle correctly, the other we do not. The second exception is the case of allocated allocatables which may appear as subobjects in an expression result. They are automatically deallocated (9.7.3.2p9), and deallocation is on that list of finalization-triggering events (7.5.6.3p2). So this patch makes sure to finalize any allocatable finalizable subobject, unless the destruction is part of the left-hand side of an intrinsic assignment -- in which case it will have already been finalized (7.5.6.3p1) anyway. Note: the "stop 171" case in the test llvm-test-suite/Fortran/gfortran/regression/finalize_38.f90 will now fail, and a companion patch will disable that test when this patch is merged.
1 parent 2775c79 commit 49c4043

File tree

5 files changed

+49
-35
lines changed

5 files changed

+49
-35
lines changed

flang-rt/include/flang-rt/runtime/work-queue.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,18 @@ class DestroyTicket : public ImmediateTicketRunner<DestroyTicket>,
298298
private ComponentsOverElements {
299299
public:
300300
RT_API_ATTRS DestroyTicket(const Descriptor &instance,
301-
const typeInfo::DerivedType &derived, bool finalize)
301+
const typeInfo::DerivedType &derived, bool finalize,
302+
bool isNestedFinalizationPossible)
302303
: ImmediateTicketRunner<DestroyTicket>{*this},
303304
ComponentsOverElements{instance, derived}, finalize_{finalize},
305+
isNestedFinalizationPossible_{isNestedFinalizationPossible},
304306
fixedStride_{instance.FixedStride()} {}
305307
RT_API_ATTRS int Begin(WorkQueue &);
306308
RT_API_ATTRS int Continue(WorkQueue &);
307309

308310
private:
309311
bool finalize_{false};
312+
bool isNestedFinalizationPossible_{false};
310313
common::optional<SubscriptValue> fixedStride_;
311314
};
312315

@@ -479,11 +482,15 @@ class WorkQueue {
479482
}
480483
}
481484
RT_API_ATTRS int BeginDestroy(const Descriptor &descriptor,
482-
const typeInfo::DerivedType &derived, bool finalize) {
485+
const typeInfo::DerivedType &derived, bool finalize,
486+
bool isNestedFinalizationPossible) {
483487
if (runTicketsImmediately_) {
484-
return DestroyTicket{descriptor, derived, finalize}.Run(*this);
488+
return DestroyTicket{
489+
descriptor, derived, finalize, isNestedFinalizationPossible}
490+
.Run(*this);
485491
} else {
486-
StartTicket().u.emplace<DestroyTicket>(descriptor, derived, finalize);
492+
StartTicket().u.emplace<DestroyTicket>(
493+
descriptor, derived, finalize, isNestedFinalizationPossible);
487494
return StatContinue;
488495
}
489496
}

flang-rt/lib/runtime/assign.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,8 @@ RT_API_ATTRS int AssignTicket::Begin(WorkQueue &workQueue) {
373373
// is executed, any noncoarray allocated allocatable subobject of the
374374
// variable is deallocated before the assignment takes place."
375375
if (int status{
376-
workQueue.BeginDestroy(to_, *toDerived_, /*finalize=*/false)};
376+
workQueue.BeginDestroy(to_, *toDerived_, /*finalize=*/false,
377+
/*isNestedFinalizationPossible=*/false)};
377378
status != StatOk && status != StatContinue) {
378379
return status;
379380
}
@@ -683,8 +684,9 @@ RT_API_ATTRS int DerivedAssignTicket<IS_COMPONENTWISE>::Continue(
683684
if (toDesc->IsAllocated()) {
684685
if (this->phase_ == 0) {
685686
if (componentDerived && !componentDerived->noDestructionNeeded()) {
686-
if (int status{workQueue.BeginDestroy(
687-
*toDesc, *componentDerived, /*finalize=*/false)};
687+
if (int status{workQueue.BeginDestroy(*toDesc, *componentDerived,
688+
/*finalize=*/false,
689+
/*isNestedFinalizationPossible=*/false)};
688690
status != StatOk) {
689691
this->phase_++;
690692
return status;

flang-rt/lib/runtime/derived-api.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ void RTDEF(Destroy)(const Descriptor &descriptor) {
4646
if (!derived->noDestructionNeeded()) {
4747
// TODO: Pass source file & line information to the API
4848
// so that a good Terminator can be passed
49-
Destroy(descriptor, true, *derived, nullptr);
49+
Destroy(descriptor, /*finalize=*/true, *derived, nullptr);
5050
}
5151
}
5252
}
@@ -144,6 +144,10 @@ bool RTDEF(ExtendsTypeOf)(const Descriptor &a, const Descriptor &mold) {
144144
}
145145
}
146146

147+
// The object will be destroyed without finalization at the top level,
148+
// but allocated allocatable subobjects must be deallocated, and
149+
// deallocation is a finalization-triggering event, so they will
150+
// be finalized regardless.
147151
void RTDEF(DestroyWithoutFinalization)(const Descriptor &descriptor) {
148152
if (const DescriptorAddendum * addendum{descriptor.Addendum()}) {
149153
if (const auto *derived{addendum->derivedType()}) {

flang-rt/lib/runtime/derived.cpp

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -396,24 +396,28 @@ RT_API_ATTRS int FinalizeTicket::Continue(WorkQueue &workQueue) {
396396

397397
// The order of finalization follows Fortran 2018 7.5.6.2, with
398398
// elementwise finalization of non-parent components taking place
399-
// before parent component finalization, and with all finalization
400-
// preceding any deallocation.
399+
// before parent component finalization, and with all "top level"
400+
// finalization preceding any deallocation.
401401
RT_API_ATTRS void Destroy(const Descriptor &descriptor, bool finalize,
402402
const typeInfo::DerivedType &derived, Terminator *terminator) {
403403
if (descriptor.IsAllocated() && !derived.noDestructionNeeded()) {
404404
Terminator stubTerminator{"Destroy() in Fortran runtime", 0};
405405
WorkQueue workQueue{terminator ? *terminator : stubTerminator};
406-
if (workQueue.BeginDestroy(descriptor, derived, finalize) == StatContinue) {
406+
if (workQueue.BeginDestroy(descriptor, derived, finalize,
407+
/*isNestedFinalizationPossible=*/!finalize) == StatContinue) {
407408
workQueue.Run();
408409
}
409410
}
410411
}
411412

412413
RT_API_ATTRS int DestroyTicket::Begin(WorkQueue &workQueue) {
413-
if (finalize_ && !derived_.noFinalizationNeeded()) {
414-
if (int status{workQueue.BeginFinalize(instance_, derived_)};
415-
status != StatOk && status != StatContinue) {
416-
return status;
414+
if (finalize_) {
415+
isNestedFinalizationPossible_ = false;
416+
if (!derived_.noFinalizationNeeded()) {
417+
if (int status{workQueue.BeginFinalize(instance_, derived_)};
418+
status != StatOk && status != StatContinue) {
419+
return status;
420+
}
417421
}
418422
}
419423
return StatContinue;
@@ -440,8 +444,13 @@ RT_API_ATTRS int DestroyTicket::Continue(WorkQueue &workQueue) {
440444
if (d.IsAllocated()) {
441445
if (componentDerived && !componentDerived->noDestructionNeeded() &&
442446
phase_ == 0) {
443-
if (int status{workQueue.BeginDestroy(
444-
d, *componentDerived, /*finalize=*/false)};
447+
// Per F'2023 7.5.6.3p2, deallocations are finalization-triggering
448+
// events -- so if an allocatable component was not finalized
449+
// before, and we're not in the left-hand side of an intrinsic
450+
// assignment, it must be finalized now.
451+
if (int status{workQueue.BeginDestroy(d, *componentDerived,
452+
/*finalize=*/!finalize_ && isNestedFinalizationPossible_,
453+
isNestedFinalizationPossible_)};
445454
status != StatOk) {
446455
++phase_;
447456
return status;
@@ -465,8 +474,8 @@ RT_API_ATTRS int DestroyTicket::Continue(WorkQueue &workQueue) {
465474
++j, p += *fixedStride_) {
466475
compDesc.set_base_addr(p);
467476
++elementAt_;
468-
if (int status{workQueue.BeginDestroy(
469-
compDesc, compType, /*finalize=*/false)};
477+
if (int status{workQueue.BeginDestroy(compDesc, compType,
478+
/*finalize=*/false, isNestedFinalizationPossible_)};
470479
status != StatOk) {
471480
return status;
472481
}
@@ -481,8 +490,8 @@ RT_API_ATTRS int DestroyTicket::Continue(WorkQueue &workQueue) {
481490
instance_.ElementComponent<char>(subscripts_, component_->offset()),
482491
component_->rank(), extents);
483492
Advance();
484-
if (int status{
485-
workQueue.BeginDestroy(compDesc, compType, /*finalize=*/false)};
493+
if (int status{workQueue.BeginDestroy(compDesc, compType,
494+
/*finalize=*/false, isNestedFinalizationPossible_)};
486495
status != StatOk) {
487496
return status;
488497
}

flang/docs/Extensions.md

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -817,20 +817,12 @@ end
817817
`INDEX` include an optional `BACK=` argument, but it doesn't actually
818818
work.
819819

820-
* Allocatable components of array and structure constructors are deallocated
821-
after use without calling final subroutines.
822-
The standard does not specify when and how deallocation of array and structure
823-
constructors allocatable components should happen. All compilers free the
824-
memory after use, but the behavior when the allocatable component is a derived
825-
type with finalization differ, especially when dealing with nested array and
826-
structure constructors expressions. Some compilers call final routine for the
827-
allocatable components of each constructor sub-expressions, some call it only
828-
for the allocatable component of the top level constructor, and some only
829-
deallocate the memory. Deallocating only the memory offers the most
830-
flexibility when lowering such expressions, and it is not clear finalization
831-
is desirable in such context (Fortran interop 1.6.2 in F2018 standards require
832-
array and structure constructors not to be finalized, so it also makes sense
833-
not to finalize their allocatable components when releasing their storage).
820+
* Allocatable components nested in expression results are finalized before being
821+
deallocated. F'2023 identifies expression results as "data entities", and
822+
the deallocation of non-coarray allocatables as being an event that triggers
823+
finalization. Compilers differ widely in their finalization behavior in
824+
expression results, especially with structure constructors, but at least two
825+
others match this interpretation and it seems clear enough in the standard.
834826

835827
* F'2023 19.4 paragraph 5: "If integer-type-spec appears in data-implied-do or
836828
ac-implied-do-control it has the specified type and type parameters; otherwise

0 commit comments

Comments
 (0)