!#################### Thoughts from link on pointer layout #####################
Thanks for providing the link discussing the pointer descriptors in Fortran. Whenever I tried to look up info like this, I mostly just found forum posts with people asking about how pointers work in Fortran and no real information about what's under the hood so I resorted to debugging probes and changing values to glean what the representation was (I demonstrate an example towards the end of this message).
Looking at the content in that link, it does clarify some things I was uncertain of (for example, the "0x501" value I kept seeing apparently indicates it's a rank-1 derived type), and why I seemingly saw a duplication of "sizeof(myType)" (stored in +0x10 as the size of myType, stored "again" in 0x20 as the stride for the first dimension).
Although the link details the pointer descriptor layout, it mentions the layout changes between standards and since I've also observed differing pointer sizes/layouts in my debugging, I'm not currently 100% sure what the 0x20 offset invloved in the seg fault corresponds to - I'll have to look in more detail later. For now, I think it's the stride or, at the very least, is a field involved in indexing, and I'm not sure it's actual meaning would change the discussion that follows.
With this interpretation, I thought about what the extra copying of the "0x20" field might be doing and thought it was weird that it would need to get some value from the RHS after the LHS was just given a copy of that info. That is, it seems that redoing the same index/offset calculation after copying the RHS descriptor is needless/redundant.
I then considered that my demo is probably too simple to demonstrate the usecase for which these instructions become necessary (since my demo has the same shape/bounds on the LHS and RHS) so I figured there must be a more complicated scenario it's trying to accommodate.
My first thought was that a reshaping of the layout between the LHS and RHS might require the RHS descriptor to perform the reshaping after the descriptor had been copied over. After confirming with the standard that reshaping is allowed in a pointer-assignment-stmt, I turned to my demo program to test this and inspect the disassembly.
I provide the results of this investigation below, but first I'll revisit my uncertainty in whether or not "child => child(1)%pChild" is an ill-formed statement (i.e., non-standard conforming and open to seg faults).
!##################### Reconsidering the original question #####################
Returning to the original statement, "child => child(1)%pChild", I see it as nothing more than simply "ptr1 => ptr2". How we arrive at "ptr1" and "ptr2" seems inconsequential to me; at least, according to my reading of the standard since I don't see any rules/clauses that strict a statement of the form "child => child(1)%pChild". All that needs to happen is we find the location of descriptor "ptr1", the location of descriptor "ptr2", and copy the descriptor of "ptr2" into "ptr1". That is, the contents of descriptor "ptr1" should not affect the value obtained in evaluating "ptr2" throughout the execution of the statement.
My reasoning for this interpretation comes from the following snippets of the standard (again, I'm reading
https://wg5-fortran.org/f2008.html but maybe I should be reading a different document such as the one FortranFan links to).
I haven't seen anything directly regarding how to resolve the LHS and RHS of a pointer-assignment-stmt, so I'll construct the rule by combinining various rules/clauses.
To start, I'll note the comments on the similar scenario of an (intrinsic) assignment-stmt:
* 7.2.1.3 Interpretation of intrinsic assignments
** 4 Both variable and expr may contain references to any portion of the variable
*** NOTE 7.37
*** For example, in the character intrinsic assignment statement:
*** STRING (2:5) = STRING (1:4)
*** the assignment of the first character of STRING to the second character does not affect the evaluation of
*** STRING (1:4). If the value of STRING prior to the assignment was ’ABCDEF’, the value following the
*** assignment is ’AABCDF’.
Again, I realize this is referring to an assignment-stmt, not a pointer-assignment-stmt, but pulling from other sections, I find this rule for pointer-assignment-stmt's by construction:
* 7.2.2 Pointer assignment
** R733 pointer-assignment-stmt is data-pointer-object [ (bounds-spec-list) ] => data-target
** R737 data-target is variable
** R602 variable is designator or expr
* 7.1.2 Form of an expression
** R701 primary [expr] is ... or designator ... or ( expr )
Thus, I read that the RHS can be considered an expression.
Reading the rules on expressions:
* 7 Expressions and assignment
** 7.1 Expressions
*** 1 An expression represents either a data reference... An expression is formed from operands, operators, and parentheses.
*** 3 Evaluation of an expression produces a value, which has a type, type parameters (if appropriate), and a shape (7.1.9)...
So, it would seem to me that the RHS is an expression which is used as the operand of the "=>" operator, so it needs to be evaluated and that value is used to execute the pointer-assignment-stmt. That is, it seems the standard requires the RHS of a pointer-assignment-stmt to be evaluated at the start of the statement, and it should retain this value throughout the statement (this mirrors the above note on assignment-stmt).
There is a possible gap in this reasoning due to the fact that I don't see anywhere that the symbol "=>" is actually called an "operator" (I don't see "=" referred to as an operator either), but turning to the similar section on assignment-stmt (the closest I could find), I find:
* 7.2.1 Assignment statement
** R732 assignment-stmt is variable = expr
* 7.2.1.3 Interpretation of intrinsic assignments
** 1 Execution of an intrinsic assignment causes, in effect, the evaluation of the expression expr and all expressions within variable (7.1)
This simplistic scenario of "child => child(1)%pChild" does change slightly when we move to a more complicated pointer-assignment-stmt, such as reshaping during the pointer-assignment-stmt; e.g.:
L20: 20 dummy_child(1:1,1:1) => child(1:2)
because, here, we know that we cannot simply copy the descriptor from the RHS to the LHS without doing some processing to define the remapping of the layout of the data. But, as I read the standard, this shouldn't be a problem because the RHS should retain its value throughout execution of the statement.
With that in mind, I move on to inspecting the disassembly of a pointer-assignment-stmt that involves reshaping:
!############## Exploring reshaping in a pointer-assignment-stmt ###############
I modified my demo program to look like the following:
1 program main
2 implicit none
3
4 type myType
5 type(myType), pointer, dimension(:) :: pChild => null()
6 end type myType
7
8 type(myType), pointer, dimension(:) :: root, child
9 type(myType), pointer, dimension(:,:) :: dummy_child
10
11 nullify(root)
12 nullify(child)
13 nullify(dummy_child)
14 call allocateMyType(root)
15
16 call allocateMyType(root(1)%pChild)
17 call allocateMyType(root(1)%pChild(1)%pChild)
18
19 child => root(1)%pChild
20 dummy_child(1:1,1:1) => child(1:2)
! ... the rest omitted as I just wanted to see the disassembly of L20
and the (notable) disassembly is:
; ...
L11 nullify(root)
L11+00 movq $0x0,-0xf0(%rbp)
; ...
L12 nullify(child)
L12+00 movq $0x0,-0x50(%rbp)
; ...
L13 nullify(dummy_child)
L13+00 movq $0x0,-0xb0(%rbp)
; ...
20 dummy_child(1:1,1:1) => child(1:2)
; ... L20+0 through L20+47 appear to be bounds check lines
L20+48 mov -0x30(%rbp),%rax
L20+49 mov %rax,-0x2f0(%rbp)
L20+50 movq $0x0,-0x300(%rbp)
L20+51 movq $0x0,-0x2f8(%rbp)
L20+52 movq $0x40,-0x300(%rbp)
L20+53 movb $0x1,-0x2f4(%rbp)
L20+54 movb $0x5,-0x2f3(%rbp)
L20+55 mov -0x28(%rbp),%rax
L20+56 movq $0x1,-0x2e0(%rbp)
L20+57 movq $0x2,-0x2d8(%rbp)
L20+58 mov %rax,-0x2e8(%rbp)
L20+59 mov -0x50(%rbp),%rdx
L20+60 mov -0x20(%rbp),%rcx
L20+61 mov $0x1,%esi
L20+62 sub %rcx,%rsi
L20+63 mov %rsi,%rcx
L20+64 imul %rax,%rcx
L20+65 shl $0x6,%rcx
L20+66 add %rcx,%rdx
L20+67 mov %rdx,-0x310(%rbp)
L20+68 neg %rax
L20+69 mov %rax,-0x308(%rbp)
L20+70 movq $0x0,-0xa0(%rbp)
L20+71 movq $0x0,-0x98(%rbp)
L20+72 movq $0x40,-0xa0(%rbp)
L20+73 movb $0x2,-0x94(%rbp)
L20+74 movb $0x5,-0x93(%rbp)
L20+75 mov -0x310(%rbp),%rax
L20+76 mov %rax,-0xb0(%rbp)
L20+77 movq $0x40,-0x90(%rbp)
L20+78 mov -0x308(%rbp),%rdx
L20+79 mov -0x2e8(%rbp),%rcx
L20+80 mov -0x2e0(%rbp),%rax
L20+81 imul %rcx,%rax
L20+82 add %rdx,%rax
L20+83 mov %rax,-0xa8(%rbp)
L20+84 movq $0x1,-0x80(%rbp)
L20+85 movq $0x1,-0x78(%rbp)
L20+86 mov -0x2e8(%rbp),%rax
L20+87 mov %rax,-0x88(%rbp)
L20+88 mov -0xa8(%rbp),%rdx
L20+89 sub %rax,%rdx
L20+90 mov %rdx,-0xa8(%rbp)
L20+91 movq $0x1,-0x68(%rbp)
L20+92 movq $0x1,-0x60(%rbp)
L20+93 mov %rax,-0x70(%rbp)
L20+94 mov -0xa8(%rbp),%rdx
L20+95 sub %rax,%rdx
L20+96 mov %rdx,%rax
L20+97 mov %rax,-0xa8(%rbp)
; ... L20+98 through L20+129 also appear to be bounds check lines
; ... This is probably due to compiling with -fbounds-check and these
; ... checks are probably validating the reshaping, but I'd have to
; ... play around with this more to be more certain
I haven't had a lot of time to pour through this more carfully, so I could be misreading things, but I have some interpretation of it at a glance based on the rest of the debugging I've done.
To start I note that the descriptor of "dummy_child" is stored at (%rbp-0xb0) and the descriptor of "child" is at (%rbp-0x50). It's important to keep these addresses in mind, and note the stack starts at some offset before %rbp and grows toward %rbp (notice that "root" is stored at (%rbp-0xf0), preceding "child" and "dummy_child").
If we look at lines L20+48, L20+55, ..., we see that that they are taking values from the "child" descriptor (RHS), which seems to occupy (%rbp-0x50) to (%rbp-0x10).
If we look at L20+52 (movq sizeof(myType)), L20+53:54 (copying the 0x501 I've noted previously), etc., it seems what is happening is a copy of the descriptor data necessary to define a pointer descriptor. This claim that it's constructing "the descriptor data necessary to define a pointer descriptor" isn't conceptually much different than what we saw in the original disassembly (in both cases, we just need to define the descriptor of the LHS), but there is clearly a difference here.
First, notice the addresses used in the first portion of the instructions: L20+48 through L20+69
-- The source values (the first argument of the mov instructions) all come from either literal constants or values read in from the "child" descriptor.
-- The destination for these values is an address range we haven't seen before, starting at (%rbp-0x310) (I think). I believe this is a portion of the stack set aside to keep temporary results, but I could be mistaken.
Next, notice the addresses used in the second portion of the instructions: L20+70 through L20+98
-- Now, the source values are either constant literals or the values that have been placed/manipulated in the temp value address space ~(%rbp-0x310)
-- And the destination is the addresses of the "dummy_child" descriptor
Looking at this disassembly, which should be the more safe version since the LHS and RHS _don't_ use the same pointer object in their expressions, we see that it moves the data from the RHS descriptor into a junk address space, makes the required modifications to morph it into the form that will be needed on the LHS, then moves the values over once those operations are done. Furthermore, once the data starts being copied into the "dummy_child" descriptor, the RHS seemingly never gets accessed again (which was the exact problem causing the seg fault in my original post).
Recalling my interpretation of the standard and my quick analysis of this disassembly, it seems to me that "child => child(1)%pChild" should be a perfectly valid statement, and the extra lines which caused the seg fault either shouldn't be there, or the copying of the data should be done into an auxilary address space until the RHS no longer needs to be accessed, thus preventing corruption during execution of the pointer-assignment-stmt.
I definitely could be overlooking/misinterpreting something though (especially with this new analysis) so I'd love to hear other people's thoughts on the matter.
!########################## Details Referenced Above: ##########################
!################# How I originally determined pointer layout ##################
To determine the addresses that pointers were stored at, I'd write some variables/nullifies as follows and inspect the addresses used in the disasembly (I did something similar to locate their offset from a parent object when they appeared as a component in a derived type):
1 program main
! ...
9 type(myType), pointer, dimension(:) :: root, child, dummy_child
10
11 nullify(root)
11 nullify(child)
11 nullify(dummy_child)
result in the following disassemblies (the definition of myType can be modified but these address seem to remain the same):
!####################### (NON-seg fault) gfortran 6.3.1 ########################
9 type(myType), pointer, dimension(:) :: root, child, dummy_child
11 nullify(root)
L11+0 mov 0x0,-0x90(%rbp) ; Start address of "root" descriptor in gfortran 6.3.1
12 nullify(child)
L12+0 mov 0x0,-0x30(%rbp) ; Start address of "child" descriptor in gfortran 6.3.1
13 nullify(dummy_child)
L13+0 mov 0x0,-0x60(%rbp) ; Start address of "dummy_child" descriptor in gfortran 6.3.1
!######################### (SEG fault) gfortran 9.3.1 ##########################
9 type(myType), pointer, dimension(:) :: root, child, dummy_child
11 nullify(root)
L11+0 mov 0x0,-0xd0(%rbp) ; Start address of "root" descriptor in gfortran 9.3.1
12 nullify(child)
L12+0 mov 0x0,-0x50(%rbp) ; Start address of "child" descriptor in gfortran 9.3.1
13 nullify(dummy_child)
L13+0 mov 0x0,-0x90(%rbp) ; Start address of "dummy_child" descriptor in gfortran 9.3.1
I did similar things to determine things like pointer size, etc. Additionally, it was through probes such as this I came to the conclusion that the message "...stack allocation..." was probably genuine.
Here's the full disassembly of the reshape-on-target statement at L20:
!####################### (newer compiler) gfortran 9.3.1 #######################
20 dummy_child(1:1,1:1) => child(1:2)
L20+0 mov -0x20(%rbp),%rax
L20+1 cmp $0x1,%rax
L20+2 jle 0x401800 <MAIN__+1082>
L20+3 mov -0x18(%rbp),%rdx
L20+4 mov -0x20(%rbp),%rax
L20+5 mov %rdx,%r8
L20+6 mov %rax,%rcx
L20+7 mov $0x1,%edx
L20+8 mov $0x403288,%esi
L20+9 mov $0x4032d8,%edi
L20+10 mov $0x0,%eax
L20+11 callq 0x401030 <_gfortran_runtime_error_at<at>plt>
L20+12 mov -0x18(%rbp),%rax
L20+13 test %rax,%rax
L20+14 jg 0x401830 <MAIN__+1130>
L20+15 mov -0x18(%rbp),%rdx
L20+16 mov -0x20(%rbp),%rax
L20+17 mov %rdx,%r8
L20+18 mov %rax,%rcx
L20+19 mov $0x1,%edx
L20+20 mov $0x403288,%esi
L20+21 mov $0x4032d8,%edi
L20+22 mov $0x0,%eax
L20+23 callq 0x401030 <_gfortran_runtime_error_at<at>plt>
L20+24 mov -0x20(%rbp),%rax
L20+25 cmp $0x2,%rax
L20+26 jle 0x401861 <MAIN__+1179>
L20+27 mov -0x20(%rbp),%rdx
L20+28 mov -0x18(%rbp),%rax
L20+29 mov %rdx,%r8
L20+30 mov %rax,%rcx
L20+31 mov $0x2,%edx
L20+32 mov $0x403288,%esi
L20+33 mov $0x4032d8,%edi
L20+34 mov $0x0,%eax
L20+35 callq 0x401030 <_gfortran_runtime_error_at<at>plt>
L20+36 mov -0x18(%rbp),%rax
L20+37 cmp $0x1,%rax
L20+38 jg 0x401892 <MAIN__+1228>
L20+39 mov -0x20(%rbp),%rdx
L20+40 mov -0x18(%rbp),%rax
L20+41 mov %rdx,%r8
L20+42 mov %rax,%rcx
L20+43 mov $0x2,%edx
L20+44 mov $0x403288,%esi
L20+45 mov $0x4032d8,%edi
L20+46 mov $0x0,%eax
L20+47 callq 0x401030 <_gfortran_runtime_error_at<at>plt>
L20+48 mov -0x30(%rbp),%rax
L20+49 mov %rax,-0x2f0(%rbp)
L20+50 movq $0x0,-0x300(%rbp)
L20+51 movq $0x0,-0x2f8(%rbp)
L20+52 movq $0x40,-0x300(%rbp)
L20+53 movb $0x1,-0x2f4(%rbp)
L20+54 movb $0x5,-0x2f3(%rbp)
L20+55 mov -0x28(%rbp),%rax
L20+56 movq $0x1,-0x2e0(%rbp)
L20+57 movq $0x2,-0x2d8(%rbp)
L20+58 mov %rax,-0x2e8(%rbp)
L20+59 mov -0x50(%rbp),%rdx
L20+60 mov -0x20(%rbp),%rcx
L20+61 mov $0x1,%esi
L20+62 sub %rcx,%rsi
L20+63 mov %rsi,%rcx
L20+64 imul %rax,%rcx
L20+65 shl $0x6,%rcx
L20+66 add %rcx,%rdx
L20+67 mov %rdx,-0x310(%rbp)
L20+68 neg %rax
L20+69 mov %rax,-0x308(%rbp)
L20+70 movq $0x0,-0xa0(%rbp)
L20+71 movq $0x0,-0x98(%rbp)
L20+72 movq $0x40,-0xa0(%rbp)
L20+73 movb $0x2,-0x94(%rbp)
L20+74 movb $0x5,-0x93(%rbp)
L20+75 mov -0x310(%rbp),%rax
L20+76 mov %rax,-0xb0(%rbp)
L20+77 movq $0x40,-0x90(%rbp)
L20+78 mov -0x308(%rbp),%rdx
L20+79 mov -0x2e8(%rbp),%rcx
L20+80 mov -0x2e0(%rbp),%rax
L20+81 imul %rcx,%rax
L20+82 add %rdx,%rax
L20+83 mov %rax,-0xa8(%rbp)
L20+84 movq $0x1,-0x80(%rbp)
L20+85 movq $0x1,-0x78(%rbp)
L20+86 mov -0x2e8(%rbp),%rax
L20+87 mov %rax,-0x88(%rbp)
L20+88 mov -0xa8(%rbp),%rdx
L20+89 sub %rax,%rdx
L20+90 mov %rdx,-0xa8(%rbp)
L20+91 movq $0x1,-0x68(%rbp)
L20+92 movq $0x1,-0x60(%rbp)
L20+93 mov %rax,-0x70(%rbp)
L20+94 mov -0xa8(%rbp),%rdx
L20+95 sub %rax,%rdx
L20+96 mov %rdx,%rax
L20+97 mov %rax,-0xa8(%rbp)
L20+98 mov -0x78(%rbp),%rdx
L20+99 mov -0x80(%rbp),%rax
L20+100 sub %rax,%rdx
L20+101 mov %rdx,%rax
L20+102 mov $0xffffffffffffffff,%rdx
L20+103 test %rax,%rax
L20+104 cmovs %rdx,%rax
L20+105 lea 0x1(%rax),%rcx
L20+106 mov -0x60(%rbp),%rdx
L20+107 mov -0x68(%rbp),%rax
L20+108 sub %rax,%rdx
L20+109 mov %rdx,%rax
L20+110 mov $0xffffffffffffffff,%rdx
L20+111 test %rax,%rax
L20+112 cmovs %rdx,%rax
L20+113 add $0x1,%rax
L20+114 imul %rcx,%rax
L20+115 mov -0x2d8(%rbp),%rcx
L20+116 mov -0x2e0(%rbp),%rdx
L20+117 sub %rdx,%rcx
L20+118 mov %rcx,%rdx
L20+119 mov $0xffffffffffffffff,%rcx
L20+120 test %rdx,%rdx
L20+121 cmovs %rcx,%rdx
L20+122 add $0x1,%rdx
L20+123 cmp %rax,%rdx
L20+124 jge 0x401a64 <MAIN__+1694>
L20+125 mov %rax,%rcx
L20+126 mov $0x4032f8,%esi
L20+127 mov $0x4032d8,%edi
L20+128 mov $0x0,%eax
L20+129 callq 0x401030 <_gfortran_runtime_error_at<at>plt>