Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Does any compiler implement finalization correctly for function results?

116 views
Skip to first unread message

Peter Klausler US

unread,
Feb 2, 2023, 2:16:20 PM2/2/23
to
Consider this simple test:

module m
type t
integer n
contains
final :: final
end type
contains
type(t) function func(n)
integer, intent(in) :: n
func = t(n)
end function
impure elemental subroutine final(x)
type(t), intent(in out) :: x
print *, 'final: ', x%n
end subroutine
end module

program test
use m
character(20) buffer
1 format(5I2)
write(buffer,1) (func(j),j=1,5)
print *, 'wrote: ', buffer
end

Fortran 2018 is fairly clear on the expected behavior: 7.5.6.3p5 "If an executable construct references a nonpointer function, the result is finalized after execution of the innermost executable construct containing the reference." So one would expect five lines "final: 1", "final: 2", &c. to "final: 5", followed by "wrote: 1 2 3 4 5".

Tried it on recent releases of six currently maintained Fortran compilers, and none of them produced these expected results. Instead, I observed:

Compiler A:
wrote: 1 2 3 4 5

Compilers B & C:
final: 0
final: 1
final: 2
final: 3
final: 4
final: 5
wrote: 1 2 3 4 5

Compiler D:
final: 0
final: 0
final: 0
final: 0
final: 0
wrote: 1 2 3 4 5

Compiler E:
final: 0
final: 1
final: -1955471140
final: 2
final: -1955471140
final: 3
final: -1955471140
final: 4
final: -1955471140
final: 5
wrote: 1 2 3 4 5

Compiler F:
I/O recursion detected.

If you know of a compiler that produces something other than these, please let me know about it.

Steven G. Kargl

unread,
Feb 2, 2023, 2:25:49 PM2/2/23
to
On Thu, 02 Feb 2023 11:16:18 -0800, Peter Klausler US wrote:

>
> If you know of a compiler that produces something other
> than these, please let me know about it.

gfortran has a number of known issues with finalization.
There is a patch that supposes addresses most/all open bug
reports, which might make it into gcc 13.1 when released.

https://gcc.gnu.org/pipermail/fortran/2023-January/058723.html

--
steve

Steve Lionel

unread,
Feb 2, 2023, 2:48:01 PM2/2/23
to
On 2/2/2023 2:16 PM, Peter Klausler US wrote:
> If you know of a compiler that produces something other than these, please let me know about it.

Compiler G
final: 0
final: 1
final: 0
final: 2
final: 0
final: 3
final: 0
final: 4
final: 0
final: 5
wrote: 1 2 3 4 5

(This is similar to your compiler E, however, so these may be the same.)

I don't understand what is happening here. From my stepping through with
a debugger, using a compiler that gives the B/C result, there is an
uninitialized instantiation of the type that is finalized before the
function returns. In the B/C case, this happens only once - clearly in
compiler G (E) it happens on each call to func.

I hope that someone can explain this, because I can't.

(The recursive I/O error is just incorrect.)
--
Steve Lionel
ISO/IEC JTC1/SC22/WG5 (Fortran) Convenor
Retired Intel Fortran developer/support
Email: firstname at firstnamelastname dot com
Twitter: @DoctorFortran
LinkedIn: https://www.linkedin.com/in/stevelionel
Blog: https://stevelionel.com/drfortran
WG5: https://wg5-fortran.org

FortranFan

unread,
Feb 2, 2023, 3:19:32 PM2/2/23
to
On Thursday, February 2, 2023 at 2:48:01 PM UTC-5, Steve Lionel wrote:

> ..
> (The recursive I/O error is just incorrect.) ..

@Steve Lionel,

Not sure what you mean by, "The recursive I/O error is just incorrect."

Arguably one can view the code in the original post to attempt I/O data transfer in a similar manner as the following silly snippet:

print *, f(1)
contains
integer function f(a)
integer, intent(in) :: a
f = a + 1
print *, f
end function
end

But this silly code does NOT conform, if I recall correctly the last I checked the standard. However the processors are not required to detect and report this and at run-time, some tend to *freeze up* during the data transfer to the same IO unit. Thus the processor in question might be trying to help prevent such a problematic scenario and report out the nonconforming behavior to the programmer?

Steve Lionel

unread,
Feb 2, 2023, 3:31:16 PM2/2/23
to
On 2/2/2023 3:19 PM, FortranFan wrote:
> Not sure what you mean by, "The recursive I/O error is just incorrect."

There is no recursive I/O here. The function writes to unit *, the main
program does an internal write. These are allowed to coexist (12.12p2)

FortranFan

unread,
Feb 2, 2023, 3:58:12 PM2/2/23
to
On Thursday, February 2, 2023 at 3:31:16 PM UTC-5, Steve Lionel wrote:

>..
> There is no recursive I/O here. The function writes to unit *, the main
> program does an internal write. These are allowed to coexist (12.12p2) ..

My bad, you're right - I misread the code in the original post and *overlooked* the fact the main program first has write to an internal file.

FortranFan

unread,
Feb 2, 2023, 4:11:26 PM2/2/23
to
On Thursday, February 2, 2023 at 2:16:20 PM UTC-5, pkla...@nvidia.com wrote:

> .. So one would expect five lines "final: 1", "final: 2", &c. to "final: 5", followed by "wrote: 1 2 3 4 5". ..

For whatever it's worth, my take is the code in the original post does not conform in that component 'n' of the derived type 't' gets referenced before it's defined.

This has to do with first line in paragraph 1 of section 7.5.6.3 When finalization occurs should also apply, "When an intrinsic assignment statement is executed (10.2.1.3), if the variable is not an unallocated allocatable variable, it is finalized after evaluation of expr and before the definition of the variable."

So the local object 'func` corresponding to the function result is "not an unallocated allocatable variable" should be "finalized after evaluation of expr and before the definition of the variable." And when that happens, the impure finalizer ends referencing component 'n' before it's defined.

FortranFan

unread,
Feb 2, 2023, 4:19:27 PM2/2/23
to
On Thursday, February 2, 2023 at 4:11:26 PM UTC-5, FortranFan wrote:

> ..
> For whatever it's worth, my take is the code in the original post does not conform in that component 'n' of the derived type 't' gets referenced before it's defined. ..

Perhaps the following code can be considered for the analysis in order to focus on finalization and move past the nonconforming aspects and any IO bugs/misunderstanding:

module m
type t
integer :: n = -1
character(len=8) :: s = "default"
contains
final :: final
end type
contains
type(t) function func(n)
integer, intent(in) :: n
func%s = "lvar-fun"
func = t(n,s="rhs-tmp")
end function
impure elemental subroutine final(x)
type(t), intent(in out) :: x
print *, 'final: ', trim(x%s), "; n = ", x%n
end subroutine
end module

program test
use m
type(t), allocatable :: a(:)
a = [( func(j), j=1,5 )]
print *, 'wrote: ', (a(j)%n, j=1,size(a))
end

For whatever it's worth, I believe the conforming response by the processor shall be as follows:
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: rhs-tmp; n = 1
final: rhs-tmp; n = 2
final: rhs-tmp; n = 3
final: rhs-tmp; n = 4
final: rhs-tmp; n = 5
wrote: 1 2 3 4 5

However, I find the following:

Compiler A:
wrote: 1 2 3 4 5

Compiler B or C (I'm guessing, one of these)
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: lvar-fun; n = -1
final: rhs-tmp; n = 5

gah4

unread,
Feb 2, 2023, 7:12:48 PM2/2/23
to
On Thursday, February 2, 2023 at 12:31:16 PM UTC-8, Steve Lionel wrote:
> On 2/2/2023 3:19 PM, FortranFan wrote:
> > Not sure what you mean by, "The recursive I/O error is just incorrect."
> There is no recursive I/O here. The function writes to unit *, the main
> program does an internal write. These are allowed to coexist (12.12p2)

They weren't always allowed to coexist.

It would be strange to implement finalization in a compiler that old, though.

Now, when did it change?

bara...@gmail.com

unread,
Feb 3, 2023, 10:53:04 AM2/3/23
to
> For whatever it's worth, I believe the conforming response by the processor shall be as follows:
> final: lvar-fun; n = -1
> final: lvar-fun; n = -1
> final: lvar-fun; n = -1
> final: lvar-fun; n = -1
> final: lvar-fun; n = -1
> final: rhs-tmp; n = 1
> final: rhs-tmp; n = 2
> final: rhs-tmp; n = 3
> final: rhs-tmp; n = 4
> final: rhs-tmp; n = 5
> wrote: 1 2 3 4 5

If it is of interest, the NAG compiler (7.1, build 7118) produces exactly this result.

However, I do not understand, why this result is the standard conforming one. Looking at the line

func = t(n,s="rhs-tmp")

I would have naively thought, it

1: creates t()
2: finalizes func and creates a new instance (as it were a case for an intent(out) dummy argument)
3: assigns t to func (by copying its content)
4: finalizes t

I understand, that it makes much more sense to use "move semantics" instead of the "copy semantics" I've described above, but I could not find the spot, where this is explicitely allowed/treated by the standard.

Steve Lionel

unread,
Feb 3, 2023, 11:25:51 AM2/3/23
to
You made me look!

F2003 was the first to have any wording on this - I can't find relevant
wording in prior standards:

"An input/output statement that is executed while another input/output
statement is being executed is called a recursive input/output statement.

A recursive input/output statement shall not identify an external unit
except that a child data transfer statement may identify its parent data
transfer statement external unit." 9.11 p2-3

A simplistic reading of this is that co-executing I/O statements, where
one identifies an external unit and one an internal unit, are allowed. A
more careful reading suggests order is important, and that if the
internal unit statement is started first, you're not allowed to start a
recursive operation on an external unit (which is what the test program
does.) I don't think this is what was intended.

F2008 revised this text, eliminating the ordering implication:

"An input/output statement that is executed while another input/output
statement is being executed is a recursive input/output statement. A
recursive input/output statement shall not identify an external unit
that is identified by another input/output statement being executed
except that a child data transfer statement may identify its parent data
transfer statement external unit." 9.12p2

So as of F2008, recursive data transfer statements were allowed as long
as they didn't both identify the same external unit.

F2018 did not change the wording here.

FortranFan

unread,
Feb 3, 2023, 11:34:01 AM2/3/23
to
On Friday, February 3, 2023 at 10:53:04 AM UTC-5, bara...@gmail.com wrote:

> ..
> 4: finalizes t
>
> I understand, that it makes much more sense to use "move semantics" instead of the "copy semantics" I've described above, but I could not find the spot, where this is explicitely allowed/treated by the standard.


Under section 7.5.6.3 When finalization occurs in the standard, I don't recall anything being stipulated for finalization of "whatever" is done by a structure constructor which is what is 't' in your blurb above.

Thus I don't believe a conforming processor shall "finalize t". Hence my comment on what the conforming response shall be.

jfh

unread,
Feb 3, 2023, 5:27:29 PM2/3/23
to
That implies that Steve's earlier posting saying "There is no recursive I/O here. The function writes to unit *, the main
program does an internal write. These are allowed to coexist (12.12p2)" was not quite right. The I/O was recursive according to F2008 but it was allowed by that standard.
0 new messages