Implementation of a parametrized objective function without using module variables or internal subroutines

Could someone please explain what is an “executable stack”? I did a google search, but did not see anything that looked relevant.

The example given above by @ivanpribec seems like the ideal solution for this kind of problem. When used recursively, each instance of MY_INTEGRATION() should involve also its corresponding instance of MY_F(). It is difficult to imagine how the language could be changed to make this association any easier on the programmer to use or more straightforward to understand. I think this is exactly what was intended when this feature was added to the language (I’m not sure when, maybe f2003?). If gfortran does not handle this correctly, even after a couple of decades, then I can see how that can be frustrating, but there are actually several such features like this that have taken a long time to implement correctly (e.g. parametrized data types). That is the nature of publicly supported open source software.

1 Like

Here is a pretty good explanation as a starting point and links for further information: https://grok.com/share/bGVnYWN5_d52704a6-4ac6-4dc9-a258-7d606f3b9aea.

1 Like

That’s true for simple cases, but while trying to migrate some complex code from ifort to ifx, while ifort gives me

ld: warning: <source>.o: requires executable stack (because the .note.GNU-stack section is executable)

with ifx 2025.2 I get

ld: warning: <source>.o: missing .note.GNU-stack section implies executable stack
ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker

I just haven’t been sufficiently motivated to create a small reproducer to report the issue.

I’ve tried to create a case where the x86-64 assembly would be simple enough to follow:

subroutine doit(f)
external f
call f
end subroutine

integer function timestwo (n)    ! returns 2*n
    implicit none
    integer, intent(in) :: n
    external :: doit
    call doit(multiply_by_2)
contains
   subroutine multiply_by_2()
    timestwo = 2*n
   end subroutine
end function

When compiled using gfortran -Os -fno-inline this produces (Compiler Explorer),

multiply_by_2.0:
        mov     rax, QWORD PTR [r10]        ; load the context address
        mov     eax, DWORD PTR [rax]        ; load the value of n from the context
        add     eax, eax                    ; 2*n = n + n
        mov     DWORD PTR [r10+8], eax      ; store result back in context
        ret
doit_:
        xor     eax, eax                    ; void result
        jmp     rdi                         ; jump to procedure f 
timestwo_:
        sub     rsp, 56                             ; reserve 56 bytes on the stack
        xor     eax, eax                            ;
        mov     edx, OFFSET FLAT:multiply_by_2.0    ; take address (offset) of the internal procedure
        mov     QWORD PTR [rsp], rdi                ; store address of n on the stack
        lea     rdi, [rsp+12]                       ; load (effective) callback address (to be passed to doit)
        mov     QWORD PTR [rsp+40], rax         ; trampoline?
        mov     WORD PTR [rsp+12], -17599       ; trampoline?
        mov     DWORD PTR [rsp+14], edx         ; trampoline?
        mov     WORD PTR [rsp+18], -17847       ; trampoline?
        mov     QWORD PTR [rsp+20], rsp         ; trampoline?
        mov     DWORD PTR [rsp+28], -1864106167 ; trampoline?
        call    doit_                               ; doit takes a single argument passed in rdi
        mov     eax, DWORD PTR [rsp+8]              ; retrieve result from the stack (result is in eax)
        add     rsp, 56                             ; release reserved stack area
        ret

I’ve tried to annotate this to my best understanding. Their is a group of six mov instructions which appear to set up the trampoline in the current stack frame:

[rsp +  0]   8 bytes    address of n
[rsp +  8]   4 bytes    area to return 2*n
[rsp + 12]   2 bytes    magic value: -17599          <-- this address is passed to doit
[rsp + 14]   4 bytes    callback offset
[rsp + 18]   2 bytes    magic value: -17487 
[rsp + 20]   8 bytes    current stack pointer
[rsp + 28]   4 bytes    magic value: -1864106167
[rsp + 40]   8 bytes    zero (?)

I think you can see the trampoline here, because instead of passing the address of multiply_by_2 directly, the address of the trampoline is passed instead. What I’m missing is at some point there should be an instruction like lea r10, [rsp] to connect the nested procedure to the context (lea stands for load effective address), which is then used by the internal procedure.

Anyways, the reason it would be called an executable stack is because the data stored in the current stack frame between [rsp + 12] and [rsp + 28] is treated as code (i.e. instructions).

Disclaimer: perhaps this is not the trampoline, but just a struct describing a nested function. I would appreciate if anyone can double check this.

2 Likes

What I’m missing is at some point there should be an instruction like lea r10, [rsp] to connect the nested procedure to the context

That’s on the line:

mov     WORD PTR [rsp+18], -17847       

Where -17847 = 0x49 0xBA = mov r10, imm64.

1 Like

Thanks, @certik. No, I am not asking for any new language feature. It will take toooo much time for a new feature to get into the standard (if ever) and toooo much time for it to be implemented by compilers.

I agree with @RonShepard that the current internal-procedure approach is a good solution for this kind of problem. The only issue is that it leads to security issues and makes the code non-compileable on some modern systems. Sure, some compilers are fixing this issue, but the default implementation still uses executable stacks. I hope LFortran will solve this problem for good.

It seems that most of us here agrees with ChatGPT o3-pro. This is very interesing.

There is an active Fortran community on StackOverflow (although with a different culture), but not everyone there participates in this discourse. Thus a question is asked there. If you want to prove ChatGPT o3-pro wrong, you may post your solution here under this post or on StackOverflow.

Thanks.

Thank you @ivanpribec for directing me to this option. I agree that it fixes the issue for gfortran. If only this became the default behavior.

This is very interesting. So it seems that ifx has not completely solved the problem yet.

Thank you @ivanpribec for posting the example here. Your example of root finding in a previous discussion is essentially the same. Are they consistent with what you quoted above? :wink:

1 Like