Re: ISO C 23 on macOS

Bruno Haible via Gnulib discussion list <[email protected]>
Newsgroups gmane.comp.lib.gnulib.bugs
Message-ID <2774913.pDPN8HKQh3@cagnes>
Paul Eggert wrote:
> Something like the following, perhaps.
> ...
> /* A type suitable for holding a function pointer, after casting it to this type.
>     A value of this type should never be used to call a function.
>     Cast the value to the proper function type before calling.
>     The incomplete struct type helps prevent improper use.  */
> typedef struct _X_incomplete (*new_generic_function_t) (struct _X_incomplete);

Yes, this would help prevent wrong use of this type. But it does not help
with the problem, which is:

> # define fp2 ((int (*) (int)) fp2)

... the need to cast the function pointer before use.
And the real problem is that when casting to ((int (*) (...))
the compiler will emit different code at the call site.

The fundamental problem:

In C, since 1990, it was equivalent to invoke a function pointer
via a specific function type (that lists all parameter types)
or via a generic function type. But in macOS/arm64, there now is
a difference: If the generic function type uses '...' in its
parameter list (which is a requirement in C23 or newer, since '()'
no longer serves this purpose), the parameters that correspond
to '...' are all passed on the stack.

This holds for both "cc -std=c23" and "cc" with older standard options,
as can be seen through the program below.

It does not hold for Windows MSVC/arm64, as can be seen by compiling
the program below on godbolt.org.

The call site can be far away from the function's definition. This is
quite typical when using an FFI (with GNU libffcall or libffi).
At the place where the function gets defined, we therefore cannot
force the way it will get invoked.

The problem exists since Apple invented this ABI [1]. But it is becoming
more urgent now because
  - Autoconf 2.73 and newer forces option "-std=gnu23" when possible.
    Also, newer versions of gcc and clang do/will adopt C23 by default.
  - The use of C23 mode disables the use of '()' and pushes the programmers
    towards using '(...)' as a replacement.

What I will do in GNU libffcall is:
  - For the <avcall.h> facility, add a 'bool variadic' parameter to the API,
    to inform the implementation whether the callee expects to be called
    via '...' or not.
  - For the <callback.h> facility, add a 'bool variadic' parameter to the API,
    to inform the implementation whether the resulting callback shall be
    invoked via a variadic function pointer or via a specific function pointer.
  - For the <trampoline.h> facility, force a specific function pointer,
    while making use of macros, 'typeof', and 'auto', to reduce the necessary
    verbosity.

Bruno

[1] https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms

==================================== foo.c ====================================
#include <stdio.h>

typedef int (*old_generic_function_t) ();

typedef int (*new_generic_function_t) (int unused, ...);

int
f (int unused, int x)
{
  return x + 42;
}

old_generic_function_t fp1 = (old_generic_function_t) f;

new_generic_function_t fp2 = (new_generic_function_t) f;

int (*fp3) (int, int) = f;

int
main ()
{
#if !(defined __cplusplus || __STDC_VERSION__ >= 202300) /* Avoid error "too many arguments to function" */
  printf ("Calling f through old_generic_function_t: %d\n", fp1 (42, 100));
#endif
  printf ("Calling f through new_generic_function_t: %d\n", fp2 (42, 1000));
  printf ("Calling f through precise function type:  %d\n", fp3 (42, 10000));
}
===============================================================================
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.