Strange issue with ifx compiler and '-assume recursion'

I’ve discovered this issue while trying to get some C and Fortran code to interop.

Essentially when using an internal procedure I get problems with ifx when using the ‘-assume recursion’ flag. I’ve made a minimally reproducible example here: Compiler Explorer

It compiles on 2023.2 and 2024, but has some runtime problems (it hangs, but in some test cases with certain compile flags I can get it to segfault). With 2023.0 and 2023.1 the compiler straight up crashes when trying to compile this.

Changing the location of the call to C_F_PROCPOINTER resolves the issue, but creates a separate issue in ifort (Problem with ifort compiler and type(C_PTR) - #6 by zaikunzhang).

Given that it crashes during compilation on 2023.0 and 2023.1 I’m guessing this is a compiler bug, but I’d appreciate it if anyone had any insight on this matter.

1 Like

A different way to trigger this is by adding the recursive attribute to the subroutine. (Which AFAIK is the default since Fortran 2018). If you add the non_recursive attribute to the subroutine it works with all major versions of ifx (the source attribute appears to have precedence over the -assume recursion compiler flag).

1 Like

You’re right that it’s the default in the 2018 standard, but Intel Fortran does not default to that unless you ask for -standard-semantics. I was very put out when I learned this.

Quoting @greenrongreen in another thread:

Admittedly, it doesn’t help in this situation, as it still crashes with even with 2024.0.

I’ve managed to find a few leads. The first thing I did was to eliminate every line I could, but would still lead to a segmentation fault:

! recursion.F90
!
module mymodule_mod
    use, intrinsic :: iso_c_binding
    implicit none
    private
    public :: callback_wrapper
    abstract interface
        subroutine callback() bind(c)
        end subroutine callback
    end interface
contains
#if RECURSION
    recursive subroutine callback_wrapper(callback_ptr)
#else
    subroutine callback_wrapper(callback_ptr)
#endif
        type(c_funptr), value :: callback_ptr
        procedure(callback), pointer :: callback_procedure
        call c_f_procpointer(callback_ptr, callback_procedure)
        call callback_closure
    contains
        subroutine callback_closure
            call callback_procedure()
        end subroutine callback_closure
    end subroutine callback_wrapper
end module mymodule_mod

program main
    use, intrinsic :: iso_c_binding
    use mymodule_mod
    implicit none
    call callback_wrapper(c_funloc(callback))
contains
    subroutine callback() bind(c)
        print *, "callback called"
    end subroutine
end program

When compiled normally the callback is called as expected. However when you turn on the definition -DRECURSION, or equivalently the flag -assume recursion, the program suffers a segmention fault.

Looking at the diff view in compiler explorer there are a few noticeable changes (red is the one that works, green is the one that segfaults):

The recursive attribute appears to changes a few load instructions, but I can’t really why :person_shrugging:. From here I noticed if I add -O1, the internal procedure gets inlined (the equivalent of removing the closure), and the problem disappears:

(You can see the internal procedure is left empty in the right panel. In the left panel, it consists just of a jump to the to the target of the procedure pointer callback_procedure, and was also inlined). In your original example, -O1 doesn’t help however, for whatever bug-related reason.

The second observation, is that initialising the procedure pointer, also makes the segfault disappear:

procedure(callback), pointer :: callback_procedure => null()  ! initalization

In fact, the generated assembly is now the same with or without the recursive attribute. I’ve also checked that the initialization to null() works in your original example.

Note that it doesn’t work if you nullify the pointer in as the first statement:

procedure(callback), pointer :: callback_procedure
nullify(callback_procedure)  ! first statement

I’d need to consult the standard to say if this is semantically equivalent to the initialisation or not.

The thing about recursive is that “the values of variables allocated in one activation of a recursive procedure must be protected from change by other activations,” quoting from a random IBM page on PL/I. If one of the local variables in a recursive procedure is a pointer, how do you make sure that other calls of the recursive procedure don’t invalidate the pointer? What if other threads change it?

1 Like

Perhaps I am doing something wrong with your reproducer, as it compiles and runs with the 2024.0.0 ifx with or without -assume recursion.

type or paste code here
ifx -what -V -assume recursion repro.F90 
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.0 Build 20231017
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

 Intel(R) Fortran 24.0-1238.2
GNU ld version 2.39-9.fc38
$ ./a.out
 callback called

$ ifx -what -V repro.F90 
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.0 Build 20231017
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

 Intel(R) Fortran 24.0-1238.2
GNU ld version 2.39-9.fc38
rwgreen@orcsle153:~/quad/tuesday$ ./a.out
 callback called

and with DRECURSION

ifx -what -V -DRECURSION repro.F90 
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.0 Build 20231017
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

 Intel(R) Fortran 24.0-1238.2
GNU ld version 2.39-9.fc38
$ ./a.out
 callback called

Did you mean this causes a segfault with the older 2023.1 or even older ifx compilers? Could you add the -what -V options to print the compiler version and edit number information?
Also, you can add -# option to dump all defines, options, paths for the compiler without actually compiling the code.

I agree with Steve that perhaps -standard-semantics should be a default. I’ll bring this up with the team tomorrow.

Without recursive it works (on all version):

~/repro$ ifx repro.F90 
~/repro$ ./a.out
 callback called

With the recursive attribute it causes an ICE in ifx 2023.1:

~/repro$ ifx -what -V -DRECURSION repro.F90    # or -assume recursion
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2023.1.0 Build 20230320
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

 Intel(R) Fortran 23.0-1474.2
          #0 0x0000000001f63112
          #1 0x0000000001fc5727
          #2 0x0000000001fc5850
          #3 0x00007fbb9486fd70
          #4 0x0000000003c0eaa5
          #5 0x0000000003c0d543
          #6 0x0000000003c0a650
          #7 0x0000000002e8a09d
          #8 0x00000000022ebeec
          #9 0x0000000002e84fbd
         #10 0x0000000002b20da4
         #11 0x0000000002e9b94d
         #12 0x0000000002b1ca5b
         #13 0x0000000002e84aed
         #14 0x0000000002b1f02b
         #15 0x0000000002e9bbad
         #16 0x0000000002b1dfde
         #17 0x0000000002e84ccd
         #18 0x00000000022eaa8a
         #19 0x0000000002ee4e4d
         #20 0x0000000002e87c1d
         #21 0x00000000022eaa8a
         #22 0x0000000001f08104
         #23 0x0000000001f06b83
         #24 0x0000000001eb5859
         #25 0x00000000020797c5
         #26 0x00007fbb9485a2bd __libc_start_main + 239
         #27 0x0000000001cf1729

/tmp/ifx0970681804QH7mYk/ifx3ApBUh.i90: error #5633: **Internal compiler error: segmentation violation signal raised** Please report this error along with the circumstances in which it occurred in a Software Problem Report.  Note: File and line given may not be explicit cause of this error.
compilation aborted for repro.F90 (code 3)

If null() is added, it works again:

~/repro$ nano repro.F90   # add null()
~/repro$ ifx -what -V -DRECURSION repro.F90 
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2023.1.0 Build 20230320
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

 Intel(R) Fortran 23.0-1474.2
GNU ld (GNU Binutils; SUSE Linux Enterprise 15) 2.37.20211103-150100.7.29
~/repro$ ./a.out
 callback 1 called

I tested now with the same build as you, on a locally installed instance, and it works:

$ ifx -what -V -DRECURSION repro.F90 
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.0 Build 20231017
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

 Intel(R) Fortran 24.0-1238.2
GNU ld (GNU Binutils; SUSE Linux Enterprise 15) 2.39.0.20220810-150100.7.40
$ ./a.out
 callback 1 called

(I double-checked null() was not there.)


The ifx 2024.0.0 version on Compiler Explorer (running on AWS) fails however: Compiler Explorer

 Intel(R) Fortran 24.0-1238.2
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.0 Build 20231017
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

ASM generation compiler returned: 0
 Intel(R) Fortran 24.0-1238.2
GNU ld (Compiler-Explorer-Build-gcc--binutils-2.40) 2.40
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.0 Build 20231017
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

Execution build compiler returned: 0
Program returned: 174
forrtl: severe (174): SIGSEGV, segmentation fault occurred
Image              PC                Routine            Line        Source             
libc.so.6          00007F2D14042520  Unknown               Unknown  Unknown
output.s           00000000004051BC  Unknown               Unknown  Unknown
output.s           000000000040519E  Unknown               Unknown  Unknown
output.s           0000000000405B36  Unknown               Unknown  Unknown
output.s           000000000040515D  Unknown               Unknown  Unknown
libc.so.6          00007F2D14029D90  Unknown               Unknown  Unknown
libc.so.6          00007F2D14029E40  __libc_start_main     Unknown  Unknown
output.s           0000000000405075  Unknown               Unknown  Unknown
This file was compiled by Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.0 Build 20231017 using the options -g -o /app/output.s -gxx-name=/opt/compiler-explorer/gcc-13.2.0/bin/g++ -what -V -fpp -DRECURSION -O0 -L./lib -Wl,-rpath,./lib -Wl,-rpath,/opt/compiler-explorer/intel-fortran-2024.0.0.49493/compiler/latest/lib 
1 Like

Interesting … I noticed they use binutils 2.40 and gcc 13.2. Note that my FC 38 server has binutils 2.39 and gcc 13.0. I wonder if their base OS is FC39 now. That was released Nov 7th, well after our 2024.0.0 was validated on FC38. So their glibc may (probably) be updated to something not tested or supported in 2024.0. Our Sys Reqs are here: https://www.intel.com/content/www/us/en/developer/articles/system-requirements/oneapi-fortran-compiler-system-requirements.html

Perhaps someone else can chime in on whether they also see a segfault on any OS/gcc from FC38 or older / gcc (and glibc) from gnu 13.0 or older.

@greenrongreen When I compile with 2024.0.1 I don’t get a segfault, it just hangs. Eventually I Ctrl+C it and get some sort of stack trace. Adding => null() fixes this (thanks so much for that @ivanpribec)

# ifx -what -V -assume recursion test.f90 
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2024.0.1 Build 20231122
Copyright (C) 1985-2023 Intel Corporation. All rights reserved.

 Intel(R) Fortran 24.0-1238.2
GNU ld (GNU Binutils for Ubuntu) 2.38
# ./a.out
^Cforrtl: error (69): process interrupted (SIGINT)
Image              PC                Routine            Line        Source             
libc.so.6          00007FF6D203B520  Unknown               Unknown  Unknown
a.out              0000000000405280  Unknown               Unknown  Unknown
a.out              0000000000405343  Unknown               Unknown  Unknown
a.out              000000000040515D  Unknown               Unknown  Unknown
libc.so.6          00007FF6D2022D90  Unknown               Unknown  Unknown
libc.so.6          00007FF6D2022E40  __libc_start_main     Unknown  Unknown
a.out              0000000000405075  Unknown               Unknown  Unknown
# 

@ivanpribec I think I figured out why initializing to null works, but calling nullify immediately after declaration does not. Initializing to null implicitly adds the save attribute. If you just add the save attribute to the procedure pointer without initializing, it also fixes the bug, although it might introduce some other bugs depending on how you use the procedure pointer.