This is the mail archive of the
fortran@gcc.gnu.org
mailing list for the GNU Fortran project.
RFC: Rework local variable clean-up code and use try-finally
- From: Daniel Kraft <d at domob dot eu>
- To: Fortran List <fortran at gcc dot gnu dot org>
- Date: Wed, 07 Jul 2010 15:09:31 +0200
- Subject: 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