This is an archive of the discontinued LLVM Phabricator instance.

[flang][hlfir] Enable assignments with allocatable components.
ClosedPublic

Authored by vzakhari on Jun 6 2023, 1:33 PM.

Details

Summary

The TODO was left there to verify that Assign() runtime handles
overlaps of allocatable components. It did not, and this change-set
fixes it. Note that the same Assign() issue can be reproduced
without HLFIR. In the following example the LHS would be reallocated
before value of RHS (essentially, the same memory) is read:

program main
  type t1
     integer, allocatable :: a(:)
  end type t1
  type(t1) :: x, y
  allocate(x%a(10))
  do i =1,10
     x%a(i) = 2*i
  end do
  x = x
  print *, x%a
  deallocate(x%a)
end program main

The test's output would be incorrect (though, this depends on the memory
reuse by malloc):
0 0 0 0 10 12 14 16 18 20

It is very hard to add a Flang unittest exploiting derived types.

Diff Detail

Event Timeline

vzakhari created this revision.Jun 6 2023, 1:33 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 6 2023, 1:33 PM
vzakhari requested review of this revision.Jun 6 2023, 1:33 PM
vzakhari edited the summary of this revision. (Show Details)
vzakhari edited the summary of this revision. (Show Details)
klausler added inline comments.Jun 6 2023, 1:51 PM
flang/runtime/assign.cpp
333

Did you move this block of code in order to ensure LHS finalization prior to invoking defined assignment? Be advised, finalization applies only to intrinsic assignments (7.5.6.3); a defined assignment subroutine is responsible for calling any final subroutines. Programs with defined assignments basically have to implement everything, including storage allocation and deallocation and finalization, so I get worried when I see other stuff in here that might inappropriately take place "automatically" before a defined assignment procedure gets called.

334

See 10.2.1.3 para 13 for how defined assignment applies during intrinsic assignment with allocatable components with derived types.

vzakhari added inline comments.Jun 6 2023, 2:29 PM
flang/runtime/assign.cpp
333

Thank you for the references! I tried to follow them with my changes.

I moved the code so that the deallocation and reallocation with AllocateAssignmentLHS kicks in for the component assignment case, i.e. when we call Assign recursively below at line 462. Note that line 462 is currently the only case when we call Assign with CanBeDefinedAssignment flag. Also this call will not set NeedFinalization, so the finalization will not happen. And it will explicitly sey DeallocateLHS to conform to the behavior for allocatable components.

Of course, the main goal of moving this code here was to reuse the MayAlias logic above.

klausler added inline comments.Jun 6 2023, 3:20 PM
flang/runtime/assign.cpp
333

If *any* deallocation or finalization takes place in a case where defined assignment should be called, it's incorrect. Calling AllocateAssignmentLHS() in a case where defined assignment should be called is incorrect. In short, if control arrives at Assign() for a case that turns out to be defined assignment, Assign() should do *nothing* except call defined assignment. The one exception is the creation and population of a temporary to hold the value of the RHS if it aliases any part of the LHS, since the RHS of a defined assignment must be processed as an expression, not as a variable.

vzakhari added inline comments.Jun 6 2023, 3:39 PM
flang/runtime/assign.cpp
333

As I understand, by convention with the compiler Assign is not called for the top-level assignments of variables with defined assignments (e.g. CanBeDefinedAssignment flag is not set by any external entry point that calls Assign). The compiler is supposed to call the TBP directly in such cases. Assign may only call TBP for cases where defined assignment is provided for the component of a derived type, e.g.:

Type :: t1
  Integer :: i = 42
Contains
  Procedure :: asgn
  Generic :: Assignment (=) => asgn
End Type
Type :: t2
  Type(t1), Allocatable :: c
End Type

When a variable of type t2 is assigned (with intrinsic assignment) using Assign, 10.2.1.3 para 13 states that the component must be deallocated (if it is allocated) and allocated with the same dynamic type as component of the RHS (if the component is allocated). This is what we have been doing around line 450. This change moves the actions that were done around line 450 into the recursion call, which also makes sure the aliasing is taken into account. I could do the same by "duplicating" the aliasing handling in case typeInfo::Component::Genre::Allocatable below, but I decided to let the recursion handle it.

In other words, with the current agreement between the compiler and runtime, CanBeDefinedAssignment flag can reach here only if an assignment to an allocatable/automatic component is happening as part of an intrinsic assignment of a derived type, in which case the deallocation and AllocateAssignmentLHS is required.

Please let me know if you would prefer the code to be organized differently and how you would like to see it done.

klausler accepted this revision.Jun 6 2023, 4:08 PM

Please address the pre-merge CI build failures before merging.

This revision is now accepted and ready to land.Jun 6 2023, 4:08 PM

Please address the pre-merge CI build failures before merging.

Thank you for the review! I will fix the source info patterns.

vzakhari updated this revision to Diff 529094.Jun 6 2023, 5:03 PM

Rebased and updated the test before merging.