Discussion:
MOVE_ALLOC() question
(too old to reply)
Ron Shepard
2020-03-06 16:00:08 UTC
Permalink
I have a question about the usage of the MOVE_ALLOC() intrinsic. Suppose
I have a stack implemented as

type stack_type
type(stack_type), allocatable :: previous
...other data
end type stack_type
type(stack_type), allocatable :: stack, temp

Suppose that I have some entries that have been pushed onto the stack,
and I want to pop the last entry from the stack. This might be done as

call MOVE_ALLOC( from=stack%previous, to=temp )
call MOVE_ALLOC( from=temp, to=stack )

or as

call MOVE_ALLOC( from=stack, to=temp )
call MOVE_ALLOC( from=temp%previous, to=stack )

or as

call MOVE_ALLOC( from=stack%previous, to=stack )

The first option leaves temp unallocated, while it remains allocated in
the second case (with the data associated with the last stack entry). Of
course, temp is unchanged in the last case.

Are all of those equivalent as far as popping the stack? Particularly
that last option is not clear. I can see that it *could* be implemented
so that it works correctly, but I can also see how if the deallocations
are done in the wrong order, it could fail, and I can't really tell from
reading the standard whether or not that is required to work.

If the from= and to= are the same variable, then the standard requires
that variable to be unallocated after the call, which indirectly implies
something about the order of the deallocation in that case. But I don't
know if stack%previous and stack are the same variable in standardspeak,
so I don't know if that applies.

$.02 -Ron Shepard
FortranFan
2020-03-06 17:14:20 UTC
Permalink
Post by Ron Shepard
I have a question about the usage of the MOVE_ALLOC() intrinsic. Suppose
I have a stack implemented as
type stack_type
type(stack_type), allocatable :: previous
...other data
end type stack_type
type(stack_type), allocatable :: stack, temp
Suppose that I have some entries that have been pushed onto the stack,
and I want to pop the last entry from the stack. This might be done as
call MOVE_ALLOC( from=stack%previous, to=temp )
call MOVE_ALLOC( from=temp, to=stack )
or as
call MOVE_ALLOC( from=stack, to=temp )
call MOVE_ALLOC( from=temp%previous, to=stack )
or as
call MOVE_ALLOC( from=stack%previous, to=stack )
The first option leaves temp unallocated, while it remains allocated in
the second case (with the data associated with the last stack entry). Of
course, temp is unchanged in the last case.
Are all of those equivalent as far as popping the stack? ..
For whatever it's worth, all 3 options look equivalent to me from a Fortran language perspective.

There is a 4th option also that is supported by the language and it is simply:

stack = stack%previous

As to any other considerations which may be implied by OP with the inquiry in the original post on whether the options are 'equivalent' e.g., *efficiency* of the 'pop' operation particularly as the 'stack' becomes large, I believe that can vary considerably with how the derived type is designed (e.g., whether any 'move semantics' has been explicitly coded in by the developer whereas OP's constant pejorative of 'lesser' languages tend to offer such facilities in the language itself c.f. https://www.oreilly.com/radar/2-major-reasons-why-modern-c-is-a-performance-beast/) and with compiler implementations of this Fortran 2008 feature of 'allocatable components of recursive type'.

Two Fortran compilers I know suffered from several bugs for quite a while with both 'push' and 'pop' operations with derived types that model stack containers using this Fortran 2008 feature.
g***@u.washington.edu
2020-03-07 00:13:39 UTC
Permalink
On Friday, March 6, 2020 at 9:14:23 AM UTC-8, FortranFan wrote:

(snip)
Post by FortranFan
stack = stack%previous
I think I don't know allocate on assignment well enough, but I suspect
that this might allocate a new one, copy the data over, and deallocate
the old one. (Including MOVE_ALLOC the rest of the stack.)
Post by FortranFan
As to any other considerations which may be implied by OP with
the inquiry in the original post on whether the options are
'equivalent' e.g., *efficiency* of the 'pop' operation particularly
as the 'stack' becomes large, I believe that can vary considerably
with how the derived type is designed (e.g., whether any
'move semantics' has been explicitly coded in by the developer
whereas OP's constant pejorative of 'lesser' languages tend
to offer such facilities in the language itself
(snip)

Before MOVE_ALLOC, it took two allocations to resize an
allocatable array. Now it only takes one.

C's realloc() has the ability, though no guarantee, of doing
it in place, with no new allocation.

The important thing about ALLOCABLE is that, unlike POINTER,
there can only be one variable with the data at a time. That is,
the MOVE is an important part of MOVE_ALLOC.

With pointers, you can make a copy of a pointer, which is equal
in all ways to the original. Both point to the same data. If it
is DEALLOCATEd, both don't point to data. (But you might not
know that.)

In java:

stack = stack.previous;

would be usual, as the variables are object references, and
the old one is reclaimed by garbage collection.
Ron Shepard
2020-03-07 08:50:24 UTC
Permalink
Post by FortranFan
For whatever it's worth, all 3 options look equivalent to me from a Fortran language perspective.
stack = stack%previous
I wondered about this one, and I convinced myself that this involves a
deep copy of the stack. Consider

temp = stack%previous
stack = temp

That is obviously a deep copy (the time depends on the stack depth), and
it leaves both stack and temp with separate copies of the same data. It
seemed to me that stack=stack%previous would do the same thing, except
possibly if the optimizer realizes what is happening and sees the shortcut.

All of the MOVE_ALLOC() options are shallow copies, meaning they operate
in time that is independent of the depth of the stack. Also with the
shallow copy, pointers to entities within stack remain defined, whereas
they would not with the deep copy approach.
Post by FortranFan
As to any other considerations which may be implied by OP with the inquiry in the original post on whether the options are 'equivalent' e.g., *efficiency* of the 'pop' operation particularly as the 'stack' becomes large, I believe that can vary considerably with how the derived type is designed (e.g., whether any 'move semantics' has been explicitly coded in by the developer whereas OP's constant pejorative of 'lesser' languages tend to offer such facilities in the language itself c.f. https://www.oreilly.com/radar/2-major-reasons-why-modern-c-is-a-performance-beast/) and with compiler implementations of this Fortran 2008 feature of 'allocatable components of recursive type'.
Do any other languages have something comparable to fortran
allocatables? As far as I know, this would need to be done with pointers
in c and other languages. Of course, that is how the low-level
implementation is done in fortran too, but the high-level construct of
allocatable is different from the pointers in these other languages (or
in fortran too, with its higher-level pointer type). In many ways, you
can think of allocatables as being tame pointers. So I still maintain
that fortran is a superior language in this respect, along with its more
flexible handling of arrays, and its automatic generation of interfaces,
and so on.
Post by FortranFan
Two Fortran compilers I know suffered from several bugs for quite a while with both 'push' and 'pop' operations with derived types that model stack containers using this Fortran 2008 feature.
I think MOVE_ALLOC() itself is f2003. I'm not sure about the ability to
create stacks with allocatables, that might well have been in the later
revision.

I complain here in c.l.f. often about what seems to me to be missing
features in fortran. Another missing feature is the ability to redefine
array bounds in a shallow-copy-like manner. For example, suppose you
have an allocatable array dimensioned as (1:11), and you want to change
it to (-5:5). I think now you must make a copy, deallocate and allocate
the original array with the new bounds, and then copy the data back.
There should be a simple way to just modify the metadata to do that same
thing, and like MOVE_ALLOC(), it should leave all existing pointers to
that data defined. Is there something that accomplishes that already in
the language?

$.02 -Ron Shepard
r***@gmail.com
2020-03-07 09:39:26 UTC
Permalink
Post by Ron Shepard
Do any other languages have something comparable to fortran
allocatables?
PL/I has allocatable arrays.
It also has dynamic arrays (aka automatic) and static.

Algol 60 has dynamic arrays.

In PL/I, scalar variable also can be allocatable,
and provides a simple means of setting up a push-down pop-up stack.
g***@u.washington.edu
2020-03-07 10:49:13 UTC
Permalink
Post by Ron Shepard
Post by FortranFan
For whatever it's worth, all 3 options look equivalent to me
from a Fortran language perspective.
stack = stack%previous
I wondered about this one, and I convinced myself that this involves a
deep copy of the stack. Consider
temp = stack%previous
stack = temp
I had only thought about one level, but I suspect that to simplify
the thought process, I was thinking about a two element stack.

Yes it does seem like it is a deep copy.
Post by Ron Shepard
That is obviously a deep copy (the time depends on the stack depth), and
it leaves both stack and temp with separate copies of the same data. It
seemed to me that stack=stack%previous would do the same thing, except
possibly if the optimizer realizes what is happening and sees the shortcut.
All of the MOVE_ALLOC() options are shallow copies, meaning they operate
in time that is independent of the depth of the stack. Also with the
shallow copy, pointers to entities within stack remain defined, whereas
they would not with the deep copy approach.
To me, shallow copy is when you copy one level, but MOVE_ALLOC
should be copying zero levels.
Ron Shepard
2020-03-07 18:49:31 UTC
Permalink
This post might be inappropriate. Click to display it.
e***@gmail.com
2020-03-09 11:00:12 UTC
Permalink
Post by Ron Shepard
Post by g***@u.washington.edu
To me, shallow copy is when you copy one level, but MOVE_ALLOC
should be copying zero levels.
Here is how I was using that term. Consider the defined type
type xxx
integer, allocatable :: a(:)
integer, pointer :: pa(:)
end type xxx
type(xxx), target :: x, y
x%a = [1,2,3,4]
x%pa => x%a
Ok, now the question is what happens with the assignment
y = x
The answer is that y%a is a newly allocated array with a copy of the x%a
values. That is a deep copy, although in this case there is no recursion
as there was in the original example, it is just a single level. But
y%pa points back to x%a, it does not point to the new copy. That is a
shallow copy, although without the recursion aspect of the original
example. The pointer metadata is copied, but not the data. Practically,
the deep copy involves memory allocations and memory transfer, while the
shallow copy only involves defining the metadata associated with the array.
This also leads to the following issue. After the statement
x%a(:) = [5,6,7,8]
all of the pointers now point to the new values, and y%a still has the
original values. But the assignment
x%a = [-1,-2,-3]
without the parentheses on the left now causes all the pointers to
become undefined. Allocate on assignment kicked in, and that is the
result. But what if the assignment had been
x%a = [-1,-2,-3,-4]
No new allocation would be done, probably, because the sizes and bounds
all match, yet the pointers are now undefined, same as before.
Why should the pointer be undefined as according to the standard (if I correctly understand it) there is non deallocation?

The gfortran has the -Wrealloc-lhs to warn about reallocation of lhs, you can try to see if that is the problem (compiler bugs may be still present of course).

Many time I have used the -fsanitize=address of gfortran, that it is like valgrind but the program has a slowdown of only a factor 2.
Post by Ron Shepard
compiler could, if it wanted to, move the allocation even though the
bounds are the same before and after. Some compilers go to effort to
warn you about the possible reallocation in statements like this. What
if the right hand side array were a few GB rather than a few bytes? The
compiler might well think it is doing you a big favor by avoiding that
unnecessary memory transfer.
I have a bug in one of my codes that I am almost certain is due to this.
I wrote the code carefully to avoid that trap, but I must have made a
mistake somewhere. The code runs dozens of times without error, and then
every so often it dumps core. I change compiler options, and the bug
goes away. I change compilers, the bug goes away. I have pointers to
allocatables that need to stay defined at least temporarily, and I think
that is the problem. I somewhere assumed that the sizes match, so no
reallocation would be done, but the compiler instead decided to change
allocation rather than to copy the values. Somewhere, within a few
thousand lines of code, I need to add some parentheses to the left of an
assignment to block the reallocation, and I have not yet located where,
not even with valgrind.
The first versions of this code were written before the allocate on
assignment feature was in the language, and I often did not put the (:),
(:,:), (:,:,:), etc. on the left hand side of assignments. It didn't
accomplish anything, and it seemed to be redundant. Then the code
evolved after that and after the feature was introduced to the language.
So this could well be due to a line of code written in the 1990s that is
coming back to haunt me now. Allocate on assignment is a useful feature,
I don't want it removed from the language, and I admit this is probably
my code mistake. But it sure can be difficult to locate these kinds of
errors.
$.02 -Ron Shepard
Ron Shepard
2020-03-09 16:36:49 UTC
Permalink
Post by e***@gmail.com
Post by Ron Shepard
No new allocation would be done, probably, because the sizes and bounds
all match, yet the pointers are now undefined, same as before.
Why should the pointer be undefined as according to the standard (if I correctly understand it) there is non deallocation?
In this situation, I think the compiler has to option to either
reallocate or copy the values. For scalars and small vectors on the
right, the most efficient way is to copy into the existing memory. For
large arrays (as determined by the compiler, not the standard), it is
more efficient to move the allocation and to avoid the actual memory
references associated with the copy.
Post by e***@gmail.com
The gfortran has the -Wrealloc-lhs to warn about reallocation of lhs, you can try to see if that is the problem (compiler bugs may be still present of course).
As I understand that option, the warning tells you when code has been
inserted that might invoke reallocation. I think the choice is made
later at runtime, and I do not know of a way to turn on warnings when it
actually occurs.
Post by e***@gmail.com
Many time I have used the -fsanitize=address of gfortran, that it is like valgrind but the program has a slowdown of only a factor 2.
Thanks for the suggestion. I will look into that. I am currently
initializing floating point values to NaN and integers to a large
negative integer to try to catch errors, so I will look into this option
too. For this kind of tedious debugging, performance is not a factor.
Ironically in this case, the -O code almost always runs without errors,
it is the -O0 code that sometimes fails; usually it is the opposite.

$.02 -Ron Shepard
FortranFan
2020-03-09 22:27:54 UTC
Permalink
Post by Ron Shepard
..
Here is how I was using that term. Consider the defined type
type xxx
integer, allocatable :: a(:)
integer, pointer :: pa(:)
end type xxx
type(xxx), target :: x, y
x%a = [1,2,3,4]
x%pa => x%a
Ok, now the question is what happens with the assignment
y = x
..
I have a bug in one of my codes that I am almost certain is due to this.
I wrote the code carefully to avoid that trap, but I must have made a
mistake somewhere. The code runs dozens of times without error, and then
every so often it dumps core. I change compiler options, and the bug
goes away. I change compilers, the bug goes away. I have pointers to
allocatables that need to stay defined at least temporarily, and I think
that is the problem. ..
.. Allocate on assignment is a useful feature,
I don't want it removed from the language, and I admit this is probably
my code mistake. But it sure can be difficult to locate these kinds of
errors.
..
@Ron Shepard,

It'll be highly useful if you can really expend some time to thoroughly debug and fully diagnose the issue you've experienced with your code rather than "let sleeping dogs lie" in your code but nonetheless comment your way to "glory" on this forum. Or if you're pressed for time, if you can share your code some place online - say GitHub? Yes, everyone knows it can be excruciatingly difficult but that's no excuse particularly if one's going to allude to that in a public forum in the context of some limitation of a current situation or with an allusion to some improvement toward it.

That way, one can get to the root of the issue(s) and put together a small working example that will allow one to really contemplate how the Fortran language and its semantics might be hindering (or helping) with the troublesome situation.

Currently your verbiage provides no details and that can only further the discussion *in vacuo* which won't be helpful at all from a Fortran language improvement perspective.
Ron Shepard
2020-03-10 07:55:37 UTC
Permalink
Post by FortranFan
It'll be highly useful if you can really expend some time to thoroughly debug and fully diagnose the issue you've experienced with your code rather than "let sleeping dogs lie" in your code but nonetheless comment your way to "glory" on this forum.
I do not understand this sentence. I have never used those terms you are
quoting, so I do not understand the context of your statement.
Post by FortranFan
Or if you're pressed for time, if you can share your code some place online - say GitHub? Yes, everyone knows it can be excruciatingly difficult but that's no excuse particularly if one's going to allude to that in a public forum in the context of some limitation of a current situation or with an allusion to some improvement toward it.
I do plan to put the code on one of the public cloud servers, but I
would like to finish it and debug it first.

In addition to trying various compiler options, and also different
compilers, I have used valgrind. I have also attempted to use gdb, but
for the last couple of years, gdb has been broken on MacOS. The gdb
documentation at the web page says they are working on fixing this, but
like all public software, schedules are never firm.

I can use the intel debugger with ifort, but my code has never displayed
the problem with ifort, so that doesn't help.
Post by FortranFan
That way, one can get to the root of the issue(s) and put together a small working example that will allow one to really contemplate how the Fortran language and its semantics might be hindering (or helping) with the troublesome situation.
The code I posted before explains the problem with the combination of
allocate-on-assignment and pointers. I am not certain that is the root
of the bug in my code, but I suspect it is. My code does use
allocatables, and it does use pointers to them.
Post by FortranFan
Currently your verbiage provides no details and that can only further the discussion*in vacuo* which won't be helpful at all from a Fortran language improvement perspective.
As far as the landmine of allocate-on-assignment and pointers, what more
could you possibly need to know?

This is similar to the problems with fortran functions that return
pointers. There are a million ways to screw that up, so most programmers
know to avoid it.

I do not believe this is a compiler bug. It shows up with -O0, and there
are very few gfortran bugs that occur that way. It is possible, of
course, but I do not think it is probable. My working assumption is that
this is my error.

$.02 -Ron Shepard
g***@u.washington.edu
2020-03-07 11:01:47 UTC
Permalink
On Saturday, March 7, 2020 at 12:50:27 AM UTC-8, Ron Shepard wrote:

(snip)
Post by Ron Shepard
Do any other languages have something comparable to fortran
allocatables? As far as I know, this would need to be done with pointers
in c and other languages. Of course, that is how the low-level
implementation is done in fortran too, but the high-level construct of
allocatable is different from the pointers in these other languages (or
in fortran too, with its higher-level pointer type). In many ways, you
can think of allocatables as being tame pointers. So I still maintain
that fortran is a superior language in this respect, along with its more
flexible handling of arrays, and its automatic generation of interfaces,
and so on.
PL/I has CONTROLLED which is somewhat similar.

One difference is that with CONTROLLED, if you allocate when one is
already allocated, it stacks them. That is, this whole question goes
away, as it is built in already! I presume the stack method
is pretty convenient for recursion.

But otherwise, they also aren't FREEd when the procedure returns.

There is also CONTROLLED EXTERNAL, which is the global version.


It is also not so different from instance variable allocation
in Java, though the Java case is more like pointers, as there can
be more than one reference to the same variable, which are all equal.
But since Java uses garbage collection, you don't have to worry
about freeing them. If the references are local (auto) variables,
then they go away when the method returns. There is no automatic
deep copy in Java.
g***@u.washington.edu
2020-03-07 11:05:10 UTC
Permalink
On Saturday, March 7, 2020 at 12:50:27 AM UTC-8, Ron Shepard wrote:

(snip)
Post by Ron Shepard
I complain here in c.l.f. often about what seems to me to be missing
features in fortran. Another missing feature is the ability to redefine
array bounds in a shallow-copy-like manner. For example, suppose you
have an allocatable array dimensioned as (1:11), and you want to change
it to (-5:5). I think now you must make a copy, deallocate and allocate
the original array with the new bounds, and then copy the data back.
There should be a simple way to just modify the metadata to do that same
thing, and like MOVE_ALLOC(), it should leave all existing pointers to
that data defined. Is there something that accomplishes that already in
the language?
My first thought was C_F_POINTER, but it always generates pointers
withe lower bound one. So, you can convert other lower bounds to 1,
but not other than one, with C_PTR and C_F_POINTER.

Seems to be a defect in C_F_POINTER. (It should at least allow
for a lower bound of 0, to match the C pointer.)
FortranFan
2020-03-07 15:29:05 UTC
Permalink
Post by Ron Shepard
..
I complain here in c.l.f. often about what seems to me to be missing
features in fortran. ..
@Ron Shepard,

Again, you should seriously consider posting all your suggestions at the GitHub site for Fortran proposals:
https://github.com/j3-fortran/fortran_proposals
Post by Ron Shepard
Another missing feature is the ability to redefine
array bounds in a shallow-copy-like manner. For example, suppose you
have an allocatable array dimensioned as (1:11), and you want to change
it to (-5:5). ..
Fortran offers a couple of options here:

1) for arrays with TARGET attribute, one can employ POINTERs with bounds of one's choice:

integer, target :: n(11)
..
integer, pointer :: pn(:)
..
pn(-5:) => n

2) for arrays which are dummy arguments of assumed shape, one can define the bounds on the declaration itself
<some type> :: foo(11)
..
call sub( .., foo, .. )
..
subroutine sub( .., arg, .. )
..
<some type>, intent(..) :: foo(-5:)

Here's an example anyone familiarizing themselves with Fortran can try:

integer, target :: n(11)
integer, pointer :: pn(:)
print *, "lbound(n) = ", lbound(n, dim=1)
print *, "ubound(n) = ", ubound(n, dim=1)
pn(-5:) => n
print *, "lbound(pn) = ", lbound(pn, dim=1)
print *, "ubound(pn) = ", ubound(pn, dim=1)
call sub( n )
contains
subroutine sub( a )
integer, intent(in) :: a(-5:)
print *, "In sub with dummy argument 'a'"
print *, "lbound(a) = ", lbound(a, dim=1)
print *, "ubound(a) = ", ubound(a, dim=1)
end subroutine
end

Upon execution, the output will be:

lbound(n) = 1
ubound(n) = 11
lbound(pn) = -5
ubound(pn) = 5
In sub with dummy argument 'a'
lbound(a) = -5
ubound(a) = 5
Ron Shepard
2020-03-07 17:39:11 UTC
Permalink
This post might be inappropriate. Click to display it.
FortranFan
2020-03-07 15:43:12 UTC
Permalink
Post by Ron Shepard
..
Do any other languages have something comparable to fortran
allocatables? ..
The so-called "smart pointer" set of functionalities introduced in C++ starting with C++ 11 standard pretty much brings the benefits similar to ALLOCATABLEs in Fortran to that language:
https://en.cppreference.com/book/intro/smart_pointers

When one combines many of the advantages of C++ - a tremendously strong ecosystem for developers, it being taught and practiced far more widely than Fortran in academia including in scientific and computing domains, an already large and yet growing pool of top talent both using and advancing C++ including in compiler optimization and also the growing libraries of high performance code which can readily interoperate with many front-end platforms including Python, .NET, web/mobile, etc. - all combine to make many organization to move a lot of libraries and back-end "engine" code where performance may be critical also to C++.
g***@u.washington.edu
2020-03-06 23:56:46 UTC
Permalink
Post by Ron Shepard
I have a question about the usage of the MOVE_ALLOC() intrinsic. Suppose
I have a stack implemented as
type stack_type
type(stack_type), allocatable :: previous
...other data
end type stack_type
type(stack_type), allocatable :: stack, temp
Suppose that I have some entries that have been pushed onto the stack,
and I want to pop the last entry from the stack. This might be done as
call MOVE_ALLOC( from=stack%previous, to=temp )
call MOVE_ALLOC( from=temp, to=stack )
or as
call MOVE_ALLOC( from=stack, to=temp )
call MOVE_ALLOC( from=temp%previous, to=stack )
or as
call MOVE_ALLOC( from=stack%previous, to=stack )
I believe this (third) one fails, at least according
to the standard. (Which doesn't mean it won't work.)

The description says first that if TO is allocated, then
it is deallocated. You can't access it, such as stack%previous,
after that. You might be (un)lucky to find that the value
is actually still there, and that it is found at the appropriate
time, but can't rely on that.
Post by Ron Shepard
The first option leaves temp unallocated, while it remains allocated in
the second case (with the data associated with the last stack entry). Of
course, temp is unchanged in the last case.
Sounds right to me.
Post by Ron Shepard
Are all of those equivalent as far as popping the stack? Particularly
that last option is not clear. I can see that it *could* be implemented
so that it works correctly, but I can also see how if the deallocations
are done in the wrong order, it could fail, and I can't really tell from
reading the standard whether or not that is required to work.
If the from= and to= are the same variable, then the standard requires
that variable to be unallocated after the call, which indirectly implies
something about the order of the deallocation in that case. But I don't
know if stack%previous and stack are the same variable in standardspeak,
so I don't know if that applies.
The standard does say that TO is unallocated first if it is allocated.

It seems to be that, after that, if FROM is unallocated that it doesn't
do anything else. So if it is the same variable, it doesn't do anything.
p***@gmail.com
2020-03-14 18:27:12 UTC
Permalink
Hi Ron,
Post by Ron Shepard
call MOVE_ALLOC( from=stack%previous, to=temp )
call MOVE_ALLOC( from=temp, to=stack )
This is what appears in the gfortran testsuite 'recursive_alloc_comp_3.f08' - see below. ( s/top/stack/ and s/next/previous/ )

As required by the standard, FROM becomes unallocated so that subsequent uses of 'temp' do not cause any side effects.

You might find the use of a pointer as an iterator (subroutine 'output') to be a handy trick ;-)
Post by Ron Shepard
or as
call MOVE_ALLOC( from=stack, to=temp )
call MOVE_ALLOC( from=temp%previous, to=stack )
This works for the testcase below but note that temp does not become unallocated. It contains the payload of the original top of stack, albeit with the 'previous' component nullified. Any other allocatable resources in the payload will remain allocated, leading to possible side effects if temp is used again.
Post by Ron Shepard
or as
call MOVE_ALLOC( from=stack%previous, to=stack )
This, in principle, would do the same. However,gfortran emits:

Error: The FROM and TO arguments at (1) violate aliasing restrictions (F2003 12.4.1.7)

stack%previous = stack

This deallocates everything below 'stack%previous' and then creates a closed loop, where stack points to itself, with the obvious consequence in subroutine 'output'.

(Note to self: The compiler should generate a runtime error if an allocatable component winds up pointing to its parent derived type.)

You might find 'recursive_alloc_comp_[1,2].f08' interesting as well. 'recursive_alloc_comp_4.f08' tests allocatable array components of the parent type and demonstrates that these are tricky to use, to say the least of it :-)

Regards

Paul

! { dg-do run }
!
! Tests functionality of recursive allocatable derived types.
!
module m
type :: stack
integer :: value
integer :: index
type(stack), allocatable :: next
end type stack
end module

use m
! Here is how to add a new entry at the top of the stack:
type (stack), allocatable :: top, temp, dum

call poke (1)
call poke (2)
call poke (3)
if (top%index .ne. 3) STOP 1
call output (top)
call pop
if (top%index .ne. 2) STOP 2
call output (top)
deallocate (top)
contains
subroutine output (arg)
type(stack), target, allocatable :: arg
type(stack), pointer :: ptr

if (.not.allocated (arg)) then
print *, "empty stack"
return
end if

print *, " idx value"
ptr => arg
do while (associated (ptr))
print *, ptr%index, " ", ptr%value
ptr => ptr%next
end do
end subroutine
subroutine poke(arg)
integer :: arg
integer :: idx
if (allocated (top)) then
idx = top%index + 1
else
idx = 1
end if
allocate (temp)
temp%value = arg
temp%index = idx
call move_alloc(top,temp%next)
call move_alloc(temp,top)
end subroutine
subroutine pop
call move_alloc(top%next,temp)
call move_alloc(temp,top)
end subroutine
end
g***@u.washington.edu
2020-03-14 19:30:18 UTC
Permalink
On Saturday, March 14, 2020 at 11:27:16 AM UTC-7, ***@gmail.com wrote:

(snip)
Post by p***@gmail.com
Post by Ron Shepard
call MOVE_ALLOC( from=stack%previous, to=stack )
Error: The FROM and TO arguments at (1) violate aliasing restrictions (F2003 12.4.1.7)
From the description of MOVE_ALLOC, it deallocates TO, if allocated,
as the first step. At that point, you can't reference stack%previous
anymore, so the message seems correct.
Ron Shepard
2020-03-14 20:57:39 UTC
Permalink
Post by g***@u.washington.edu
(snip)
Post by p***@gmail.com
Post by Ron Shepard
call MOVE_ALLOC( from=stack%previous, to=stack )
Error: The FROM and TO arguments at (1) violate aliasing restrictions (F2003 12.4.1.7)
From the description of MOVE_ALLOC, it deallocates TO, if allocated,
as the first step. At that point, you can't reference stack%previous
anymore, so the message seems correct.
As far as I can tell, it would be possible to redefine the MOVE_ALLOC()
procedure so that this would work correctly, and that new definition
would be consistent with the currently defined procedure. That is, the
new definition would not break current standard-conforming code.

$.02 -Ron Shepard
Ron Shepard
2020-03-14 20:50:03 UTC
Permalink
Post by p***@gmail.com
ptr => arg
do while (associated (ptr))
print *, ptr%index, " ", ptr%value
ptr => ptr%next
end do
I started a separate thread about this issue yesterday. In the above
code, what happens when arg is not allocated? And then on the last pass
through the loop, the pointer assignment is to an unallocated array. The
test in the do while loop is on the associated() status of ptr, and the
above assumes that pointer assignment to an unallocated array results in
an unassociated pointer.

Is that behavior defined by the standard?

$.02 -Ron Shepard
FortranFan
2020-03-15 04:21:26 UTC
Permalink
Post by p***@gmail.com
..
You might find the use of a pointer as an iterator (subroutine 'output') to be a handy trick ;-)
..
As explained in OP's other thread - https://groups.google.com/d/msg/comp.lang.fortran/7Er9xp5CqEc/XER2nogVAQAJ, the instruction 'ptr => ptr%next' can be non-conforming.

Also try this variant of your code to catch a segfault:

module m
type :: stack
integer :: value
integer :: index
type(stack), allocatable :: next
end type stack
end module

use m
! Here is how to add a new entry at the top of the stack:
type (stack), allocatable :: top, temp, dum

call pop
contains
subroutine output (arg)
type(stack), target, allocatable :: arg
type(stack), pointer :: ptr

if (.not.allocated (arg)) then
print *, "empty stack"
return
end if

print *, " idx value"
ptr => arg
do while (associated (ptr))
print *, ptr%index, " ", ptr%value
ptr => ptr%next
end do
end subroutine
subroutine poke(arg)
integer :: arg
integer :: idx
if (allocated (top)) then
idx = top%index + 1
else
idx = 1
end if
allocate (temp)
temp%value = arg
temp%index = idx
call move_alloc(top,temp%next)
call move_alloc(temp,top)
end subroutine
subroutine pop
call move_alloc(top%next,temp)
call move_alloc(temp,top)
end subroutine
end


Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
FortranFan
2020-03-16 12:55:23 UTC
Permalink
..
You might find 'recursive_alloc_comp_[1,2].f08' interesting as well. 'recursive_alloc_comp_4.f08' tests allocatable array components of the parent type and demonstrates that these are tricky to use, to say the least of it :-) ..
Hi Paul and any gfortran volunteers,

I believe the code variant below of Paul's example upthread is standard-conforming and it runs fine with another compiler I tried. However it encounters a run-time error with gfortran. This case might be of interest toward the gfortran test suite.

--- begin variant ---
module m

type :: stack_t
integer :: value = -1
integer :: index = 1
type(stack_t), allocatable :: next
end type stack_t

contains

subroutine poke( stack, value )

type(stack_t), intent(inout), allocatable :: stack
integer, intent(in) :: value

type(stack_t), allocatable :: item

allocate( item )
item%value = value

if ( allocated(stack) ) then
item%index = stack%index + 1
call move_alloc(stack, item%next)
end if

call move_alloc(item, stack)

return

end subroutine poke

subroutine pop( stack )

type(stack_t), intent(inout), allocatable :: stack

if ( allocated(stack) ) then
if ( allocated(stack%next) ) then
stack = stack%next
else
deallocate( stack )
end if
end if

return

end subroutine pop

recursive subroutine output( stack, head )

type(stack_t), intent(in), allocatable :: stack
logical, intent(in), optional :: head

character(len=*), parameter :: fmtg = "(t5,g0,t15,g0)"

if ( allocated(stack) ) then
if ( present(head) ) print fmtg, "index", "value"
print fmtg, stack%index, stack%value
call output( stack%next )
else if ( present(head) ) then
print *, "Empty stack"
end if

return

end subroutine output

end module

use m

type (stack_t), allocatable :: stack

call pop( stack )
call output( stack, head=.true. )

call poke( stack, value=1 )
call poke( stack, value=2 )
call poke( stack, value=3 )
call output( stack, head=.true. )

call pop( stack )
call output( stack, head=.true. )

call pop( stack )
call output( stack, head=.true. )

call pop( stack )
call pop( stack )
call output( stack, head=.true. )

call poke( stack, value=1 )
call output( stack, head=.true. )

end
--- end variant ---

Here's the console output:

C:\Temp>x86_64-w64-mingw32-gfortran.exe -Wall -std=f2018 -fcheck=all p.f90 -o p.exe

C:\Temp>p.exe
Empty stack
index value
3 3
2 2
1 1
At line 67 of file p.f90
Fortran runtime error: Recursive call to nonrecursive procedure '__deallocate_m_Stack_t'

Error termination. Backtrace:

Could not print backtrace: libbacktrace could not find executable to open
#0 0xffffffff
#1 0xffffffff
#2 0xffffffff
#3 0xffffffff
#4 0xffffffff
#5 0xffffffff
#6 0xffffffff
#7 0xffffffff
#8 0xffffffff
#9 0xffffffff
#10 0xffffffff
#11 0xffffffff
#12 0xffffffff

C:\Temp>
Ron Shepard
2020-03-17 18:52:21 UTC
Permalink
[...]
Post by FortranFan
I believe the code variant below of Paul's example upthread is standard-conforming and it runs fine with another compiler I tried. However it encounters a run-time error with gfortran. This case might be of interest toward the gfortran test suite.
To my eye, this all looks standard conforming and legal too. However, a
few comments might be in order.
Post by FortranFan
--- begin variant ---
[...]
Post by FortranFan
subroutine pop( stack )
type(stack_t), intent(inout), allocatable :: stack
if ( allocated(stack) ) then > if ( allocated(stack%next) ) then
stack = stack%next
As discussed elsethread, this invokes a deep copy from stack%next, so it
is unnecessarily inefficent for the desired operation. Of course, an
optimizing compiler might recognize this and do the efficient shallow
copy instead, but it is just as clear and simple to write the shallow
copy code directly.
Post by FortranFan
else
deallocate( stack )
end if
end if
return
end subroutine pop
recursive subroutine output( stack, head )
type(stack_t), intent(in), allocatable :: stack
logical, intent(in), optional :: head
character(len=*), parameter :: fmtg = "(t5,g0,t15,g0)"
if ( allocated(stack) ) then
if ( present(head) ) print fmtg, "index", "value"
print fmtg, stack%index, stack%value
call output( stack%next )
This recursive call is also potentially unnecessarily inefficient. It
basically copies and saves addresses of the entire data structure in the
subroutine call stack until it gets to the end, and then prints out the
results one element at a time from that copy. If you do this with a
local pointer as in my original code, then that overhead is avoided.
However, the elements would be printed out in the opposite order, so the
two approaches are not equivalent.

If you actually need the elements printed in the other order, or if you
otherwise need to traverse through the list in both directions, then it
might be better to add the appropriate pointer member to the derived
type, and update that pointer in the push and pop operations. I like to
avoid using pointers, especially nonlocal ones, in fortran code, so this
itself comes with its set of compromises, both aesthetically and
practically.

Another approach would be to allocate a local array of pointers, assign
them from top to bottom, and then use the pointers to print the elements
from bottom to top. This still has the overhead of saving all the
addresses at once, but it avoids the overhead associated with the
recursive call. The code for this should still be relatively clear and
easy to understand.
Post by FortranFan
else if ( present(head) ) then
print *, "Empty stack"
end if
return
[...]

$.02 -Ron Shepard
g***@u.washington.edu
2020-03-17 20:24:45 UTC
Permalink
On Tuesday, March 17, 2020 at 11:52:24 AM UTC-7, Ron Shepard wrote:

(snip)
Post by Ron Shepard
As discussed elsethread, this invokes a deep copy from stack%next, so it
is unnecessarily inefficent for the desired operation.
Yes, try to avoid that. The whole idea of MOVE_ALLOC is to reduce
copying.
Post by Ron Shepard
Of course, an
optimizing compiler might recognize this and do the efficient shallow
copy instead, but it is just as clear and simple to write the shallow
copy code directly.
I suspect that they don't, but I suppose it is possible.

(snip)
Post by Ron Shepard
This recursive call is also potentially unnecessarily inefficient. It
basically copies and saves addresses of the entire data structure in the
subroutine call stack until it gets to the end, and then prints out the
results one element at a time from that copy. If you do this with a
local pointer as in my original code, then that overhead is avoided.
However, the elements would be printed out in the opposite order, so the
two approaches are not equivalent.
If you don't do it so much, then inefficiency isn't so bad.

If you really need that order, then they have to be saved somewhere,
and the call stack isn't so bad a place. It is, for example,
the traditional way to deallocate a tree, and often enough for a
linked list.

It is also the old favorite for binary to ASCII decimal conversion,
where you need to print the digits in the opposite order that
they are computed in. Though that was usually in assembler, where
it was fairly fast, and not so much overhead on the stack.
Post by Ron Shepard
If you actually need the elements printed in the other order, or if you
otherwise need to traverse through the list in both directions, then it
might be better to add the appropriate pointer member to the derived
type, and update that pointer in the push and pop operations. I like to
avoid using pointers, especially nonlocal ones, in fortran code, so this
itself comes with its set of compromises, both aesthetically and
practically.
Doubly linked list. A little less common that ordinary linked list,
but not so uncommon.
Post by Ron Shepard
Another approach would be to allocate a local array of pointers, assign
them from top to bottom, and then use the pointers to print the elements
from bottom to top. This still has the overhead of saving all the
addresses at once, but it avoids the overhead associated with the
recursive call. The code for this should still be relatively clear and
easy to understand.
If you know the length. Otherwise, you need a loop to count them,
which is extra overhead, though not all that much. It is hard for us
to know, and maybe the OP doesn't even know, but it might be a small
fraction of the time, and so not worth extra work to speed up.

If the length is 10s or 100s, probably not. If it is millions, then
it might be.
Ron Shepard
2020-03-17 22:17:00 UTC
Permalink
Post by g***@u.washington.edu
(snip)
Post by Ron Shepard
As discussed elsethread, this invokes a deep copy from stack%next, so it
is unnecessarily inefficent for the desired operation.
Yes, try to avoid that. The whole idea of MOVE_ALLOC is to reduce
copying.
I think MOVE_ALLOC was a really nice feature to add to the language.
However, it seems more limited than necessary. In this thread it has
already been mentioned that

call move_alloc( from=stack%previous, to=stack )

would be a nice thing to allow. As far as I can tell, that statement is
either undefined or illegal, depending on whether the two arguments are
the same "entity" in the standardese.

Going a little further, it would be nice to allow conversions between
allocatable and pointer entities, and MOVE_ALLOC() would seem to be a
good start, if not entirely sufficient, along those lines.

call_move_alloc( from=ptr, to=allocatable_entity )
call move_alloc( from=allocatable_entity, to=ptr )

Just think of all the sidestepping, pointer assignments, and copying
that is required now to do that same thing.

[...]
Post by g***@u.washington.edu
Post by Ron Shepard
This recursive call is also potentially unnecessarily inefficient. It
basically copies and saves addresses of the entire data structure in the
subroutine call stack until it gets to the end, and then prints out the
results one element at a time from that copy. If you do this with a
local pointer as in my original code, then that overhead is avoided.
However, the elements would be printed out in the opposite order, so the
two approaches are not equivalent.
If you don't do it so much, then inefficiency isn't so bad.
There might be other reasons to avoid the recursion too, besides
aesthetics and efficiency. For example, a machine might set aside only a
limited amount of space for stack frames.
Post by g***@u.washington.edu
If you really need that order, then they have to be saved somewhere,
and the call stack isn't so bad a place. It is, for example,
the traditional way to deallocate a tree, and often enough for a
linked list.
Yes, I agree. In fact, if you just say

DEALLOCATE( stack )

that is probably the way it is implemented, bottom up using the call
stack. But with with MOVE_ALLOC, you can deallocate the linked list from
the top down, without all of that overhead, and it only takes a loop
with a couple of statements, so it is still simple and clear code.

[...]
Post by g***@u.washington.edu
Post by Ron Shepard
Another approach would be to allocate a local array of pointers, assign
them from top to bottom, and then use the pointers to print the elements
from bottom to top. This still has the overhead of saving all the
addresses at once, but it avoids the overhead associated with the
recursive call. The code for this should still be relatively clear and
easy to understand.
If you know the length. Otherwise, you need a loop to count them,
which is extra overhead, though not all that much.
Yes, this is right. In my original post, I specified that you knew the
length, but I did not say how. In some of the subsequent posts, the
stack level was included as a member of the derived type, so the length
of the linked list was always available as part of the data structure
itself.


[...]
Post by g***@u.washington.edu
If the length is 10s or 100s, probably not. If it is millions, then
it might be.
These days with 64-bit addressing and TB memories, a list could be
billions of levels.

$.02 -Ron Shepard
g***@u.washington.edu
2020-03-17 23:35:10 UTC
Permalink
On Tuesday, March 17, 2020 at 3:17:03 PM UTC-7, Ron Shepard wrote:

(snip)
Post by Ron Shepard
I think MOVE_ALLOC was a really nice feature to add to the language.
However, it seems more limited than necessary. In this thread it has
already been mentioned that
call move_alloc( from=stack%previous, to=stack )
would be a nice thing to allow. As far as I can tell, that statement is
either undefined or illegal, depending on whether the two arguments are
the same "entity" in the standardese.
It specifically says that the "to" is deallocated first.
It seems to me, that is independent of any "entity" question.
Once you deallocate something, you can't use it anywhere.

So, it takes two MOVE_ALLOC calls, as previously shown.
I suppose it could have been defined in terms of the two moves.
Ron Shepard
2020-03-18 06:41:14 UTC
Permalink
Post by g***@u.washington.edu
(snip)
Post by Ron Shepard
I think MOVE_ALLOC was a really nice feature to add to the language.
However, it seems more limited than necessary. In this thread it has
already been mentioned that
call move_alloc( from=stack%previous, to=stack )
would be a nice thing to allow. As far as I can tell, that statement is
either undefined or illegal, depending on whether the two arguments are
the same "entity" in the standardese.
It specifically says that the "to" is deallocated first.
It seems to me, that is independent of any "entity" question.
Once you deallocate something, you can't use it anywhere.
If the two arguments are the same entity, then the operation is defined
by the standard to be equivalent to deallocation. So in that case, I
guess it would have a defined outcome, just not the one you want.
Post by g***@u.washington.edu
So, it takes two MOVE_ALLOC calls, as previously shown.
I suppose it could have been defined in terms of the two moves.
One wonders why it was defined that way in the standard in the first
place. I'm just guessing, but regular assignment of an allocatable
entity is defined that way too (conditional deallocation of the entity
as the first step), so maybe they were just copying those high-level
semantics, but applying it to the shallow copy situation rather than the
deep copy one. In any case, the two separate MOVE_ALLOC calls are as
efficient as possible for this operation, so nothing significant can
really be gained in that respect by changing the standard definition. It
is only a cosmetic, aesthetic issue.

$.02 -Ron Shepard
FortranFan
2020-03-18 02:45:59 UTC
Permalink
Post by Ron Shepard
[...]
Post by FortranFan
I believe the code variant below of Paul's example upthread is standard-conforming and it runs fine with another compiler I tried. However it encounters a run-time error with gfortran. This case might be of interest toward the gfortran test suite.
To my eye, this all looks standard conforming and legal too. However, a
few comments might be in order. ..
Given what I had in mind, both the comments are of little interest:

* First, the example variant I provided pertains to the feature introduced in Fortran 2008 with "allocatable components of recursive type" and the purpose is to provide something I felt was standard-conforming but failed with gfortran. My thought is as I wrote upthread that that "case might be of interest toward the gfortran test suite". Any notion of perceived inefficiency or otherwise with my example is totally besides the point.

* So the assignment instruction "stack%previous = stack" is something I expect a standard-conforming compiler to process alright in the current situation with this recursive derived type with the allocatable component. And it is something with which gfortran appears to have difficulty. The assignment instruction is essential to show the problem and that's why I included it to show to Paul and other GCC volunteers.

* Though I can't expect all the processor implementations to notice, the case I showed can be of interest to those who do i.e., to check how their implementations might handle the case. I think it to be reasonable for Fortranners to expect the processors to perform the equivalent of 'call move_alloc( stack%previous, temp ) ; call move_alloc( temp, stack )' when they see 'stack%previous = stack'.

* In the case of employing recursion to scan the "recursive derived type with allocatable component" as I show above with 'recursive subroutine output(..' I really do NOT think the point about "This recursive call is also potentially unnecessarily inefficient" is of concern here. Modern compilers are good at handling recursive procedures and this fact is among the reasons why Fortran 2018 has made 'recursive' the default attribute of procedures. Besides, a stack container is about the only use case for this Fortran 2008 feature and it makes little sense to have a stack container with so many elements the processor's stack limits are exhausted.
p***@gmail.com
2020-03-19 19:00:13 UTC
Permalink
Post by FortranFan
..
You might find 'recursive_alloc_comp_[1,2].f08' interesting as well. 'recursive_alloc_comp_4.f08' tests allocatable array components of the parent type and demonstrates that these are tricky to use, to say the least of it :-) ..
Hi Paul and any gfortran volunteers,
I believe the code variant below of Paul's example upthread is standard-conforming and it runs fine with another compiler I tried. However it encounters a run-time error with gfortran. This case might be of interest toward the gfortran test suite.
--- begin variant ---
module m
type :: stack_t
integer :: value = -1
integer :: index = 1
type(stack_t), allocatable :: next
end type stack_t
contains
subroutine poke( stack, value )
type(stack_t), intent(inout), allocatable :: stack
integer, intent(in) :: value
type(stack_t), allocatable :: item
allocate( item )
item%value = value
if ( allocated(stack) ) then
item%index = stack%index + 1
call move_alloc(stack, item%next)
end if
call move_alloc(item, stack)
return
end subroutine poke
subroutine pop( stack )
type(stack_t), intent(inout), allocatable :: stack
if ( allocated(stack) ) then
if ( allocated(stack%next) ) then
stack = stack%next
else
deallocate( stack )
end if
end if
return
end subroutine pop
recursive subroutine output( stack, head )
type(stack_t), intent(in), allocatable :: stack
logical, intent(in), optional :: head
character(len=*), parameter :: fmtg = "(t5,g0,t15,g0)"
if ( allocated(stack) ) then
if ( present(head) ) print fmtg, "index", "value"
print fmtg, stack%index, stack%value
call output( stack%next )
else if ( present(head) ) then
print *, "Empty stack"
end if
return
end subroutine output
end module
use m
type (stack_t), allocatable :: stack
call pop( stack )
call output( stack, head=.true. )
call poke( stack, value=1 )
call poke( stack, value=2 )
call poke( stack, value=3 )
call output( stack, head=.true. )
call pop( stack )
call output( stack, head=.true. )
call pop( stack )
call output( stack, head=.true. )
call pop( stack )
call pop( stack )
call output( stack, head=.true. )
call poke( stack, value=1 )
call output( stack, head=.true. )
end
--- end variant ---
C:\Temp>x86_64-w64-mingw32-gfortran.exe -Wall -std=f2018 -fcheck=all p.f90 -o p.exe
C:\Temp>p.exe
Empty stack
index value
3 3
2 2
1 1
At line 67 of file p.f90
Fortran runtime error: Recursive call to nonrecursive procedure '__deallocate_m_Stack_t'
Could not print backtrace: libbacktrace could not find executable to open
#0 0xffffffff
#1 0xffffffff
#2 0xffffffff
#3 0xffffffff
#4 0xffffffff
#5 0xffffffff
#6 0xffffffff
#7 0xffffffff
#8 0xffffffff
#9 0xffffffff
#10 0xffffffff
#11 0xffffffff
#12 0xffffffff
C:\Temp>
I just came back from a break at Centerparcs - a model of social distancing because nearly everybody cleared off the day after we arrived and we more or less had the place to ourselves :-) It closes for a month, though, starting tomorrow.

From a quick look, there appears to be two issues with this testcase. I will turn them into PRs tomorrow. The reallocation on assignment process simply did not encompass the possibility of "recursive allocatable" components. I think that the fix is relatively trivial although the polymorphic version migh well be "interesting".

Thanks

Paul
Ron Shepard
2020-03-19 20:01:33 UTC
Permalink
Post by p***@gmail.com
From a quick look, there appears to be two issues with this testcase. I will turn them into PRs tomorrow. The reallocation on assignment process simply did not encompass the possibility of "recursive allocatable" components. I think that the fix is relatively trivial although the polymorphic version migh well be "interesting".
This statement was not included in the test example, but does

deallocate( stack )

work correctly for "recursive allocatable" components?

$.02 -Ron Shepard
p***@rspcaoxford.org.uk
2020-04-06 13:30:12 UTC
Permalink
Post by Ron Shepard
Post by p***@gmail.com
From a quick look, there appears to be two issues with this testcase. I will turn them into PRs tomorrow. The reallocation on assignment process simply did not encompass the possibility of "recursive allocatable" components. I think that the fix is relatively trivial although the polymorphic version migh well be "interesting".
This statement was not included in the test example, but does
deallocate( stack )
work correctly for "recursive allocatable" components?
$.02 -Ron Shepard
One effect of the lock down is that I am spending an enormous amount of time attending remote meetings and by the evening I lose any interest in pursuing fortran matters or looking at clf. So I apologise for only just getting to your question.

The answer is yes it does. dallocate(stack) with gfortran from version 7.4.1 through 10.0.0 produces this code:

if (stack == 0B)
{
_gfortran_runtime_error_at (&"At line 96 of file fortranfan.f90"[1]{lb: 1 sz: 1}, &"Attempt to DEALLOCATE unallocated \'%s\'"[1]{lb: 1 sz: 1}, &"stack"[1]{lb: 1 sz: 1});
}
else
{
{
struct array1_unknown cdesc.21;

if (stack->next != 0B)
{
cdesc.21.dtype = 601;
cdesc.21.dim[0].lbound = 1;
cdesc.21.dim[0].stride = 1;
cdesc.21.dim[0].ubound = 1;
cdesc.21.data = (void *) stack->next;
__vtab_m_Stack_t._deallocate (&cdesc.21);
}
__builtin_free ((void *) stack);
}
}
stack = 0B;

with

__deallocate_m_Stack_t (struct array1_stack_t & restrict arg)
{
{
integer(kind=8) D.3557;
integer(kind=8) D.3558;
integer(kind=8) D.3559;
integer(kind=8) S.0;
struct array1_unknown cdesc.1;

if ((struct stack_t[0:] * restrict) arg->data != 0B)
{
D.3557 = (arg->dim[0].ubound - arg->dim[0].lbound) + 1;
D.3558 = arg->dim[0].stride * D.3557;
D.3559 = D.3558 + -1;
S.0 = 0;
while (1)
{
if (S.0 > D.3559) goto L.1;
if ((*(struct stack_t[0:] * restrict) arg->data)[S.0].next != 0B)
{
cdesc.1.dtype = 601;
cdesc.1.dim[0].lbound = 1;
cdesc.1.dim[0].stride = 1;
cdesc.1.dim[0].ubound = 1;
cdesc.1.data = (void *) (*(struct stack_t[0:] * restrict) arg->data)[S.0].next;
__vtab_m_Stack_t._deallocate (&cdesc.1);
}
S.0 = S.0 + 1;
}
L.1:;
}
if ((struct stack_t[0:] * restrict) arg->data == 0B)
{
_gfortran_runtime_error_at (&"At line 69 of file fortranfan.f90"[1]{lb: 1 sz: 1}, &"Attempt to DEALLOCATE unallocated \'%s\'"[1]{lb: 1 sz: 1}, &"arg"[1]{lb: 1 sz: 1});
}
else
{
__builtin_free ((void *) arg->data);
(struct stack_t[0:] * restrict) arg->data = 0B;
}
}
}

So the deallocator function __vtab_m_Stack_t._deallocate is called recursively down to the end of the stack and deallocates the 'next' component before deallocating the element itself. This can be checked with valgrind.

This works with more complicated structures, such as binary trees.

Paul
--
RSPCA Oxfordshire Branch (charity number 205156)



www.rspca.org.uk/local/oxfordshire-branch
<https://www.rspca.org.uk/local/oxfordshire-branch>


Our vision is a
caring world where all animals are respected and treated with compassion



This email and any files transmitted with it are confidential and intended
solely for the use of the individual or entity to whom they are addressed.
If you have received this email in error please delete it from your system.
This email message has been swept by virus checking software for the
presence of computer viruses but no warranty is given that this email is
virus free. You should make your own checks. Please note the RSPCA is not
responsible or liable for the content of an email where it is used for
personal purposes.
Ron Shepard
2020-04-06 15:30:17 UTC
Permalink
Post by p***@rspcaoxford.org.uk
Post by Ron Shepard
Post by p***@gmail.com
From a quick look, there appears to be two issues with this testcase. I will turn them into PRs tomorrow. The reallocation on assignment process simply did not encompass the possibility of "recursive allocatable" components. I think that the fix is relatively trivial although the polymorphic version migh well be "interesting".
This statement was not included in the test example, but does
deallocate( stack )
work correctly for "recursive allocatable" components?
[...]
Post by p***@rspcaoxford.org.uk
So the deallocator function __vtab_m_Stack_t._deallocate is called recursively down to the end of the stack and deallocates the 'next' component before deallocating the element itself. This can be checked with valgrind.
This works with more complicated structures, such as binary trees.
Thanks for the detailed answer. This is good to know, and I can see why
it was implemented that way to cover the general case. A simple linked
list can be deallocated also from the top down, with no stack storage,
so if storage efficiency is critical, it looks like the programmer
should write that code manually rather than use the simple
deallocate(stack) statement.

As a practical matter, do you know what are the stack storage limits,
hard and soft, for various operating systems, and have those limits
changed with the different versions of gfortran?

$.02 -Ron Shepard
Steve Lionel
2020-04-07 15:29:10 UTC
Permalink
Post by Ron Shepard
As a practical matter, do you know what are the stack storage limits,
hard and soft, for various operating systems
On Linux, the hard limit is set when the kernel was built, and this
varies. The use of the keyword "unlimited" when changing the stack size
means this hard limit. Otherwise the soft limit will vary a lot based on
distro, version, etc.

On Windows, the stack comes from the same 2GB as static code and data,
and is a property of the EXE set at link time. The default I think is
10MB. If you set this too high, that doesn't leave enough room for other
things competing for that 2GB and your EXE may not run. Note also that
since it is an EXE property, nothing you do while building a DLL will
affect it.

Compilers have no effect on stack limits. Pretty much any compiler you
are likely to use will have an option (and may be the default) to use
the heap for temporaries that would otherwise go on the stack. IIRC,
gfortran has this on by default; ifort does not.
--
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: http://intel.com/software/DrFortran
WG5: https://wg5-fortran.org
g***@u.washington.edu
2020-04-07 18:40:24 UTC
Permalink
Post by Steve Lionel
Post by Ron Shepard
As a practical matter, do you know what are the stack storage limits,
hard and soft, for various operating systems
(snip)
Post by Steve Lionel
On Windows, the stack comes from the same 2GB as static code and data,
and is a property of the EXE set at link time. The default I think is
10MB. If you set this too high, that doesn't leave enough room for other
things competing for that 2GB and your EXE may not run. Note also that
since it is an EXE property, nothing you do while building a DLL will
affect it.
In the 16 bit DOS days, there was a program to change the stack value
in an EXE file, without relinking. (Especially if you don't have the
files to link.) I don't know if there is still one of those.

The default for way too long was 4K. I remember running into this
in the early OS/2 days, with stack in virtual memory. I could make
it really huge (more than the virtual memory that I had) and wondered
why it was only 4K.
Post by Steve Lionel
Compilers have no effect on stack limits. Pretty much any compiler you
are likely to use will have an option (and may be the default) to use
the heap for temporaries that would otherwise go on the stack. IIRC,
gfortran has this on by default; ifort does not.
I think I remember some system that writes the stack needs into the
object modules, and the linker then adds them up. I don't remember now
which one, so maybe I am imagining it.

Loading...