F2003 Finalization -- progress and problems

Tobias Burnus burnus@net-b.de
Tue Jun 10 10:02:00 GMT 2008


Hi Daniel,

Daniel Kraft wrote in an earlier email:
> After finishing the code, I want to write some little documentation
> about how finalization works and is implemented if you want so in the
> future everyone has it easier to work on it (now that I still know how
> it works...), if you point me where to write this.
GREAT idea! I think gfc-internals.texi is the best place, see:
http://gcc.gnu.org/onlinedocs/gfc-internals/
(An alternative would be the Wiki, but I think gfc-internals.texi is 
better.)

> c) what exactly does that "specification expression in scoping unit"
> mean?  But otherwise, this sounds similar to b).

"If a specification expression in a scoping unit references a function, 
the result is finalized before execution of the first executable 
statement in the scoping unit."

Well, a specification expression is for instance

subroutine bar(x)
  integer :: x
  character(len=func(x)) :: str

which could be translated into the following tree-like pseudo code:
 {
    type(func_type) *tmp
    tmp = func(x)
    malloc(str, tmp->len)
    finalize(tmp)
 }

(Assuming that func returns an a derived type and "=" is overloaded to 
return the "len" component.)

If there are several specification expressions, one could finalize them 
either directly after using them (as we will probably do) or one could 
defer it and finalize them one after another just before first 
executable statement comes.


Actually, there might be more restrictions:

"7.1.6 Specification expression"
"R729 specification-expr is scalar-int-expr"
"C710 (R729) The scalar-int-expr shall be a restricted expression."
"A *restricted expression* is an expression [...], and where any final 
subroutine that is invoked is *pure*."


> b) results of functions and structure-constructors after they have been
> used:  I'm not sure how this could be done in resolution-phase (without
> "rewriting" the code and introducing temporaries), but maybe in trans
> this will be easier.  Any ideas and tipps? 
I don't quite understand what with "after they have been used" is meant 
- nor can I find it in the standard. (Currently, I have no opinion 
whether on should use trans*.c or resolve*.c.)

> e) intrinsic assignment: for ordinary assignments this is handled
What do you mean by ordinary assignments? If you mean a simple

  type(t) :: x, y
  y = x

then this is an intrinsic assignment unless you have "assignment(=)" 
anywhere. Using

  interface assignment(=)
      subroutine assign_t(x,y)

the finalization happens automatically as the LHS argument matches a 
INTENT(OUT) in assign_t.

> OTOH, for assignments inside WHERE/FORALL I still have to look into 
> it; maybe this
> will be easy, too, but there's at least something I'm thinking about:
>
> WHERE (arr == 2)
>      arr = 5
> END WHERE
>
> Should this finalize the whole masked arr at once or each element before
> it is overwritten?  Can there be a similar unclarity for FORALL?
I really dislike WHERE as it is (at least to me) a bit intransparent 
what happens there. Maybe something to ask at lang.comp.fortran.

 From the standard:

"If a nonelemental function reference occurs in the expr or variable of 
a where-assignment-stmt or in a mask-expr, the function is evaluated 
without any masked control; that is, all of its argument expressions are 
fully evaluated and the function is fully evaluated. If the result is an 
array and the reference is not within the argument list of a 
nonelemental function, elements corresponding to true values in the 
control mask are selected for use in evaluating the expr, variable or 
mask-expr.
If an elemental operation or function reference occurs in the expr or 
variable of a where-assignment-stmt or in a mask-expr, and is not within 
the argument list of a nonelemental function reference, the operation is 
performed or the function is evaluated only for the elements 
corresponding to true values of the control mask."

I read this as follows, but now guarantees that I interpret the standard 
correctly.

{
  tmp = function(array)
  finalize(array(mask))
  array(mask) = tmp(mask)
}

or


{
  finalize(array(mask))
  array(mask) = array2(mask)
}

or

{
  finalize(array(mask))
  array(mask) = elementalfunc(array2(mask))
}


FORALL: I have not yet fully understand how this should be handled, but 
note:

"C738 (R756) Any procedure referenced in a forall-body-construct, 
including one referenced by a defined operation, assignment, or 
*finalization*, shall be a *pure* procedure."

And for PURE procedures:

"C1273 Any procedure referenced *in a pure subprogram*, including one 
referenced via a defined operation, assignment, or *finalization*, shall 
be *pure*."

See also examples "C.1.8 Final subroutines", though they do not apply 
yet (parameterized derived types and type extension is not yet implemented).


Daniel Kraft wrote:
> Ok, so in this case I'll assume INTENT(INOUT) and don't finalize the 
> arguments. 
Yes. Only if the interface is known (explicit interface) and the intent 
== INTENT_OUT.

> I was more thinking about the expression "x + foobar (x)" in itself 
> rather than the I/O; that's because foobar (x) changes the value of x 
> and I wanted to know whether it was defined or not if the first x had 
> the earlier or later value.
Found it. "7.1.8 Evaluation of operations":
"[...] The evaluation of a function reference shall neither affect nor 
be affected by the evaluation of any other entity within the statement. 
If a function reference causes definition or undefinition of an actual 
argument of the function, that argument or any associated entities shall 
not appear elsewhere in the same statement. However, execution of a 
function reference in the logical expression in an IF statement 
(8.1.2.4), the mask expression in a WHERE statement (7.4.3.1), or the 
subscripts and strides in a FORALL statement (7.4.4) is permitted to 
define variables in the statement that is conditionally executed."

In other words: The program is invalid and the compiler can do anything. 
(The saying goes this includes starting WW3.)

> I was unsure about whether to call the array-finalizer routine or the 
> scalar one for each element.  As in theory they could be totally 
> different and I think the standard should somehow define which one 
> ought to be called.  But maybe this isn't the case and we "are free to 
> choose"?  I'd find it more intuitive if we finalized the whole array 
> as for the user an ELEMENTAL-procedure is just like one taking a whole 
> array.  So try to finalize the array on a whole before the call and 
> any scalarization of it.
I think the standard is rather clear about it:

"When an entity is finalized, the following steps are carried out in 
sequence: (1) If the dynamic type of the entity has a final subroutine 
whose dummy argument has the same kind type parameters and rank as the 
entity being finalized, it is called with the entity as an actual 
argument. Otherwise, if there is an elemental final subroutine whose 
dummy argument has the same kind type parameters as the entity being 
finalized, it is called with the entity as an actual argument. 
Otherwise, no subroutine is called at this point."

And: "When a procedure is invoked, a nonpointer, nonallocatable object 
that is an actual argument associated with an INTENT(OUT) dummy argument 
is finalized."

The actual argument is an array and thus one causes the array version of 
the finalization subroutine (if present).

>> +    TYPE(yes_t), ALLOCATABLE :: alloc_vector(:)
>> +    ! alloc_vector is deallocated automatically here
>> +    ! XXX: Or not?
>
> BTW, I was a bit surprised when I first read about this nice feature 
> of automatic deallocation (and automatic allocation the like)--Fortran 
> isn't C++ :D
That is one of the nice features of Fortran: With allocatable, one 
cannot have loose memory. If one tries to allocate an already allocated 
variable, one gets an error; and when the variable goes out of scope, it 
is automatically deallocated.

Additionally, all variables are by default the only reference to a chunk 
of memory (C: "restricted"), which helps with the alias analysis. Only 
if a variable has the attribute TARGET (or when it is a POINTER), there 
might be some (other) pointer pointing to the same memory. This helps 
with the compiler alias analysis and makes the program potentially faster.

Tobias



More information about the Fortran mailing list