• Question on finalization standards

    From Andrew Benson@21:1/5 to All on Fri Sep 24 10:02:08 2021
    I'm working (with much help from Paul Thomas) on adding finalization on intrinsic assignment to gfortran. There are a few instances where I'm unclear on precisely what is required by the standard, so would be very grateful for any insight any one here
    can offer.

    The current issue is related to this example code:

    module testmode
    implicit none

    type :: simple
    character(4) :: scope="NONE"
    integer :: ind
    contains
    final :: destructor1
    end type simple

    contains

    subroutine destructor1(self)
    type(simple), intent(inout) :: self
    print *, "destructor1(", self%scope, ") ", self%ind
    end subroutine destructor1

    end module testmode

    program test_final
    use testmode
    implicit none

    type(simple), allocatable :: MyType
    type(simple) :: ThyType = simple("MAIN", 21)

    print *, "[1] ifort finalizes here, whereas gfortran does not."
    MyType = ThyType

    end program test_final

    The new implementation of finalization on intrinsic assignment that I have in gfortran does not finalize MyType here, because it is not allocated prior to the assignment. This seems reasonable. But, Intel ifort does call the finalizer for MyType in this
    case.

    Looking in the Fortran 2003 standards, section 4.5.5.2 "When finalization occurs" states:

    "When an intrinsic assignment statement is executed, variable is finalized after evaluation of expr and before the definition of variable."

    which doesn't really make it clear to me what should happen in this situation.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From FortranFan@21:1/5 to abe...@carnegiescience.edu on Fri Sep 24 12:51:02 2021
    On Friday, September 24, 2021 at 1:02:10 PM UTC-4, abe...@carnegiescience.edu wrote:

    I'm working (with much help from Paul Thomas) on adding finalization on intrinsic assignment to gfortran. There are a few instances where I'm unclear on precisely what is required by the standard, so would be very grateful for any insight any one here
    can offer.

    ..
    Looking in the Fortran 2003 standards, section 4.5.5.2 "When finalization occurs" states: ..

    @abe...@carnegiescience.edu,

    Thank you for taking the initiative to work on this in collaboration with Paul Thomas, that's great news for gfortran.

    Here're some quick suggestions:

    1) Discuss and decide with the gfortran team the plan for this, specifically which standard edition do you intent to target. I write this because you mention 2003 standard, but note there have been some revisions of significant consequence since, some
    of which were captured in Fortran 2008 Corrigenda that later went to official 2018 standard but which didn't catch everyone's attention. My suggestion will be to go with 18-007r1 document toward Fortran 2018 as your primary reference, but please do keep
    an eye an out during your planning and work for the *tweaks* that will indeed go into Fortran 202X when it gets published.

    2) You may also want to consider other online resources such as the Fortran Discourse site (https://fortran-lang.discourse.group/) and GitHub Fortran Issues (https://github.com/j3-fortran/fortran_proposals) to engage with the Community on any and all
    topics that come up as you pursue this. This can be especially important toward the topic at hand - finalization - where you would do well to beware of *possible* "bugs" everywhere (!!) including in the standard and other compiler implementations. This
    is simply due to its history starting 2003 revision. So you can proceed to trust the standard, but please do try to *verify* everything and to crosscheck with the standards committee if anything doesn't make sense to you!

    I'll provide my comments on your code example later.

    Best,

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From John@21:1/5 to All on Fri Sep 24 13:25:24 2021
    RllJOiBudmZvcnRyYW4gZG9lcyBub3QsIGFzIHdlbGwuIEkgZG8gZmluZCBpdCB1bmNsZWFyIGFz IHRvIHdoaWNoIGl0IHNob3VsZCBkby4gRXZlbiB0aG91Z2ggdHdvIGZvciBhbmQgb25lIGFnYWlu c3QgZnJvbSB0aGUgY29tcGlsZXJzLAogICAgICAgSSBsZWFuIHRvd2FyZHMgdG93YXJkcyB0aGUg ZGVzdHJ1Y3RvciBiZWluZyBjYWxsZWQgYXMgaXQgaXMgdXNlZCBpbiBhbiBhc3NpZ25tZW50OyBi dXQgZ2V0dGluZyB0aGUgY29tbWl0dGVlcyB0byB2b3RlIHdvdWxkIGJlIGEgbmljZSB3YXkgdG8g cmVzb2x2ZSB0aGUgcXVlc3Rpb24uIEl0IHNlZW1zIGlkZWFsbHkgdGhlcmUgc2hvdWxkIGJlIGEg Zm9ybWFsIHdheSBmb3IgY29tcGlsZXIgd3JpdGVycyB0byBwZXRpdGlvbiBmb3IgY2xhcmlmaWNh dGlvbi4KCisgOiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09 PT09PT09PQorIGdmb3J0cmFuIC1PMCBmaW5hbDEuZjkwCisgLi9hLm91dAorIDogPT09PT09PT09 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KKyBpZm9ydCAtTzAg ZmluYWwxLmY5MAorIC4vYS5vdXQKIGRlc3RydWN0b3IxKCkgICAgICAgICAgICAwCisgOiA9PT09 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQorIGlmeCAt TzAgZmluYWwxLmY5MAorIC4vYS5vdXQKIGRlc3RydWN0b3IxKCkgICAgICAgICAgICAwCisgOiA9 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQorIG52 Zm9ydHJhbiAtTzAgZmluYWwxLmY5MAorIC4vYS5vdXQK

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andrew Benson@21:1/5 to FortranFan on Fri Sep 24 13:30:54 2021
    Thank you for your response. As you suggested, I took a look at the Fortran 2018 standard document, which I think clears up the ambiguity on this issue. Specifically, in section 7.5.6.3 "When finalization occurs", item 1 states:

    "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. If the variable is an allocated allocatable
    variable, or has an allocated allocatable subobject, that would be deallocated by intrinsic assignment, the finalization occurs before the deallocation."

    I think the key part here is "if the variable is not an unallocated allocatable variable". So, if the variable is unallocated it should not be finalized.

    So, based on my interpretation of that Intel has incorrect behavior here.

    -Andrew

    On Friday, September 24, 2021 at 12:51:04 PM UTC-7, FortranFan wrote:
    On Friday, September 24, 2021 at 1:02:10 PM UTC-4, abe...@carnegiescience.edu wrote:

    I'm working (with much help from Paul Thomas) on adding finalization on intrinsic assignment to gfortran. There are a few instances where I'm unclear on precisely what is required by the standard, so would be very grateful for any insight any one
    here can offer.

    ..
    Looking in the Fortran 2003 standards, section 4.5.5.2 "When finalization occurs" states: ..

    @abe...@carnegiescience.edu,

    Thank you for taking the initiative to work on this in collaboration with Paul Thomas, that's great news for gfortran.

    Here're some quick suggestions:

    1) Discuss and decide with the gfortran team the plan for this, specifically which standard edition do you intent to target. I write this because you mention 2003 standard, but note there have been some revisions of significant consequence since, some
    of which were captured in Fortran 2008 Corrigenda that later went to official 2018 standard but which didn't catch everyone's attention. My suggestion will be to go with 18-007r1 document toward Fortran 2018 as your primary reference, but please do keep
    an eye an out during your planning and work for the *tweaks* that will indeed go into Fortran 202X when it gets published.

    2) You may also want to consider other online resources such as the Fortran Discourse site (https://fortran-lang.discourse.group/) and GitHub Fortran Issues (https://github.com/j3-fortran/fortran_proposals) to engage with the Community on any and all
    topics that come up as you pursue this. This can be especially important toward the topic at hand - finalization - where you would do well to beware of *possible* "bugs" everywhere (!!) including in the standard and other compiler implementations. This
    is simply due to its history starting 2003 revision. So you can proceed to trust the standard, but please do try to *verify* everything and to crosscheck with the standards committee if anything doesn't make sense to you!

    I'll provide my comments on your code example later.

    Best,

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andrew Benson@21:1/5 to All on Fri Sep 24 13:31:53 2021
    VGhhbmtzIGZvciB0ZXN0aW5nIHRoaXMgd2l0aCBudmZvcnRyYW4gLSBpdCdzIGdvb2QgdG8gaGF2 ZSBhIHRoaXJkICJvcGluaW9uIiBoZXJlIQoKT24gRnJpZGF5LCBTZXB0ZW1iZXIgMjQsIDIwMjEg YXQgMToyNToyNiBQTSBVVEMtNywgSm9obiB3cm90ZToKPiBGWUk6IG52Zm9ydHJhbiBkb2VzIG5v dCwgYXMgd2VsbC4gSSBkbyBmaW5kIGl0IHVuY2xlYXIgYXMgdG8gd2hpY2ggaXQgc2hvdWxkIGRv LiBFdmVuIHRob3VnaCB0d28gZm9yIGFuZCBvbmUgYWdhaW5zdCBmcm9tIHRoZSBjb21waWxlcnMs Cj4gSSBsZWFuIHRvd2FyZHMgdG93YXJkcyB0aGUgZGVzdHJ1Y3RvciBiZWluZyBjYWxsZWQgYXMg aXQgaXMgdXNlZCBpbiBhbiBhc3NpZ25tZW50OyBidXQgZ2V0dGluZyB0aGUgY29tbWl0dGVlcyB0 byB2b3RlIHdvdWxkIGJlIGEgbmljZSB3YXkgdG8gcmVzb2x2ZSB0aGUgcXVlc3Rpb24uIEl0IHNl ZW1zIGlkZWFsbHkgdGhlcmUgc2hvdWxkIGJlIGEgZm9ybWFsIHdheSBmb3IgY29tcGlsZXIgd3Jp dGVycyB0byBwZXRpdGlvbiBmb3IgY2xhcmlmaWNhdGlvbi4KPiArIDogPT09PT09PT09PT09PT09 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KPiArIGdmb3J0cmFuIC1PMCBm aW5hbDEuZjkwCj4gKyAuL2Eub3V0Cj4gKyA6ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09 PT09PT09PT09PT09PT09PT09PT09PT09Cj4gKyBpZm9ydCAtTzAgZmluYWwxLmY5MAo+ICsgLi9h Lm91dAo+IGRlc3RydWN0b3IxKCkgMAo+ICsgOiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09 PT09PT09PT09PT09PT09PT09PT09PT09PQo+ICsgaWZ4IC1PMCBmaW5hbDEuZjkwCj4gKyAuL2Eu b3V0Cj4gZGVzdHJ1Y3RvcjEoKSAwCj4gKyA6ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09 PT09PT09PT09PT09PT09PT09PT09PT09Cj4gKyBudmZvcnRyYW4gLU8wIGZpbmFsMS5mOTAKPiAr IC4vYS5vdXQK

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andrew Benson@21:1/5 to Andrew Benson on Mon Sep 27 13:32:39 2021
    Another finalization question (I'll also post this on the Fortran Discourse site and at the GitHub Fortran Issues site).

    This example considers finalization of a class array:

    module testmode
    implicit none

    character(4) :: scope = "MAIN"

    logical, parameter :: instrument = .false.

    type :: simple
    character(4) :: scope
    integer :: ind
    contains
    final :: destructor1, destructor2
    end type simple

    type, extends(simple) :: complicated
    real :: rind
    contains
    final :: destructor3, destructor4
    end type complicated

    integer :: check_scalar
    integer :: check_array(4)
    real :: check_real
    real :: check_rarray(4)
    integer :: final_count = 0

    contains

    subroutine destructor1(self)
    type(simple), intent(inout) :: self
    print *, "destructor1(", self%scope, ") ", self%ind
    end subroutine destructor1

    subroutine destructor2(self)
    type(simple), intent(inout) :: self(:)
    print *, "destructor2(", self(1)%scope, ") ", self%ind
    end subroutine destructor2

    subroutine destructor3(self)
    type(complicated), intent(inout) :: self
    print *, "destructor3(", self%scope, ") ", self%rind
    end subroutine destructor3

    subroutine destructor4(self)
    type(complicated), intent(inout) :: self(:)
    if (size(self, 1) .gt. 0) then
    print *, "destructor4(", self(1)%scope, ") ", size(self%rind), self%rind
    else
    print *, "destructor4"
    end if
    end subroutine destructor4

    function constructor1(ind) result(res)
    type(simple), allocatable :: res
    integer, intent(in) :: ind
    scope = "CTR1"
    allocate (res, source = simple ("SOUR", ind))
    res%scope = scope
    end function constructor1

    function constructor2(ind, rind) result(res)
    class(simple), allocatable :: res(:)
    integer, intent(in) :: ind(:)
    real, intent(in), optional :: rind(:)
    type(complicated), allocatable :: src(:)
    integer :: sz
    integer :: i
    scope = "CTR2"
    if (present (rind)) then
    sz = min (size (ind, 1), size (rind, 1))
    src = [(complicated ("SOUR", ind(i), rind(i)), i = 1, sz)]
    allocate (res, source = src)
    src%scope = "SRC "
    res%scope=scope
    else
    sz = size (ind, 1)
    allocate (res, source = [(simple (scope, ind(i)), i = 1, sz)])
    end if
    end function constructor2
    end module testmode

    program test_final
    use testmode
    implicit none

    class(simple), allocatable :: MyClassArray(:)

    ! *****************
    ! Class assignments
    ! *****************

    allocate (MyClassArray, source = [complicated(scope, 1, 2.0),complicated(scope, 3, 4.0)])
    print *, "[3] ...until here. Both call the rank-1 finalizer for the extended &
    type but ifort calls the rank-0 finalizer for the parent type, while &
    gfortran uses the rank-1 finalizer."
    deallocate (MyClassArray)
    end program test_final


    With gfortran (including the patches for finalization on intrinsic assignment that I'm working on), this results in:

    [3] ...until here. Both call the rank-1 finalizer for the extended type but ifort calls the rank-0 finalizer for the parent type, while gfortran uses the rank-1 finalizer.
    destructor4(MAIN) 2 2.00000000 4.00000000
    destructor2(MAIN) 1 3

    which shows that, when deallocating 'MyClassArray', the rank-1 finalizer for the extended type 'complicated' is called, and then the rank-1 finalizer for the parent type 'simple' is called.

    But, under ifort I get:

    [3] ...until here. Both call the rank-1 finalizer for the extended type but ifo
    rt calls the rank-0 finalizer for the parent type, while gfortran uses the rank
    -1 finalizer.
    destructor4(MAIN) 2 2.000000 4.000000
    destructor1(MAIN) 1
    destructor1(MAIN) 3

    showing that the rank-1 finalizer is called for the extended type, but then the scalar finalizer of the parent type is called twice, once for each element in the array.

    ifort's behavior seems incorrect here, but I'd be interested to hear anyone's opinion on this.

    Thanks,
    Andrew

    On Friday, September 24, 2021 at 1:31:55 PM UTC-7, Andrew Benson wrote:
    Thanks for testing this with nvfortran - it's good to have a third "opinion" here!
    On Friday, September 24, 2021 at 1:25:26 PM UTC-7, John wrote:
    FYI: nvfortran does not, as well. I do find it unclear as to which it should do. Even though two for and one against from the compilers,
    I lean towards towards the destructor being called as it is used in an assignment; but getting the committees to vote would be a nice way to resolve the question. It seems ideally there should be a formal way for compiler writers to petition for
    clarification.
    + : =====================================================
    + gfortran -O0 final1.f90
    + ./a.out
    + : =====================================================
    + ifort -O0 final1.f90
    + ./a.out
    destructor1() 0
    + : =====================================================
    + ifx -O0 final1.f90
    + ./a.out
    destructor1() 0
    + : =====================================================
    + nvfortran -O0 final1.f90
    + ./a.out

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Richard Thomas@21:1/5 to All on Thu Feb 3 10:01:30 2022
    Hi Andrew,

    With gfortran (including the patches for finalization on intrinsic assignment that I'm working on), this results in:

    [3] ...until here. Both call the rank-1 finalizer for the extended type but ifort calls the rank-0 finalizer for the parent type, while gfortran uses the rank-1 finalizer.
    destructor4(MAIN) 2 2.00000000 4.00000000
    destructor2(MAIN) 1 3

    which shows that, when deallocating 'MyClassArray', the rank-1 finalizer for the extended type 'complicated' is called, and then the rank-1 finalizer for the parent type 'simple' is called.

    But, under ifort I get:

    [3] ...until here. Both call the rank-1 finalizer for the extended type but ifo
    rt calls the rank-0 finalizer for the parent type, while gfortran uses the rank
    -1 finalizer.
    destructor4(MAIN) 2 2.000000 4.000000
    destructor1(MAIN) 1
    destructor1(MAIN) 3

    showing that the rank-1 finalizer is called for the extended type, but then the scalar finalizer of the parent type is called twice, once for each element in the array.

    ifort's behavior seems incorrect here, but I'd be interested to hear anyone's opinion on this.


    I have just posted a large patch on finalization. In the accomanying message, I conclude that ifort is correct. From F2018 7.5.6.3:
    (2) All finalizable components that appear in the type definition are finalized in a processor-dependent
    order. If the entity being finalized is an array, each finalizable component of each element of that
    entity is finalized separately.
    (3) If the entity is of extended type and the parent type is finalizable, the parent component is finalized.

    The parent type is indeed a component and should apparently be treated as such. I have not yet ahad the intestinal fortitude to correct it in gfortran!

    Regards

    Paul

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andrew Benson@21:1/5 to Paul Richard Thomas on Thu Feb 3 10:05:29 2022
    Hi Paul,

    Thanks - after reading those lines from the standard carefully (a few times over!) that does make sense.

    Thanks,
    Andrew

    On Thursday, February 3, 2022 at 10:01:33 AM UTC-8, Paul Richard Thomas wrote:
    Hi Andrew,
    With gfortran (including the patches for finalization on intrinsic assignment that I'm working on), this results in:

    [3] ...until here. Both call the rank-1 finalizer for the extended type but ifort calls the rank-0 finalizer for the parent type, while gfortran uses the rank-1 finalizer.
    destructor4(MAIN) 2 2.00000000 4.00000000
    destructor2(MAIN) 1 3

    which shows that, when deallocating 'MyClassArray', the rank-1 finalizer for the extended type 'complicated' is called, and then the rank-1 finalizer for the parent type 'simple' is called.

    But, under ifort I get:

    [3] ...until here. Both call the rank-1 finalizer for the extended type but ifo
    rt calls the rank-0 finalizer for the parent type, while gfortran uses the rank
    -1 finalizer.
    destructor4(MAIN) 2 2.000000 4.000000
    destructor1(MAIN) 1
    destructor1(MAIN) 3

    showing that the rank-1 finalizer is called for the extended type, but then the scalar finalizer of the parent type is called twice, once for each element in the array.

    ifort's behavior seems incorrect here, but I'd be interested to hear anyone's opinion on this.
    I have just posted a large patch on finalization. In the accomanying message, I conclude that ifort is correct. From F2018 7.5.6.3:
    (2) All finalizable components that appear in the type definition are finalized in a processor-dependent
    order. If the entity being finalized is an array, each finalizable component of each element of that
    entity is finalized separately.
    (3) If the entity is of extended type and the parent type is finalizable, the parent component is finalized.

    The parent type is indeed a component and should apparently be treated as such. I have not yet ahad the intestinal fortitude to correct it in gfortran!

    Regards

    Paul

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Richard Thomas@21:1/5 to abe...@carnegiescience.edu on Sun Feb 6 02:17:03 2022
    On Thursday, 3 February 2022 at 18:05:32 UTC, abe...@carnegiescience.edu wrote:
    Hi Paul,

    Thanks - after reading those lines from the standard carefully (a few times over!) that does make sense.

    Thanks,
    Andrew
    On Thursday, February 3, 2022 at 10:01:33 AM UTC-8, Paul Richard Thomas wrote:
    Hi Andrew,
    ...snip...
    I have just posted a large patch on finalization. In the accomanying message, I conclude that ifort is correct. From F2018 7.5.6.3:
    (2) All finalizable components that appear in the type definition are finalized in a processor-dependent
    order. If the entity being finalized is an array, each finalizable component of each element of that
    entity is finalized separately.
    (3) If the entity is of extended type and the parent type is finalizable, the parent component is finalized.

    The parent type is indeed a component and should apparently be treated as such. I have not yet ahad the intestinal fortitude to correct it in gfortran!

    Regards

    Paul

    A dissenting voice: NAG Fortran compiler version 7.1 (thanks Damian) does the same as gfortran. Paragraph 3 seems to be somewhat prone to ambiguous interpretation.

    It turned out that making gfortran comply with ifort is much easier than I thought it would be.

    Paul

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)