iains/gcc-12-branch

Calling C from Fortran with variable number of arguments failed to work on Apple M1

mike4239 opened this issue · 5 comments

cat tst_arg.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

void tst_arg_ (int *, int *, int *);
void tst_arg2_ (int *, ...);

void tst_arg_ (int *a1, int *a2, int *a3) {
printf("tst_arg: a1:%d a2:%d (0x%08X) a3:%d (0x%08X)\n",*a1, *a2, *a2, *a3, *a3);
}
void tst_arg2_ (int *a1, ...) {
va_list ap;
int a2, a3;
va_start (ap, a1);
a2 = *(va_arg (ap, int *));
a3 = *(va_arg (ap, int *));
va_end (ap);
printf("tst_arg2: a1:%d a2:%d (0x%08X) a3:%d (0x%08X)\n",*a1, a2, a2, a3, a3);
}

cat myarg.f
program myarg
use iso_c_binding
integer4, parameter :: a1 = 1
integer
4, parameter :: a2 = 20
integer*4, parameter :: a3 = 300
call tst_arg (a1, a2, a3)
call tst_arg2 (a1, a2, a3)
stop
end

compiling:
clang -c tst_arg.c
gfortran -c myarg.f
gfortran -o myarg myarg.o tst_arg.o

file *.o
myarg.o: Mach-O 64-bit object arm64
tst_arg.o: Mach-O 64-bit object arm64

The results:

tst_arg: a1:1 a2:20 (0x00000014) a3:300 (0x0000012C)
tst_arg2: a1:1 a2:1875179488 (0x6FC4F7E0) a3:1384120320 (0x52800000)

gfortran version:
gcc version 12.2.0 (Homebrew GCC 12.2.0)

clang version:
Apple clang version 14.0.0 (clang-1400.0.29.202)

This is invalid code. From Fortran standard, F2018, “18.3.7 Interoperability of procedures and procedure interfaces”:

A Fortran procedure interface is interoperable with a C function prototype if
[…]
(7) the prototype does not have variable arguments as denoted by the ellipsis (...)

Later, we have:

NOTE 18.19

The C language allows specification of a C function that can take a variable number of arguments (ISO/IEC 9899:2011, 7.16). This document does not provide a mechanism for Fortran procedures to interoperate with such C functions.

But, the code has always been working on mac OS on x86_64 (and i386) Intel. It starts to fail just on ARM64.

The code is invalid. It happened to work on Intel because of the way the ABI is defined, but it was never considered valid code, or even good practice. In fact, it would already be broken on other platforms.

See a detailed description in Apple's documentation: Don’t Redeclare a Function to Have Variable Parameters

Just to show that the issue is not specific to Fortran, or even to GCC:

$ cat u.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

void tst_arg_ (int *, int *, int *);
void tst_arg2_ (int *, ...);

void tst_arg_ (int *a1, int *a2, int *a3) {
printf("tst_arg: a1:%d a2:%d (0x%08X) a3:%d (0x%08X)\n",*a1, *a2, *a2, *a3, *a3);
}
void tst_arg2_ (int *a1, ...) {
va_list ap;
int a2, a3;
va_start (ap, a1);
a2 = *(va_arg (ap, int *));
a3 = *(va_arg (ap, int *));
va_end (ap);
printf("tst_arg2: a1:%d a2:%d (0x%08X) a3:%d (0x%08X)\n",*a1, a2, a2, a3, a3);
}

$ cat v.c
extern void tst_arg_ (int *, int *, int *);
extern void tst_arg2_ (int *, int *, int *); 

int main (void) {
  int i = 4;
  tst_arg_ (&i, &i, &i);
  tst_arg2_ (&i, &i, &i);
}

Calling this var args function tst_arg2_ as fixed args function, from C, fails in both GCC and clang:

$ gcc-13 u.c v.c && ./a.out
tst_arg: a1:4 a2:4 (0x00000004) a3:4 (0x00000004)
tst_arg2: a1:4 a2:0 (0x00000000) a3:-1442839565 (0xAA0003F3)
$ clang u.c v.c && ./a.out 
tst_arg: a1:4 a2:4 (0x00000004) a3:4 (0x00000004)
zsh: segmentation fault  ./a.out

I think this issue be closed.

iains commented

Yes, I think so.

With Arm64 on macOS we have an additional constraint; the ABI lays out the stack frame differently for named and unnamed parameters.

That is unusual (and has caused us some extra work in the implementation since no other GCC platform currently operates that way).

Therefore, I do not see how we could support renaming of function prototypes even if someone were willing to write the patch, I think it's fundamentally in conflict with the ABI.