This is an archive of the discontinued LLVM Phabricator instance.

[flang] End IO statement before finalization to avoid recursive IO
AbandonedPublic

Authored by clementval on Feb 2 2023, 12:55 AM.

Details

Summary

Derived type finalization can be tied to the statement context in the
case of executable construct that references a non-pointer function.
Since the final subroutine can potentially do so IO, make sure the
current IO is ended before emitting finalization of the statement
context.

There might be other IO runtime that need the same update but it will be done
with proper examples.

Diff Detail

Event Timeline

clementval created this revision.Feb 2 2023, 12:55 AM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptFeb 2 2023, 12:55 AM
clementval requested review of this revision.Feb 2 2023, 12:55 AM
clementval edited the summary of this revision. (Show Details)Feb 2 2023, 12:56 AM
jeanPerier added inline comments.Feb 2 2023, 2:30 AM
flang/lib/Lower/IO.cpp
502

Unfortunately it is not possible to move this StatementContext before. It is here because IO with error recovery (IOSTAT, ERRMSG...) will create conditional IO inside fir.if (see the makeNextConditionalOn just above).

For instance:

subroutine conditional_io(stat)
  integer :: stat
  real :: a(100), b(100)
  write (6, *, iostat=stat) 42, a+b
end subroutine

Should now fail with: "operand #0 does not dominate this use" (we really should have had this one has a regression test, sorry about that).

The previous IR for this code looked like:

%5 = fir.call @_FortranAioEnableHandlers(%4, %true, %false, %false_1, %false_2, %false_3) fastmath<contract> : (!fir.ref<i8>, i1, i1, i1, i1, i1) -> none
%c42_i32 = arith.constant 42 : i32
// outputing 42, may fail for whatever reasons.
%6 = fir.call @_FortranAioOutputInteger32(%4, %c42_i32) fastmath<contract> : (!fir.ref<i8>, i32) -> i1
// Because of the IOSTAT requirements, the IO statement evaluation is stopped at the first error, so "A+B" io-item evaluation and IO is conditional.
fir.if %6 {
  // ...
  %12 = fir.allocmem !fir.array<100xf32>
  // computing A+B inside %12.
  %18 = fir.embox %12(%17) : (!fir.heap<!fir.array<100xf32>>, !fir.shape<1>) -> !fir.box<!fir.array<100xf32>>
  %19 = fir.convert %18 : (!fir.box<!fir.array<100xf32>>) -> !fir.box<none>
  %20 = fir.call @_FortranAioOutputDescriptor(%4, %19) fastmath<contract> : (!fir.ref<i8>, !fir.box<none>) -> i1
  // The statement context clean-up happens here
  fir.freemem %12 : !fir.heap<!fir.array<100xf32>>
}
%7 = fir.call @_FortranAioEndIoStatement(%4) fastmath<contract> : (!fir.ref<i8>) -> i32
// you patch is moving it here, where %12 is not accessible. 
fir.store %7 to %arg0 : !fir.ref<i32>

Things can even get funnier if one use IO implied do:

subroutine conditional_io(stat)
  integer :: stat
  real :: a(100)
  write (6, *, iostat=stat) (a(i*2-1:i*2)+42, i=1,50)
end subroutine

In this case, the allocation/deallocation happens inside an if/then/else that is inside a fir.iterate_while before FortranAioEndIoStatement.

I do not have a brilliant idea to solve this. It seems we would want to propagate the temps through the iter_while (that by the way, can be a fir.do_loop is there is no IO error recoivery) and the if/then/else.

But if you have loops with temps created at each iteration, then piling the finalization/deallocation is far from ideal (you will need an array to store the descriptors...).

Maybe there can be a clever solution in the IO runtime that would somehow allow the finalizer to call IO.

clementval abandoned this revision.Feb 2 2023, 2:58 AM
clementval added inline comments.
flang/lib/Lower/IO.cpp
502

I thought there was a reason to do the statement context finalization after the end call. Thanks for explanation. I'll look at another solution.