Fortran: fixes for procedures with ALLOCATABLE,INTENT(OUT) arguments [PR92178]
Checks
Commit Message
Dear all,
the attached patch fixes a long-standing issue with the
order of evaluation of procedure argument expressions and
deallocation of allocatable actual arguments passed to
allocatable dummies with intent(out) attribute.
It is based on an initial patch by Steve, handles issues
pointed out by Tobias, and includes a suggestion by Tobias
to scan the procedure arguments first to decide whether the
creation of temporaries is needed.
There is one unresolved issue left that might be more
general: it appears to affect character arguments (only)
in that quite often there still is no temporary generated.
I haven't found the reason why and would like to defer this,
unless someone has a good suggestion.
Regtested on x86_64-pc-linux-gnu. OK for mainline?
Thanks,
Harald
Comments
Hello,
Le 02/07/2023 à 22:38, Harald Anlauf via Fortran a écrit :
> Dear all,
>
> the attached patch fixes a long-standing issue with the
> order of evaluation of procedure argument expressions and
> deallocation of allocatable actual arguments passed to
> allocatable dummies with intent(out) attribute.
>
> It is based on an initial patch by Steve, handles issues
> pointed out by Tobias, and includes a suggestion by Tobias
> to scan the procedure arguments first to decide whether the
> creation of temporaries is needed.
>
> There is one unresolved issue left that might be more
> general: it appears to affect character arguments (only)
> in that quite often there still is no temporary generated.
> I haven't found the reason why and would like to defer this,
> unless someone has a good suggestion.
>
No problem, let's fix the easier parts first.
> Regtested on x86_64-pc-linux-gnu. OK for mainline?
>
A few thing to double check below.
> pr92178.diff
>
> From 609ba636927811cddc74fb815cb18809c7d33565 Mon Sep 17 00:00:00 2001
> From: Harald Anlauf <anlauf@gmx.de>
> Date: Sun, 2 Jul 2023 22:14:19 +0200
> Subject: [PATCH] Fortran: fixes for procedures with ALLOCATABLE,INTENT(OUT)
> arguments [PR92178]
>
> gcc/fortran/ChangeLog:
>
> PR fortran/92178
> * trans-expr.cc (gfc_conv_procedure_call): Check procedures for
> allocatable dummy arguments with INTENT(OUT) and move deallocation
> of actual arguments after evaluation of argument expressions before
> the procedure is executed.
>
> gcc/testsuite/ChangeLog:
>
> PR fortran/92178
> * gfortran.dg/pr92178.f90: New test.
> * gfortran.dg/pr92178_2.f90: New test.
>
> Co-authored-by: Steven G. Kargl <kargl@gcc.gnu.org>
> ---
> gcc/fortran/trans-expr.cc | 52 ++++++++++++++--
> gcc/testsuite/gfortran.dg/pr92178.f90 | 83 +++++++++++++++++++++++++
> gcc/testsuite/gfortran.dg/pr92178_2.f90 | 46 ++++++++++++++
> 3 files changed, 177 insertions(+), 4 deletions(-)
> create mode 100644 gcc/testsuite/gfortran.dg/pr92178.f90
> create mode 100644 gcc/testsuite/gfortran.dg/pr92178_2.f90
>
> diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
> index 30946ba3f63..16e8f037cfc 100644
> --- a/gcc/fortran/trans-expr.cc
> +++ b/gcc/fortran/trans-expr.cc
(...)
> @@ -6117,6 +6118,33 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
> && UNLIMITED_POLY (sym)
> && comp && (strcmp ("_copy", comp->name) == 0);
>
> + /* First scan argument list for allocatable actual arguments passed to
> + allocatable dummy arguments with INTENT(OUT). As the corresponding
> + actual arguments are deallocated before execution of the procedure, we
> + evaluate actual argument expressions to avoid problems with possible
> + dependencies. */
> + bool force_eval_args = false;
> + gfc_formal_arglist *tmp_formal;
> + for (arg = args, tmp_formal = formal; arg != NULL;
> + arg = arg->next, tmp_formal = tmp_formal ? tmp_formal->next : NULL)
> + {
> + e = arg->expr;
> + fsym = tmp_formal ? tmp_formal->sym : NULL;
> + if (e && fsym
> + && e->expr_type == EXPR_VARIABLE
> + && fsym->attr.intent == INTENT_OUT
> + && (fsym->ts.type == BT_CLASS && fsym->attr.class_ok
> + ? CLASS_DATA (fsym)->attr.allocatable
> + : fsym->attr.allocatable)
> + && e->symtree
> + && e->symtree->n.sym
> + && gfc_variable_attr (e, NULL).allocatable)
> + {
> + force_eval_args = true;
> + break;
> + }
> + }
> +
The function is already big enough, would you mind outlining this to its
own function?
> /* Evaluate the arguments. */
> for (arg = args, argc = 0; arg != NULL;
> arg = arg->next, formal = formal ? formal->next : NULL, ++argc)
> @@ -6680,7 +6708,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
> else
> tmp = gfc_finish_block (&block);
>
> - gfc_add_expr_to_block (&se->pre, tmp);
> + gfc_add_expr_to_block (&dealloc_blk, tmp);
> }
>
> /* A class array element needs converting back to be a
> @@ -6980,7 +7008,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
> build_empty_stmt (input_location));
> }
> if (tmp != NULL_TREE)
> - gfc_add_expr_to_block (&se->pre, tmp);
> + gfc_add_expr_to_block (&dealloc_blk, tmp);
> }
>
> tmp = parmse.expr;
> @@ -7004,7 +7032,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
> void_type_node,
> gfc_conv_expr_present (e->symtree->n.sym),
> tmp, build_empty_stmt (input_location));
> - gfc_add_expr_to_block (&se->pre, tmp);
> + gfc_add_expr_to_block (&dealloc_blk, tmp);
> }
> }
> }
These look good, but I'm surprised that there is no similar change at
the 6819 line.
This is the class array actual vs class array dummy case.
It seems to be checked by the "bar" subroutine in your testcase, except
that the intent(out) argument comes last there, whereas it was coming
first with the original testcases in the PR.
Can you double check?
> @@ -7101,6 +7129,21 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
> }
> }
>
> + /* If any actual argument of the procedure is allocatable and passed
> + to an allocatable dummy with INTENT(OUT), we conservatively
> + evaluate all actual argument expressions before deallocations are
> + performed and the procedure is executed. This ensures we conform
> + to F2023:15.5.3, 15.5.4. Create temporaries except for constants,
> + variables, and functions returning pointers that can appear in a
> + variable definition context. */
> + if (e && fsym && force_eval_args
> + && e->expr_type != EXPR_VARIABLE
> + && !gfc_is_constant_expr (e)
> + && (e->expr_type != EXPR_FUNCTION
> + || !(gfc_expr_attr (e).pointer
> + || gfc_expr_attr (e).proc_pointer)))
> + parmse.expr = gfc_evaluate_now (parmse.expr, &parmse.pre);
> +
I'm not sure about the guarding condition.
It looks like it may miss evaluation in some cases (one testcase below).
With a value dummy, it is always safe to evaluate to a temporary
variable, and with a non-value dummy, parmse.expr contains a pointer, so
it is safe as well to evaluate that to a temporary pointer?
At least a || fsym->attr.value condition is missing somewhere, but I
think the condition can be reduced to this:
if (e && fsym && force_eval_args
&& !gfc_is_constant_expr (e))
Were there failures that drove to your above guarding conditions?
Mikael
PS: The testcase (as promised):
program p
implicit none
type t
integer :: i
integer, pointer :: pi
end type t
integer, target :: j
type(t), allocatable :: ta
j = 1
ta = t(2, j)
call assign(ta, id(ta%pi))
if (ta%i /= 1) stop 1
if (associated(ta%pi)) stop 2
contains
subroutine assign(a, b)
type(t), intent(out), allocatable :: a
integer, intent(in) , value :: b
allocate(a)
a%i = b
a%pi => null()
end subroutine assign
function id(a)
integer, pointer :: id, a
id => a
end function id
end program p
Hi Mikael,
Am 03.07.23 um 13:46 schrieb Mikael Morin:
> A few thing to double check below.
>
>> diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
>> index 30946ba3f63..16e8f037cfc 100644
>> --- a/gcc/fortran/trans-expr.cc
>> +++ b/gcc/fortran/trans-expr.cc
> (...)
>> @@ -6117,6 +6118,33 @@ gfc_conv_procedure_call (gfc_se * se,
>> gfc_symbol * sym,
>> && UNLIMITED_POLY (sym)
>> && comp && (strcmp ("_copy", comp->name) == 0);
>>
>> + /* First scan argument list for allocatable actual arguments passed to
>> + allocatable dummy arguments with INTENT(OUT). As the corresponding
>> + actual arguments are deallocated before execution of the
>> procedure, we
>> + evaluate actual argument expressions to avoid problems with
>> possible
>> + dependencies. */
>> + bool force_eval_args = false;
>> + gfc_formal_arglist *tmp_formal;
>> + for (arg = args, tmp_formal = formal; arg != NULL;
>> + arg = arg->next, tmp_formal = tmp_formal ? tmp_formal->next :
>> NULL)
>> + {
>> + e = arg->expr;
>> + fsym = tmp_formal ? tmp_formal->sym : NULL;
>> + if (e && fsym
>> + && e->expr_type == EXPR_VARIABLE
>> + && fsym->attr.intent == INTENT_OUT
>> + && (fsym->ts.type == BT_CLASS && fsym->attr.class_ok
>> + ? CLASS_DATA (fsym)->attr.allocatable
>> + : fsym->attr.allocatable)
>> + && e->symtree
>> + && e->symtree->n.sym
>> + && gfc_variable_attr (e, NULL).allocatable)
>> + {
>> + force_eval_args = true;
>> + break;
>> + }
>> + }
>> +
> The function is already big enough, would you mind outlining this to its
> own function?
This can be done. At least it is not part of the monster loop.
>
>> /* Evaluate the arguments. */
>> for (arg = args, argc = 0; arg != NULL;
>> arg = arg->next, formal = formal ? formal->next : NULL, ++argc)
>> @@ -6680,7 +6708,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol
>> * sym,
>> else
>> tmp = gfc_finish_block (&block);
>>
>> - gfc_add_expr_to_block (&se->pre, tmp);
>> + gfc_add_expr_to_block (&dealloc_blk, tmp);
>> }
>>
>> /* A class array element needs converting back to be a
>> @@ -6980,7 +7008,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol
>> * sym,
>> build_empty_stmt (input_location));
>> }
>> if (tmp != NULL_TREE)
>> - gfc_add_expr_to_block (&se->pre, tmp);
>> + gfc_add_expr_to_block (&dealloc_blk, tmp);
>> }
>>
>> tmp = parmse.expr;
>> @@ -7004,7 +7032,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol
>> * sym,
>> void_type_node,
>> gfc_conv_expr_present (e->symtree->n.sym),
>> tmp, build_empty_stmt (input_location));
>> - gfc_add_expr_to_block (&se->pre, tmp);
>> + gfc_add_expr_to_block (&dealloc_blk, tmp);
>> }
>> }
>> }
> These look good, but I'm surprised that there is no similar change at
> the 6819 line.
> This is the class array actual vs class array dummy case.
> It seems to be checked by the "bar" subroutine in your testcase, except
> that the intent(out) argument comes last there, whereas it was coming
> first with the original testcases in the PR.
> Can you double check?
I believe I tried that before and encountered regressions.
The change
diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index 16e8f037cfc..43e013fa720 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -6844,7 +6844,8 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol *
sym,
else
tmp = gfc_finish_block (&block);
- gfc_add_expr_to_block (&se->pre, tmp);
+// gfc_add_expr_to_block (&se->pre, tmp);
+ gfc_add_expr_to_block (&dealloc_blk, tmp);
}
/* The conversion does not repackage the reference to a class
regresses on:
gfortran.dg/class_array_16.f90
gfortran.dg/finalize_12.f90
gfortran.dg/optional_class_1.f90
A simplified testcase for further study:
program p
implicit none
class(*), allocatable :: c(:)
c = [3, 4]
call bar (allocated (c), c, allocated (c))
if (allocated (c)) stop 14
contains
subroutine bar (alloc, x, alloc2)
logical :: alloc, alloc2
class(*), allocatable, intent(out) :: x(:)
if (allocated (x)) stop 5
if (.not. alloc) stop 6
if (.not. alloc2) stop 16
end subroutine bar
end
(This fails in a different place for the posted patch and for
the above trial change. Need to go to the drawing board...)
>> @@ -7101,6 +7129,21 @@ gfc_conv_procedure_call (gfc_se * se,
>> gfc_symbol * sym,
>> }
>> }
>>
>> + /* If any actual argument of the procedure is allocatable and
>> passed
>> + to an allocatable dummy with INTENT(OUT), we conservatively
>> + evaluate all actual argument expressions before deallocations are
>> + performed and the procedure is executed. This ensures we conform
>> + to F2023:15.5.3, 15.5.4. Create temporaries except for constants,
>> + variables, and functions returning pointers that can appear in a
>> + variable definition context. */
>> + if (e && fsym && force_eval_args
>> + && e->expr_type != EXPR_VARIABLE
>> + && !gfc_is_constant_expr (e)
>> + && (e->expr_type != EXPR_FUNCTION
>> + || !(gfc_expr_attr (e).pointer
>> + || gfc_expr_attr (e).proc_pointer)))
>> + parmse.expr = gfc_evaluate_now (parmse.expr, &parmse.pre);
>> +
> I'm not sure about the guarding condition.
> It looks like it may miss evaluation in some cases (one testcase below).
> With a value dummy, it is always safe to evaluate to a temporary
> variable, and with a non-value dummy, parmse.expr contains a pointer, so
> it is safe as well to evaluate that to a temporary pointer?
> At least a || fsym->attr.value condition is missing somewhere, but I
> think the condition can be reduced to this:
> if (e && fsym && force_eval_args
> && !gfc_is_constant_expr (e))
> Were there failures that drove to your above guarding conditions?
It seems that your simpler version essentially behaves the same way,
at least as far as regtesting is concerned.
>
> Mikael
>
> PS: The testcase (as promised):
>
> program p
> implicit none
> type t
> integer :: i
> integer, pointer :: pi
> end type t
> integer, target :: j
> type(t), allocatable :: ta
> j = 1
> ta = t(2, j)
> call assign(ta, id(ta%pi))
> if (ta%i /= 1) stop 1
> if (associated(ta%pi)) stop 2
> contains
> subroutine assign(a, b)
> type(t), intent(out), allocatable :: a
> integer, intent(in) , value :: b
> allocate(a)
> a%i = b
> a%pi => null()
> end subroutine assign
> function id(a)
> integer, pointer :: id, a
> id => a
> end function id
> end program p
Indeed, this is a nice demonstration.
While playing, I was wondering whether the following code is conforming:
program p
call s ((1))
contains
subroutine s (x)
integer :: x
x = 42
end subroutine
end
(It crashes with gfortran, but not with any foreign brand tested).
Harald
On Mon, Jul 03, 2023 at 10:49:36PM +0200, Harald Anlauf via Fortran wrote:
>
> Indeed, this is a nice demonstration.
>
> While playing, I was wondering whether the following code is conforming:
>
> program p
> call s ((1))
> contains
> subroutine s (x)
> integer :: x
> x = 42
> end subroutine
> end
>
> (It crashes with gfortran, but not with any foreign brand tested).
>
It's not conforming. '(1)' is an expression and it cannot appear
in a variable definition condition. I am not aware of any numbered
constraint tha would require a Fortran processor to generate an
error.
Le 04/07/2023 à 01:56, Steve Kargl a écrit :
> On Mon, Jul 03, 2023 at 10:49:36PM +0200, Harald Anlauf via Fortran wrote:
>>
>> Indeed, this is a nice demonstration.
>>
>> While playing, I was wondering whether the following code is conforming:
>>
>> program p
>> call s ((1))
>> contains
>> subroutine s (x)
>> integer :: x
>> x = 42
>> end subroutine
>> end
>>
>> (It crashes with gfortran, but not with any foreign brand tested).
>>
>
> It's not conforming. '(1)' is an expression and it cannot appear
> in a variable definition condition. I am not aware of any numbered
> constraint tha would require a Fortran processor to generate an
> error.
>
I think you would be right if X had INTENT(OUT) or INTENT(INOUT) attribute.
This is F2023, 15.5.2.4 (no mention of variable definition context here):
> If a dummy argument has INTENT (OUT) or INTENT (INOUT), the actual argument shall be definable.
However, with unspecified intent, I can't find the rule explicitly
forbidding the above example.
I'm tempted to say it is conforming.
Le 03/07/2023 à 22:49, Harald Anlauf a écrit :
> Hi Mikael,
>
> Am 03.07.23 um 13:46 schrieb Mikael Morin:
>> These look good, but I'm surprised that there is no similar change at
>> the 6819 line.
>> This is the class array actual vs class array dummy case.
>> It seems to be checked by the "bar" subroutine in your testcase, except
>> that the intent(out) argument comes last there, whereas it was coming
>> first with the original testcases in the PR.
>> Can you double check?
>
> I believe I tried that before and encountered regressions.
> The change
>
> diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
> index 16e8f037cfc..43e013fa720 100644
> --- a/gcc/fortran/trans-expr.cc
> +++ b/gcc/fortran/trans-expr.cc
> @@ -6844,7 +6844,8 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol *
> sym,
> else
> tmp = gfc_finish_block (&block);
>
> - gfc_add_expr_to_block (&se->pre, tmp);
> +// gfc_add_expr_to_block (&se->pre, tmp);
> + gfc_add_expr_to_block (&dealloc_blk, tmp);
> }
>
> /* The conversion does not repackage the reference to a
> class
>
> regresses on:
> gfortran.dg/class_array_16.f90
> gfortran.dg/finalize_12.f90
> gfortran.dg/optional_class_1.f90
>
> A simplified testcase for further study:
>
> program p
> implicit none
> class(*), allocatable :: c(:)
> c = [3, 4]
> call bar (allocated (c), c, allocated (c))
> if (allocated (c)) stop 14
> contains
> subroutine bar (alloc, x, alloc2)
> logical :: alloc, alloc2
> class(*), allocatable, intent(out) :: x(:)
> if (allocated (x)) stop 5
> if (.not. alloc) stop 6
> if (.not. alloc2) stop 16
> end subroutine bar
> end
>
> (This fails in a different place for the posted patch and for
> the above trial change. Need to go to the drawing board...)
>
I've had a quick look.
The code originally generated looks like:
D.4343 = (void *[0:] * restrict) c._data.data != 0B;
if (c._data.data != 0B)
// free c._data.data
c._data.data = 0B;
...
class.3._data = c._data;
...
D.4345 = (void *[0:] * restrict) c._data.data != 0B;
bar (&D.4343, &class.3, &D.4345);
this fails because D.4345 has the wrong value.
With your change, it becomes:
D.4343 = (void *[0:] * restrict) c._data.data != 0B;
...
class.3._data = c._data;
...
D.4345 = (void *[0:] * restrict) c._data.data != 0B;
if (c._data.data != 0B)
// free c._data.data
c._data.data = 0B;
bar (&D.4343, &class.3, &D.4345);
and then it is class.3._data that has the wrong value.
So basically the initialization of class.3 should move with the
deallocation.
I can reproduce a similar problem with your unmodified patch on the
following variant:
program p
implicit none
class(*), allocatable :: c
c = 3
call bar (c, allocated (c))
if (allocated (c)) stop 14
contains
subroutine bar (x, alloc2)
logical :: alloc, alloc2
class(*), allocatable, intent(out) :: x(..)
if (allocated (x)) stop 5
if (.not. alloc) stop 6
if (.not. alloc2) stop 16
end subroutine bar
end
On Tue, Jul 04, 2023 at 11:26:26AM +0200, Mikael Morin wrote:
> Le 04/07/2023 à 01:56, Steve Kargl a écrit :
> > On Mon, Jul 03, 2023 at 10:49:36PM +0200, Harald Anlauf via Fortran wrote:
> > >
> > > Indeed, this is a nice demonstration.
> > >
> > > While playing, I was wondering whether the following code is conforming:
> > >
> > > program p
> > > call s ((1))
> > > contains
> > > subroutine s (x)
> > > integer :: x
> > > x = 42
> > > end subroutine
> > > end
> > >
> > > (It crashes with gfortran, but not with any foreign brand tested).
> > >
> >
> > It's not conforming. '(1)' is an expression and it cannot appear
> > in a variable definition condition. I am not aware of any numbered
> > constraint tha would require a Fortran processor to generate an
> > error.
> >
>
> I think you would be right if X had INTENT(OUT) or INTENT(INOUT) attribute.
> This is F2023, 15.5.2.4 (no mention of variable definition context here):
> > If a dummy argument has INTENT (OUT) or INTENT (INOUT), the actual
> > argument shall be definable.
>
> However, with unspecified intent, I can't find the rule explicitly
> forbidding the above example.
> I'm tempted to say it is conforming.
I thought it was in Sec. 19, but failed to locate any prohibition.
The best I can find is
23-007r1.pdf
8.5.10 INTENT attribute
pg. 114 (following Note 1)
If no INTENT attribute is specified for a dummy argument,
its use is subject to the limitations of its effective
argument (15.5.2).
pg. 115 (within Note 4, so non-normative text)
INTENT (INOUT) is not equivalent to omitting the INTENT attribute.
The actual argument corresponding to an INTENT (INOUT) dummy argument
is always required to be definable, while an actual argument corresponding
to a dummy argument without an INTENT attribute need be definable only
if the dummy argument is actually redefined.
Searching for "definable" does not lead to a prohibition of the form
"An expression is not definable."
Hi Mikael, all,
I think I've found it: there is a call to gfc_conv_class_to_class
that - according to a comment - does a repackaging to a class array.
Deferring that repackaging along with the deallocation not only fixes
the regression, but also the cases I tested.
Attached is a "sneak preview", hoping that the experts (Paul, Mikael,
...) can tell if I am going down the wrong road.
I'll wrap up all pieces and resubmit when the dust settles.
We can then address the other findings later.
Harald
Am 04.07.23 um 15:35 schrieb Mikael Morin:
> Le 03/07/2023 à 22:49, Harald Anlauf a écrit :
>> Hi Mikael,
>>
>> Am 03.07.23 um 13:46 schrieb Mikael Morin:
>>> These look good, but I'm surprised that there is no similar change at
>>> the 6819 line.
>>> This is the class array actual vs class array dummy case.
>>> It seems to be checked by the "bar" subroutine in your testcase, except
>>> that the intent(out) argument comes last there, whereas it was coming
>>> first with the original testcases in the PR.
>>> Can you double check?
>>
>> I believe I tried that before and encountered regressions.
>> The change
>>
>> diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
>> index 16e8f037cfc..43e013fa720 100644
>> --- a/gcc/fortran/trans-expr.cc
>> +++ b/gcc/fortran/trans-expr.cc
>> @@ -6844,7 +6844,8 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol *
>> sym,
>> else
>> tmp = gfc_finish_block (&block);
>>
>> - gfc_add_expr_to_block (&se->pre, tmp);
>> +// gfc_add_expr_to_block (&se->pre, tmp);
>> + gfc_add_expr_to_block (&dealloc_blk, tmp);
>> }
>>
>> /* The conversion does not repackage the reference to a
>> class
>>
>> regresses on:
>> gfortran.dg/class_array_16.f90
>> gfortran.dg/finalize_12.f90
>> gfortran.dg/optional_class_1.f90
>>
>> A simplified testcase for further study:
>>
>> program p
>> implicit none
>> class(*), allocatable :: c(:)
>> c = [3, 4]
>> call bar (allocated (c), c, allocated (c))
>> if (allocated (c)) stop 14
>> contains
>> subroutine bar (alloc, x, alloc2)
>> logical :: alloc, alloc2
>> class(*), allocatable, intent(out) :: x(:)
>> if (allocated (x)) stop 5
>> if (.not. alloc) stop 6
>> if (.not. alloc2) stop 16
>> end subroutine bar
>> end
>>
>> (This fails in a different place for the posted patch and for
>> the above trial change. Need to go to the drawing board...)
>>
> I've had a quick look.
>
> The code originally generated looks like:
>
> D.4343 = (void *[0:] * restrict) c._data.data != 0B;
> if (c._data.data != 0B)
> // free c._data.data
> c._data.data = 0B;
> ...
> class.3._data = c._data;
> ...
> D.4345 = (void *[0:] * restrict) c._data.data != 0B;
> bar (&D.4343, &class.3, &D.4345);
>
> this fails because D.4345 has the wrong value.
> With your change, it becomes:
>
> D.4343 = (void *[0:] * restrict) c._data.data != 0B;
> ...
> class.3._data = c._data;
> ...
> D.4345 = (void *[0:] * restrict) c._data.data != 0B;
> if (c._data.data != 0B)
> // free c._data.data
> c._data.data = 0B;
> bar (&D.4343, &class.3, &D.4345);
>
> and then it is class.3._data that has the wrong value.
> So basically the initialization of class.3 should move with the
> deallocation.
>
> I can reproduce a similar problem with your unmodified patch on the
> following variant:
>
> program p
> implicit none
> class(*), allocatable :: c
> c = 3
> call bar (c, allocated (c))
> if (allocated (c)) stop 14
> contains
> subroutine bar (x, alloc2)
> logical :: alloc, alloc2
> class(*), allocatable, intent(out) :: x(..)
> if (allocated (x)) stop 5
> if (.not. alloc) stop 6
> if (.not. alloc2) stop 16
> end subroutine bar
> end
>
>
>
Le 04/07/2023 à 21:00, Harald Anlauf a écrit :
> Hi Mikael, all,
>
> I think I've found it: there is a call to gfc_conv_class_to_class
> that - according to a comment - does a repackaging to a class array.
> Deferring that repackaging along with the deallocation not only fixes
> the regression, but also the cases I tested.
>
> Attached is a "sneak preview", hoping that the experts (Paul, Mikael,
> ...) can tell if I am going down the wrong road.
>
I think that's it mostly. There is one last thing that I am not sure...
> diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
> index 16e8f037cfc..a68c8d33acc 100644
> --- a/gcc/fortran/trans-expr.cc
> +++ b/gcc/fortran/trans-expr.cc
> @@ -6858,6 +6860,10 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
> && e->symtree->n.sym->attr.optional,
> CLASS_DATA (fsym)->attr.class_pointer
> || CLASS_DATA (fsym)->attr.allocatable);
> +
> + /* Defer repackaging after deallocation. */
> + if (defer_repackage)
> + gfc_add_block_to_block (&dealloc_blk, &parmse.pre);
> }
> else
> {
... whether you will not be deferring too much here. That is parmse.pre
contains both the argument evaluation and the class container setup from
gfc_conv_class_to_class. If it's safe to defer both, that's fine,
otherwise a separate gfc_se struct should be passed to
gfc_conv_class_to_class so that only the latter part can be deferred.
Need to think of an example...
Le 04/07/2023 à 21:37, Mikael Morin a écrit :
> Le 04/07/2023 à 21:00, Harald Anlauf a écrit :
>> Hi Mikael, all,
>>
>> I think I've found it: there is a call to gfc_conv_class_to_class
>> that - according to a comment - does a repackaging to a class array.
>> Deferring that repackaging along with the deallocation not only fixes
>> the regression, but also the cases I tested.
>>
>> Attached is a "sneak preview", hoping that the experts (Paul, Mikael,
>> ...) can tell if I am going down the wrong road.
>>
> I think that's it mostly. There is one last thing that I am not sure...
>
>> diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
>> index 16e8f037cfc..a68c8d33acc 100644
>> --- a/gcc/fortran/trans-expr.cc
>> +++ b/gcc/fortran/trans-expr.cc
>> @@ -6858,6 +6860,10 @@ gfc_conv_procedure_call (gfc_se * se,
>> gfc_symbol * sym,
>> && e->symtree->n.sym->attr.optional,
>> CLASS_DATA (fsym)->attr.class_pointer
>> || CLASS_DATA (fsym)->attr.allocatable);
>> +
>> + /* Defer repackaging after deallocation. */
>> + if (defer_repackage)
>> + gfc_add_block_to_block (&dealloc_blk, &parmse.pre);
>> }
>> else
>> {
>
> ... whether you will not be deferring too much here. That is parmse.pre
> contains both the argument evaluation and the class container setup from
> gfc_conv_class_to_class. If it's safe to defer both, that's fine,
> otherwise a separate gfc_se struct should be passed to
> gfc_conv_class_to_class so that only the latter part can be deferred.
> Need to think of an example...
Here is an example, admittedly artificial. Fails with the above change,
but fails with master as well.
program p
implicit none
type t
integer :: i
end type t
type u
class(t), allocatable :: ta(:)
end type u
type(u), allocatable, target :: c(:)
c = [u([t(1), t(3)]), u([t(4), t(9)])]
call bar (allocated (c(c(1)%ta(1)%i)%ta), c(c(1)%ta(1)%i)%ta,
allocated (c(c(1)%ta(1)%i)%ta))
if (allocated(c(1)%ta)) stop 11
if (.not. allocated(c(2)%ta)) stop 12
contains
subroutine bar (alloc, x, alloc2)
logical :: alloc, alloc2
class(t), allocatable, intent(out) :: x(:)
if (allocated (x)) stop 1
if (.not. alloc) stop 2
if (.not. alloc2) stop 3
end subroutine bar
end
Hi Mikael,
Am 05.07.23 um 16:54 schrieb Mikael Morin:
> Here is an example, admittedly artificial. Fails with the above change,
> but fails with master as well.
>
> program p
> implicit none
> type t
> integer :: i
> end type t
> type u
> class(t), allocatable :: ta(:)
> end type u
> type(u), allocatable, target :: c(:)
> c = [u([t(1), t(3)]), u([t(4), t(9)])]
> call bar (allocated (c(c(1)%ta(1)%i)%ta), c(c(1)%ta(1)%i)%ta,
> allocated (c(c(1)%ta(1)%i)%ta))
> if (allocated(c(1)%ta)) stop 11
> if (.not. allocated(c(2)%ta)) stop 12
> contains
> subroutine bar (alloc, x, alloc2)
> logical :: alloc, alloc2
> class(t), allocatable, intent(out) :: x(:)
> if (allocated (x)) stop 1
> if (.not. alloc) stop 2
> if (.not. alloc2) stop 3
> end subroutine bar
> end
while it looks artificial, it is valid, and IMHO it is a beast...
I've played around and added another argument gfc_se *convse to
gfc_conv_class_to_class in an attempt to implement what I thought
you suggested (to get the .pre/.post separately), but in the end
this did not lead to working code. And the tree-dump for your
example above is beyond what I can grasp.
I've noticed that my attempt does not properly handle the
parmse.post; at least this is what the above example shows:
there is a small part after the call to bar that should have
been executed before that call, which I attribute to .post.
But my attempts in moving that part regresses on a couple
of testcases with class and intent(out). I am at a loss now.
I am attaching the latest version of my patch to give you or
Paul or others the opportunity to see what is wrong or add the
missing pieces.
Thanks for your help so far.
Harald
Le 05/07/2023 à 22:36, Harald Anlauf a écrit :
> Hi Mikael,
>
> Am 05.07.23 um 16:54 schrieb Mikael Morin:
>> Here is an example, admittedly artificial. Fails with the above change,
>> but fails with master as well.
>>
>> program p
>> implicit none
>> type t
>> integer :: i
>> end type t
>> type u
>> class(t), allocatable :: ta(:)
>> end type u
>> type(u), allocatable, target :: c(:)
>> c = [u([t(1), t(3)]), u([t(4), t(9)])]
>> call bar (allocated (c(c(1)%ta(1)%i)%ta), c(c(1)%ta(1)%i)%ta,
>> allocated (c(c(1)%ta(1)%i)%ta))
>> if (allocated(c(1)%ta)) stop 11
>> if (.not. allocated(c(2)%ta)) stop 12
>> contains
>> subroutine bar (alloc, x, alloc2)
>> logical :: alloc, alloc2
>> class(t), allocatable, intent(out) :: x(:)
>> if (allocated (x)) stop 1
>> if (.not. alloc) stop 2
>> if (.not. alloc2) stop 3
>> end subroutine bar
>> end
>
> while it looks artificial, it is valid, and IMHO it is a beast...
>
> I've played around and added another argument gfc_se *convse to
> gfc_conv_class_to_class in an attempt to implement what I thought
> you suggested (to get the .pre/.post separately), but in the end
> this did not lead to working code. And the tree-dump for your
> example above is beyond what I can grasp.
>
> I've noticed that my attempt does not properly handle the
> parmse.post; at least this is what the above example shows:
> there is a small part after the call to bar that should have
> been executed before that call, which I attribute to .post.
> But my attempts in moving that part regresses on a couple
> of testcases with class and intent(out). I am at a loss now.
>
All that I can see after the call is a reassignment of the original data
and vptr pointers from the temporary class container. They seem at
their right place there. But part of the expression seems to be
evaluated again, instead of being picked up from parmse.expr.
> I am attaching the latest version of my patch to give you or
> Paul or others the opportunity to see what is wrong or add the
> missing pieces.
>
I'm attaching what I have (lightly) tested so far, which doesn't work.
It seems gfc_conv_class_to_class reevaluates part of the original
expression, which is not correct after deallocation.
Will have a look again tonight.
Mikael
Hi Mikael,
Am 07.07.23 um 14:21 schrieb Mikael Morin:
> I'm attaching what I have (lightly) tested so far, which doesn't work.
> It seems gfc_conv_class_to_class reevaluates part of the original
> expression, which is not correct after deallocation.
this looks much more elegant than my attempt that passed an additional
argument to gfc_conv_class_to_class, to achieve what your patch does.
> Will have a look again tonight.
Great.
Harald
Hello,
Le 07/07/2023 à 20:23, Harald Anlauf a écrit :
> Hi Mikael,
>
> Am 07.07.23 um 14:21 schrieb Mikael Morin:
>> I'm attaching what I have (lightly) tested so far, which doesn't work.
>> It seems gfc_conv_class_to_class reevaluates part of the original
>> expression, which is not correct after deallocation.
>
> this looks much more elegant than my attempt that passed an additional
> argument to gfc_conv_class_to_class, to achieve what your patch does.
>
>> Will have a look again tonight.
>
> Great.
>
> Harald
>
here is what I'm finally coming to. This patch fixes my example, but is
otherwise untested.
The patch has grown enough that I'm tempted to fix my example
separately, in its own commit.
Mikael
Hi Mikael,
Am 08.07.23 um 14:07 schrieb Mikael Morin:
> here is what I'm finally coming to. This patch fixes my example, but is
> otherwise untested.
> The patch has grown enough that I'm tempted to fix my example
> separately, in its own commit.
alright. I've interpreted this as a green light for v2 of my patch
and pushed it as r14-2395-gb1079fc88f082d
https://gcc.gnu.org/g:b1079fc88f082d3c5b583c8822c08c5647810259
so that you can build upon it.
> Mikael
Thanks,
Harald
From 609ba636927811cddc74fb815cb18809c7d33565 Mon Sep 17 00:00:00 2001
From: Harald Anlauf <anlauf@gmx.de>
Date: Sun, 2 Jul 2023 22:14:19 +0200
Subject: [PATCH] Fortran: fixes for procedures with ALLOCATABLE,INTENT(OUT)
arguments [PR92178]
gcc/fortran/ChangeLog:
PR fortran/92178
* trans-expr.cc (gfc_conv_procedure_call): Check procedures for
allocatable dummy arguments with INTENT(OUT) and move deallocation
of actual arguments after evaluation of argument expressions before
the procedure is executed.
gcc/testsuite/ChangeLog:
PR fortran/92178
* gfortran.dg/pr92178.f90: New test.
* gfortran.dg/pr92178_2.f90: New test.
Co-authored-by: Steven G. Kargl <kargl@gcc.gnu.org>
---
gcc/fortran/trans-expr.cc | 52 ++++++++++++++--
gcc/testsuite/gfortran.dg/pr92178.f90 | 83 +++++++++++++++++++++++++
gcc/testsuite/gfortran.dg/pr92178_2.f90 | 46 ++++++++++++++
3 files changed, 177 insertions(+), 4 deletions(-)
create mode 100644 gcc/testsuite/gfortran.dg/pr92178.f90
create mode 100644 gcc/testsuite/gfortran.dg/pr92178_2.f90
@@ -6085,9 +6085,10 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
else
info = NULL;
- stmtblock_t post, clobbers;
+ stmtblock_t post, clobbers, dealloc_blk;
gfc_init_block (&post);
gfc_init_block (&clobbers);
+ gfc_init_block (&dealloc_blk);
gfc_init_interface_mapping (&mapping);
if (!comp)
{
@@ -6117,6 +6118,33 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
&& UNLIMITED_POLY (sym)
&& comp && (strcmp ("_copy", comp->name) == 0);
+ /* First scan argument list for allocatable actual arguments passed to
+ allocatable dummy arguments with INTENT(OUT). As the corresponding
+ actual arguments are deallocated before execution of the procedure, we
+ evaluate actual argument expressions to avoid problems with possible
+ dependencies. */
+ bool force_eval_args = false;
+ gfc_formal_arglist *tmp_formal;
+ for (arg = args, tmp_formal = formal; arg != NULL;
+ arg = arg->next, tmp_formal = tmp_formal ? tmp_formal->next : NULL)
+ {
+ e = arg->expr;
+ fsym = tmp_formal ? tmp_formal->sym : NULL;
+ if (e && fsym
+ && e->expr_type == EXPR_VARIABLE
+ && fsym->attr.intent == INTENT_OUT
+ && (fsym->ts.type == BT_CLASS && fsym->attr.class_ok
+ ? CLASS_DATA (fsym)->attr.allocatable
+ : fsym->attr.allocatable)
+ && e->symtree
+ && e->symtree->n.sym
+ && gfc_variable_attr (e, NULL).allocatable)
+ {
+ force_eval_args = true;
+ break;
+ }
+ }
+
/* Evaluate the arguments. */
for (arg = args, argc = 0; arg != NULL;
arg = arg->next, formal = formal ? formal->next : NULL, ++argc)
@@ -6680,7 +6708,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
else
tmp = gfc_finish_block (&block);
- gfc_add_expr_to_block (&se->pre, tmp);
+ gfc_add_expr_to_block (&dealloc_blk, tmp);
}
/* A class array element needs converting back to be a
@@ -6980,7 +7008,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
build_empty_stmt (input_location));
}
if (tmp != NULL_TREE)
- gfc_add_expr_to_block (&se->pre, tmp);
+ gfc_add_expr_to_block (&dealloc_blk, tmp);
}
tmp = parmse.expr;
@@ -7004,7 +7032,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
void_type_node,
gfc_conv_expr_present (e->symtree->n.sym),
tmp, build_empty_stmt (input_location));
- gfc_add_expr_to_block (&se->pre, tmp);
+ gfc_add_expr_to_block (&dealloc_blk, tmp);
}
}
}
@@ -7101,6 +7129,21 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
}
}
+ /* If any actual argument of the procedure is allocatable and passed
+ to an allocatable dummy with INTENT(OUT), we conservatively
+ evaluate all actual argument expressions before deallocations are
+ performed and the procedure is executed. This ensures we conform
+ to F2023:15.5.3, 15.5.4. Create temporaries except for constants,
+ variables, and functions returning pointers that can appear in a
+ variable definition context. */
+ if (e && fsym && force_eval_args
+ && e->expr_type != EXPR_VARIABLE
+ && !gfc_is_constant_expr (e)
+ && (e->expr_type != EXPR_FUNCTION
+ || !(gfc_expr_attr (e).pointer
+ || gfc_expr_attr (e).proc_pointer)))
+ parmse.expr = gfc_evaluate_now (parmse.expr, &parmse.pre);
+
if (fsym && need_interface_mapping && e)
gfc_add_interface_mapping (&mapping, fsym, &parmse, e);
@@ -7499,6 +7542,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
vec_safe_push (arglist, parmse.expr);
}
+ gfc_add_block_to_block (&se->pre, &dealloc_blk);
gfc_add_block_to_block (&se->pre, &clobbers);
gfc_finish_interface_mapping (&mapping, &se->pre, &se->post);
new file mode 100644
@@ -0,0 +1,83 @@
+! { dg-do run }
+!
+! PR fortran/92178
+! Re-order argument deallocation
+
+program p
+ implicit none
+ integer, allocatable :: a(:)
+ class(*), allocatable :: c(:)
+ type t
+ integer, allocatable :: a(:)
+ end type t
+ type(t) :: b
+ integer :: k = -999
+
+ ! Test based on original PR
+ a = [1]
+ call assign (a, (max(a(1),0)))
+ if (allocated (a)) stop 9
+ if (k /= 1) stop 10
+
+ ! Additional variations based on suggestions by Tobias Burnus
+ ! to check that argument expressions are evaluated early enough
+ a = [1, 2]
+ call foo (allocated (a), size (a), test (a), a)
+ if (allocated (a)) stop 11
+
+ a = [1, 2]
+ k = 1
+ call foo (allocated (a), size (a), test (k*a), a)
+ if (allocated (a)) stop 12
+
+ b% a = [1, 2]
+ call foo (allocated (b% a), size (b% a), test (b% a), b% a)
+ if (allocated (b% a)) stop 13
+
+ c = [3, 4]
+ call bar (allocated (c), size (c), test2 (c), c)
+ if (allocated (c)) stop 14
+
+contains
+
+ subroutine assign (a, i)
+ integer, allocatable, intent(out) :: a(:)
+ integer, value :: i
+ k = i
+ end subroutine
+
+ subroutine foo (alloc, sz, tst, x)
+ logical, value :: alloc, tst
+ integer, value :: sz
+ integer, allocatable, intent(out) :: x(:)
+ if (allocated (x)) stop 1
+ if (.not. alloc) stop 2
+ if (sz /= 2) stop 3
+ if (.not. tst) stop 4
+ end subroutine foo
+ !
+ logical function test (zz)
+ integer :: zz(2)
+ test = zz(2) == 2
+ end function test
+ !
+ subroutine bar (alloc, sz, tst, x)
+ logical, value :: alloc, tst
+ integer, value :: sz
+ class(*), allocatable, intent(out) :: x(:)
+ if (allocated (x)) stop 5
+ if (.not. alloc) stop 6
+ if (sz /= 2) stop 7
+ if (.not. tst) stop 8
+ end subroutine bar
+ !
+ logical function test2 (zz)
+ class(*), intent(in) :: zz(:)
+ select type (zz)
+ type is (integer)
+ test2 = zz(2) == 4
+ class default
+ stop 99
+ end select
+ end function test2
+end
new file mode 100644
@@ -0,0 +1,46 @@
+! { dg-do run }
+!
+! PR fortran/92178
+! Contributed by Tobias Burnus
+
+program foo
+ implicit none (type, external)
+
+ type t
+ end type t
+
+ type, extends(t) :: t2
+ end type t2
+
+ type(t2) :: x2
+ class(t), allocatable :: aa
+
+ call check_intentout_false(allocated(aa), aa, &
+ allocated(aa))
+ if (allocated(aa)) stop 1
+
+ allocate(t2 :: aa)
+ if (.not.allocated(aa)) stop 2
+ if (.not.same_type_as(aa, x2)) stop 3
+ call check_intentout_true(allocated(aa), (same_type_as(aa, x2)), aa, &
+ allocated(aa), (same_type_as(aa, x2)))
+ if (allocated(aa)) stop 4
+
+contains
+ subroutine check_intentout_false(alloc1, yy, alloc2)
+ logical, value :: alloc1, alloc2
+ class(t), allocatable, intent(out) :: yy
+ if (allocated(yy)) stop 11
+ if (alloc1) stop 12
+ if (alloc2) stop 13
+ end subroutine check_intentout_false
+ subroutine check_intentout_true(alloc1, same1, zz, alloc2, same2)
+ logical, value :: alloc1, alloc2, same1, same2
+ class(t), allocatable, intent(out) :: zz
+ if (allocated(zz)) stop 21
+ if (.not.alloc1) stop 22
+ if (.not.alloc2) stop 23
+ if (.not.same1) stop 24
+ if (.not.same2) stop 25
+ end subroutine check_intentout_true
+end program
--
2.35.3