Passing closures results in a segfault on Apple Silicon

I have a program that has been working fine for me until I got this new Macbook with Apple Silicon. I’ve been able to make a small reproducible build with two files:

! main.f90
program main
  use mymodule, only : mysub
  real :: x
  x = 5.0
  print *, "Calling closure from the module in which it is defined:"
  call closure()
  print *, "Calling closure from another module:"
  call mysub(closure)

contains

subroutine closure()
    print *, x+1
end subroutine

end program main
! mymodule.f90
module mymodule

abstract interface
  subroutine OBJ()
  end subroutine OBJ
end interface

contains

subroutine mysub(closure)
  procedure(OBJ) :: closure
  call closure()
end subroutine

end module mymodule

I compile it as follows:

gfortran -c mymodule.f90
gfortran main.f90 mymodule.o

When I run it on Apple Silicon (with gfortran 13.2.0) I get Segmentation fault: 11.

If I try the same compilation commands inside a docker container (either with or without --platform linux/amd64) the program runs successfully. Interestingly, compiling it within docker gives a warning about requiring an executable stack - the same warning does not appear on Apple Silicon.

I’m not sure what to do to investigate this further, can anyone help?

Update: This was able to compile with nagfor. Should I submit this as a bug to gcc in that case?

I put all the code in a single file, and it works alright with both gfortran 14.1.0 and nagfor 7.2 on MacOS 12.7.5.

Thanks for taking a look @RonShepard . Can you confirm that you’re on Apple Silicon? And did it segfault for you if it was two files?

I’m on macOS 14.5, and even when putting everything in one file it still segfaults on me.

No problems for me. One file and with two files. I am on Apple Silicon. macOS 14.5, gfortran 14.1.0.

Also works fine for my with gfortran (some 14.0.1 snapshot). But running linux on x86_64.

Technically not a closure though. Passing procedures through argument lists has been around since the 1960s. However creating closures aren’t supported.

On macOS Monterey (v12.7.1):

$ gfortran mymodule.f90 main.f90 
$ ./a.out
 Calling closure from the module in which it is defined:
   6.00000000    
 Calling closure from another module:
   6.00000000    
$ gfortran -v
Using built-in specs.
COLLECT_GCC=gfortran
COLLECT_LTO_WRAPPER=/usr/local/Cellar/gcc/13.2.0/bin/../libexec/gcc/x86_64-apple-darwin21/13/lto-wrapper
Target: x86_64-apple-darwin21
Configured with: ../configure --prefix=/usr/local/opt/gcc --libdir=/usr/local/opt/gcc/lib/gcc/current --disable-nls --enable-checking=release --with-gcc-major-version-only --enable-languages=c,c++,objc,obj-c++,fortran --program-suffix=-13 --with-gmp=/usr/local/opt/gmp --with-mpfr=/usr/local/opt/mpfr --with-mpc=/usr/local/opt/libmpc --with-isl=/usr/local/opt/isl --with-zstd=/usr/local/opt/zstd --with-pkgversion='Homebrew GCC 13.2.0' --with-bugurl=https://github.com/Homebrew/homebrew-core/issues --with-system-zlib --build=x86_64-apple-darwin21 --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.2.0 (Homebrew GCC 13.2.0) 

What is your gfortran target triplet? Are you targetting the Arm instruction set or the x86_64 emulation? You can also check the type of the executable:

$ file a.out
a.out: Mach-O 64-bit executable x86_64

AFAIK, the executable stack is used because many Fortran compilers implement internal procedures using thunks (for the interested reader I recommend Doctor Fortran in “Think, Thank, Thunk”).

Here is the relevant output on a different computer (with a recent OS).

$ uname -a
Darwin MacStudio.local 23.4.0 Darwin Kernel Version 23.4.0: Fri Mar 15 00:12:49 PDT 2024; root:xnu-10063.101.17~1/RELEASE_ARM64_T6020 arm64
$ gfortran -v
Using built-in specs.
COLLECT_GCC=gfortran
COLLECT_LTO_WRAPPER=/opt/homebrew/Cellar/gcc/14.1.0/bin/../libexec/gcc/aarch64-apple-darwin23/14/lto-wrapper
Target: aarch64-apple-darwin23
Configured with: ../configure --prefix=/opt/homebrew/opt/gcc --libdir=/opt/homebrew/opt/gcc/lib/gcc/current --disable-nls --enable-checking=release --with-gcc-major-version-only --enable-languages=c,c++,objc,obj-c++,fortran --program-suffix=-14 --with-gmp=/opt/homebrew/opt/gmp --with-mpfr=/opt/homebrew/opt/mpfr --with-mpc=/opt/homebrew/opt/libmpc --with-isl=/opt/homebrew/opt/isl --with-zstd=/opt/homebrew/opt/zstd --with-pkgversion='Homebrew GCC 14.1.0' --with-bugurl=https://github.com/Homebrew/homebrew-core/issues --with-system-zlib --build=aarch64-apple-darwin23 --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 14.1.0 (Homebrew GCC 14.1.0) 
$ gfortran obj.f90 && a.out
 Calling closure from the module in which it is defined:
   6.00000000    
 Calling closure from another module:
   6.00000000    

Thanks everyone for all the input.

I think the problem has to do with where I got gfortran. I installed it via nix. When I uninstalled it and installed the same version from macports, suddenly everything works. If anyone is interested here is the output of gfortran -v for each version:

Installed via nix:

Using built-in specs.
COLLECT_GCC=/nix/store/iq8m4kv876ijdjqwp874vrb8zxfkqi98-gfortran-13.2.0/bin/gfortran
COLLECT_LTO_WRAPPER=/nix/store/iq8m4kv876ijdjqwp874vrb8zxfkqi98-gfortran-13.2.0/libexec/gcc/aarch64-apple-darwin/13.2.0/lto-wrapper
Target: aarch64-apple-darwin
Configured with: ../gcc-13.2.0/configure --prefix=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-gfortran-13.2.0 --with-gmp-include=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-gmp-with-cxx-6.3.0-dev/include --with-gmp-lib=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-gmp-with-cxx-6.3.0/lib --with-mpfr-include=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-mpfr-4.2.1-dev/include --with-mpfr-lib=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-mpfr-4.2.1/lib --with-mpc=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-libmpc-1.3.1 --with-native-system-header-dir=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-libSystem-11.0.0/include --with-build-sysroot=/ --with-gxx-include-dir=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-gfortran-13.2.0/include/c++/13.2.0/ --program-prefix= --enable-lto --disable-libstdcxx-pch --without-included-gettext --with-system-zlib --enable-static --enable-languages=fortran,objc,obj-c++ --disable-multilib --enable-plugin --build=aarch64-apple-darwin --host=aarch64-apple-darwin --target=aarch64-apple-darwin
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 13.2.0 (GCC)

Install via macports:

Using built-in specs.
COLLECT_GCC=gfortran
COLLECT_LTO_WRAPPER=/opt/local/libexec/gcc/arm64-apple-darwin23/13.2.0/lto-wrapper
Target: arm64-apple-darwin23
Configured with: /opt/local/var/macports/build/_opt_bblocal_var_buildworker_ports_build_ports_lang_gcc13/gcc13/work/gcc-13.2.0/configure --prefix=/opt/local --build=arm64-apple-darwin23 --enable-languages=c,c++,objc,obj-c++,lto,fortran,jit --libdir=/opt/local/lib/gcc13 --includedir=/opt/local/include/gcc13 --infodir=/opt/local/share/info --mandir=/opt/local/share/man --datarootdir=/opt/local/share/gcc-13 --with-local-prefix=/opt/local --with-system-zlib --disable-nls --program-suffix=-mp-13 --with-gxx-include-dir=/opt/local/include/gcc13/c++/ --with-gmp=/opt/local --with-mpfr=/opt/local --with-mpc=/opt/local --with-isl=/opt/local --with-zstd=/opt/local --enable-checking=release --disable-multilib --enable-lto --enable-libstdcxx-time --with-build-config=bootstrap-debug --with-as=/opt/local/bin/as --with-ld=/opt/local/bin/ld-classic --with-ar=/opt/local/bin/ar --with-bugurl=https://trac.macports.org/newticket --enable-host-shared --with-darwin-extra-rpath=/opt/local/lib/libgcc --with-libiconv-prefix=/opt/local --disable-tls --with-gxx-libcxx-include-dir=/opt/local/libexec/gcc13/libc++/include/c++/v1 --with-pkgversion='MacPorts gcc13 13.2.0_4+stdlib_flag' --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.2.0 (MacPorts gcc13 13.2.0_4+stdlib_flag)

The only major difference that jumps out at me is that the nix Target is aarch64-apple-darwin and the one for macports is arm64-apple-darwin23, and also the +stdlib_flag in macports but I’m not sure that’s so relevant here.

In both cases file a.out provides a.out: Mach-O 64-bit executable arm64

I’d investigate the nix package, but I’m as yet not very comfortable with their configuration language so I think I’ll leave it here. Once again thanks to all who chimed in.

1 Like

According to Wikipedia, Aarch64 and ARM64 refer to the same instruction set. Ironically, it’s no better than the x86-64, x86_64, amd64 confusion (not to mention the Itanium, IA-64).

I’m guessing that refers to whether the compiler supports the -stdlib=... flag. (It allows you to select between libc++ and libstdc++ libraries), but I’m also not sure if it relevant.