This is the mail archive of the fortran@gcc.gnu.org mailing list for the GNU Fortran project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

RFC: Rework local variable clean-up code and use try-finally


Hi all,

the implementation of the BLOCK construct in gfortran (and especially the clean-up of ALLOCATABLE's and the like inside the BLOCK) is based on the code that originally only handled procedures with local variables. For procedures (despite possibly multiple RETURN statements in the user code), gfortran generates a single exit point, and at this point, all clean-up is done.

Unfortunately, with EXIT/CYCLE/GOTO inside a BLOCK, this is not done and thus such irregular exit methods from a BLOCK currently "skip" the necessary clean-up and thus can cause memory leaks -- this is PR 44709.

As far as I saw, the relevant init/clean-up code is generated by gfc_trans_deferred_vars in trans-decl.c, which calls a lot of other routines for special types of variables (allocatables, packing/unpacking of array dummies and a lot more it seems). Each of those routines takes the current function body, adds its initialization and clean-up to it, and returns the new result. Thus, we get something like:

{
  init_a;
  {
    init_b;
    {
      init_c;
      original body;
      clean_up_c;
    }
    clean_up_b;
  }
  clean_up_a;
}

Now, the solution suggested by Andrew Pinski to ensure clean-up of variables no matter how the block is exited is to use try-finally constructs, which are supported by the gcc middle-end and look semantically like:

try
  {
    body code;
  }
finally
  {
    clean-up code;
  }

(Where clean-up code is executed no matter how the body-block is left. Especially in C++, where exceptions may be thrown at arbitrary times inside the body. But this also makes the middle-end work out how to best handle the clean-up in the face of multiple exit jumps.)

This sounds rather nice and beautiful to me. However, I would like to generate a try-block like:

try
  {
    init_a;
    init_b;
    init_c;
    original body;
  }
finally
  {
    clean_up_c;
    clean_up_b;
    clean_up_a;
  }

for the example from above (instead of a lot of nested try-blocks) for clarity (and so that there's only a single place where the try-finally is created). For this, we have to actually get the clean-up code from the routines creating it as seperate value, rather than just getting it added to the body by them.

Thus my suggestion to restructure those routines: I'd like to add a new (simple) data-structure that represents a block of code with init and clean-up sections, like:

struct gfc_wrapped_block
{
  tree init;
  tree body;
  tree clean_up;
};

Together with some helper-routines:

gfc_start_wrapped_block: Given body, create a new one with for now empty init/clean-up.

gfc_add_init_cleanup: Add a pair of init and clean-up expressions to the block. I think it is structurally nicer to do it always in pairs, but may be easier to use with seperate "add init" and "add clean-up" routines (so that for instance not all init/clean-up has to be added to a single block by the user routines). What do you think?

gfc_finish_wrapped_block: Build a try-finally middle-end expression for the block.

Then all those routines that generate init/clean-up code (for instance, gfc_trans_deferred_array but also many, many others) would be rewritten to update such a gfc_wrapped_block.

So far my suggestion. What do you think? Are there some details I missed (or reasons why this would not work), maybe in special situations (e.g., CLASS dummy arguments)?

Do you have a good suggestion for a name of the struct instead of gfc_wrapped_block? Tobias suggested gfc_try_finally which sounds nice, but this block is not directly related to try-finally in my opinion, so I'm not sure whether I want to fix this in the name.

And finally: It may be easier to add init/clean-up always to the end of the existing init/clean-up blocks internally, leading to a structure like:

try
  {
    init_a;
    init_b;
    init_c;
    body code;
  }
finally
  {
    clean_up_a;
    clean_up_b;
    clean_up_c;
  }

(Note the reversed order of clean-ups.) Would this also be possible (i.e., a/b/c are "independent of each other" so that clean_up_c does not depend on a still available) or does this lead to problems in special situations? What do you think? (Of course, the original version is cleaner and nicer -- but if this is also possible, it may be easier to do because we can just always add the new blocks to the end of init/clean_up. Are there routines to add expressions to the *front* of a tree?)

Cheers,
Daniel

--
http://www.pro-vegan.info/
--
Done:  Arc-Bar-Cav-Ran-Rog-Sam-Tou-Val-Wiz
To go: Hea-Kni-Mon-Pri


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]