This is an archive of the discontinued LLVM Phabricator instance.

[flang] Avoid cycles during instantiation of derived types
ClosedPublic

Authored by rogfer01 on Aug 29 2023, 9:05 AM.

Details

Summary

Derived-type-spec (such as type(t)) typically cause the instantiation of a class which is also used to define the offsets of its data components and the size of the class.

Fortran derived types are always "completely" defined (i.e., no incomplete / opaque derived types exist on which we can build a pointer to them like in C/C++) so they can have their offsets always computed.

However, we must be careful not to instantiate a derived type while it is being defined. This can happen due to cycles introduced by forward references, such as the one below.

type t1
  type(t2), pointer :: b ! (A)
end type t1

type :: t2 ! (B)
  type(t1), pointer :: a ! (C)
end type t2 ! (D)

At (A), flang determines that this is a forward declaration so no instantiation happens.

At (B), flang determines t2 is not a forward declaration anymore, because we are defining it.

At (C), flang chooses to instantiate t1. Instantiation of t1 finds the field b at (A). Now t2 is not a forward declaration anymore, so it can be instantiated. But at this point the field a has not been added to t2, so we compute the size of an empty class. Because this computation is done just once, we end emitting a wrong derived type descriptor with a sizeinbytes field set to 0.

Because these kind of cycles can only happen via forward referenced derived types specifiers, the idea here is to avoid instantiating the derived type being defined (i.e. t2) until (D). Keeping the attribute "is forward reference" on until (D) avoids that.

Fixes https://github.com/llvm/llvm-project/issues/64973

Diff Detail

Event Timeline

rogfer01 created this revision.Aug 29 2023, 9:05 AM
Herald added projects: Restricted Project, Restricted Project. · View Herald Transcript
rogfer01 requested review of this revision.Aug 29 2023, 9:05 AM
rogfer01 added a comment.EditedAug 29 2023, 9:06 AM

My familiarity with this part of the code is limited, so I welcome feedback on better integrating this with the existing machinery. Thanks!

rogfer01 edited the summary of this revision. (Show Details)Aug 29 2023, 9:07 AM
rogfer01 planned changes to this revision.EditedAug 29 2023, 10:33 PM

Ok, I realised this is incomplete, an even simpler testcase still shows the issue:

program main
  implicit none

  type t1
    type(t2), pointer :: b
  end type t1

  type :: t2  ! (B)
    type(t1) :: a  ! no pointer
  end type t2 ! (D)
end program main

I will explore a different approach. Maybe we can not forget in (B) that t2 had been mentioned as a forward reference, this would prevent instantiation. Then we can drop that bit in (D).

rogfer01 updated this revision to Diff 554589.EditedAug 29 2023, 11:47 PM

ChangeLog:

  • Remember that a derived type is forward declared until the end of the derived type definition. This way we avoid instantiating it while not completely defined.
PeteSteinfeld accepted this revision.Sep 6 2023, 6:41 AM

Thanks for finding and fixing this!

All builds and tests correctly and looks good to me. It would be good for @klausler to take a look, though.

This revision is now accepted and ready to land.Sep 6 2023, 6:41 AM

I understand the problem but I'm not sure that this is a complete solution. Instantiating t1 at the end of t2 won't work if t1 also had a forward reference to another type t3. I'll have to think about this some more. It might be simpler and more complete to just defer all instantiations of pointer and allocatable component (but not variable) types unless the component appears in a specification expression. They'll get instantiated at the end of the specification part if they remain uninstantiated.

I understand the problem but I'm not sure that this is a complete solution. Instantiating t1 at the end of t2 won't work if t1 also had a forward reference to another type t3. I'll have to think about this some more. It might be simpler and more complete to just defer all instantiations of pointer and allocatable component (but not variable) types unless the component appears in a specification expression. They'll get instantiated at the end of the specification part if they remain uninstantiated.

Right. Apologies, @klausler, I may have confused you with the original description which I forgot to update along with my last change. I will update it.

In my last change, what I did was to keep marking t2 as forward referenced (if it had been marked as such because of such reference) until the end of its definition. The existing code does not attempt to instantiate those types. This means that while checking the definition of t2 we instantiate t1 and the latter (i.e. t1) won't attempt to instantiate t2. Once the whole type definition of t2 is complete we can instantiate it as needed, so we unmark it as forward referenced.

rogfer01 edited the summary of this revision. (Show Details)Sep 7 2023, 12:15 AM
klausler added inline comments.Sep 8 2023, 12:37 PM
flang/lib/Semantics/resolve-names.cpp
5260–5266

It is weird to define a lambda with no arguments just so you can call it once. Just use a local variable.

rogfer01 updated this revision to Diff 556939.Sep 18 2023, 3:25 AM

ChangeLog:

  • Simplify the DerivedTypeDetails creation. Don't use a lambda to get an rvalue. Instead, std::move a local variable to the symbol.
flang/lib/Semantics/resolve-names.cpp
5260–5266

Right, I didn't want to leave an empty (moved) husk after a std::move but I guess I'm just trying to hard.

rogfer01 marked an inline comment as done.Sep 18 2023, 3:26 AM
klausler accepted this revision.Sep 19 2023, 2:16 PM