Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ft_printf misbehaves on Apple Silicon #21

Closed
agagniere opened this issue Nov 4, 2022 · 5 comments · Fixed by #24
Closed

ft_printf misbehaves on Apple Silicon #21

agagniere opened this issue Nov 4, 2022 · 5 comments · Fixed by #24
Assignees
Labels

Comments

@agagniere
Copy link
Owner

agagniere commented Nov 4, 2022

The reinterpret casts seem to be at fault

@agagniere agagniere added the bug label Nov 4, 2022
@agagniere agagniere self-assigned this Nov 4, 2022
@agagniere agagniere changed the title ft_printf misbehaves on arm64 ft_printf misbehaves on Apple Silicon Nov 13, 2022
@agagniere
Copy link
Owner Author

agagniere commented Nov 13, 2022

Variadic C functions implementation details

Apple Silicon

Apple documentation on what is specific to their architecture : writing arm64 code for apple platforms
In particular (emphasis mine) :

Update code that passes arguments to variadic functions

For functions that contain a variable number of parameters, Apple initializes the relevant registers (Stage A) and determines how to pad or extend arguments (Stage B) as usual. When it’s time to assign arguments to registers and stack slots, Apple platforms use the following rules for each variadic argument:

  1. Round up the Next SIMD and Floating-point Register Number (NSRN) to the next multiple of 8 bytes.
  2. Assign the variadic argument to the appropriate number of 8-byte stack slots.

Because of these changes, the type va_list is an alias for char*, and not for the struct type in the generic procedure call standard. The type also isn’t in the std namespace when compiling C++ code.

Note

The C language requires the promotion of arguments smaller than int before a call. Beyond that, the Apple platforms ABI doesn’t add unused bytes to the stack.

In a way, this va_list implementation isn't specific to Apple Silicon as it is how it used to be on i386 (32bit x86).

What is new here is that they specify a different calling convention for variadic functions that for other functions. (And that is why C functions interface in other languages had issues on Apple Silicon, e.g. Java Native Runtime FFI, CPython ctypes)

Amd64

To compare, on amd64 va_list is an address to a complex structure : amd64 and va_arg

typedef struct {
    unsigned int gp_offset;
    unsigned int fp_offset;
    void *overflow_arg_area;
    void *reg_save_area;
} va_list[1];

@agagniere
Copy link
Owner Author

agagniere commented Nov 13, 2022

TL;DR

So. the difference is that va_list used to be a pointer to a struct, and va_arg could modify the struct, without needing modify its address. Here va_list is a pointer to the stack, and it is the pointer itself that va_arg modifies.

So va_list must be passed by reference, as simple as that...
...excepts that is then fails on amd64

@agagniere agagniere linked a pull request Nov 13, 2022 that will close this issue
@agagniere
Copy link
Owner Author

I initially took it by value for vprintf and consorts, as it is the standard : C standard for vprintf

int vprintf( const char *restrict format, va_list vlist );

@agagniere
Copy link
Owner Author

agagniere commented Nov 13, 2022

Since we're here

Interesting reads :

Having some fun

foo.c

#include <stdio.h>
#include "libft.h"

void foo(char c, short h, int i, long L, short s, char b)
{
    printf("%2ti : %hhi\n", (void*)&c - (void*)&c, c);
    printf("%2ti : %hi\n",  (void*)&c - (void*)&h, h);
    printf("%2ti : %i\n",   (void*)&c - (void*)&i, i);
    printf("%2ti : %li\n",  (void*)&c - (void*)&L, L);
    printf("%2ti : %hi\n",  (void*)&c - (void*)&s, s);
    printf("%2ti : %hhi\n", (void*)&c - (void*)&b, b);
    ft_print_memory(&b, &c - &b + sizeof(c));
}

main.c

#define _embed(a, b, c, d) ((d << 24) + (c << 16) + (b << 8) + a)
#define embed(S) _embed(S[0], S[1], S[2], S[3])

int main()
{
    char  c = 'C';
    int   i = embed("int ");
    short half = '<' + ('>' << 8);
    long  L = embed("l o ") + (((long)embed("n g ")) << 32);

    foo(c, half, i, L, half, c);
}

Scenarios

Let's add a line in main.c to affect how it calls foo:

  • Nominal void foo(char, short, int, long, short, char);
  • Forgot header Nothing
  • Variadic void foo(char, ...);

We now have nominal.c, forgot.c and variadic.c

Makefile

CFLAGS  += -Wall -Wextra
CPPFLAGS = -I ~/Workspace/Libft/include
LDFLAGS  = -L ~/Workspace/Libft
LDLIBS   = -lft

SCENARIOS = nominal forgot variadic

all: $(SCENARIOS)

$(SCENARIOS): foo.o

clean:
    $(RM) $(SCENARIOS) *.o

Results

$ uname -mpsr
Linux 5.15.0-52-generic x86_64 x86_64
$ cc --version
cc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
$ make
cc -Wall -Wextra -I ~/Workspace/Libft/include  -c -o foo.o foo.c
cc -Wall -Wextra -I ~/Workspace/Libft/include -L ~/Workspace/Libft  nominal.c foo.o  -lft -o nominal
cc -Wall -Wextra -I ~/Workspace/Libft/include -L ~/Workspace/Libft  forgot.c foo.o  -lft -o forgot
forgot.c: In function ‘main’:
forgot.c:11:5: warning: implicit declaration of function ‘foo’ [-Wimplicit-function-declaration]
   11 |     foo(c, half, i, L, half, c);
      |     ^~~
cc -Wall -Wextra -I ~/Workspace/Libft/include -L ~/Workspace/Libft  variadic.c foo.o  -lft -o variadic
$ ./nominal 
 0 : 67
 4 : 15932
 8 : 544501353
20 : 2334870589177536620
12 : 15932
24 : 67
437f 0000 6c20 6f20 6e20 6720 3c3e 0000 C...l o n g <>..
696e 7420 3c3e ebbf 43                  int <>..C
$ # Interesting how arguments can be out of order
$ # Exactly identical results for the other scenarios  
$ uname -mpsr
Darwin 22.1.0 arm64 arm
$ cc --version
Apple clang version 14.0.0 (clang-1400.0.29.102)
Target: arm64-apple-darwin22.1.0
$ make
cc -Wall -Wextra -I ~/Workspace/Libft/include  -c -o foo.o foo.c
cc -Wall -Wextra -I ~/Workspace/Libft/include -L ~/Workspace/Libft  nominal.c foo.o  -lft -o nominal
cc -Wall -Wextra -I ~/Workspace/Libft/include -L ~/Workspace/Libft  variadic.c foo.o  -lft -o variadic
$ ./nominal
 0 : 67
 3 : 15932
 7 : 544501353
15 : 2334870589177536620
17 : 15932
18 : 67
433c 3e6c 206f 206e 2067 2069 6e74 203c C<>l o n g int <
3e00 43                                 >.C
$ # "forgot" scenario won't compile
$ ./variadic
 0 : 67
 3 : -18496
 7 : 1796126672
15 : 6091094264
17 : -20520
18 : 44
2cd8 aff8 b80e 6b01 0000 00d0 b70e 6bc0 ,.....k.......k.
b700 43                                 ..C
$ # As we can see, arguments are not present where the callee expects them to be !
$ # (Except `c`, the only non variadic argument)

@agagniere
Copy link
Owner Author

agagniere commented Nov 13, 2022

The other way around

Let's now see what appends when the callee is actually variadic on Apple Silicon

foo.c

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

#include "libft.h"

void foo(char c, ...)
{
    va_list args;

    va_start(args, c);
    char *start = args;            printf("[%3ti] %20hhi\n", &c - start , c);
    /* Default argument promotion must be taken into account when using va_arg */
    short h = va_arg(args, int);   printf("[%3ti] %20hi\n",  args - start, h);
    int   i = va_arg(args, int);   printf("[%3ti] %20i\n",   args - start, i);
    long  L = va_arg(args, long);  printf("[%3ti] %20li\n",  args - start, L);
    short s = va_arg(args, int);   printf("[%3ti] %20hi\n",  args - start, s);
    char  b = va_arg(args, int);   printf("[%3ti] %20hhi\n", args - start, b);
    ft_print_memory(start, args - start);
    va_end(args);
}

Output

$ ./variadic
[-17]                   67
[  8]                15932
[ 16]            544501353
[ 24]  2334870589177536620
[ 32]                15932
[ 40]                   67
3c3e 0000 0000 0000 696e 7420 0000 0000 <>......int ....
6c20 6f20 6e20 6720 3c3e 0000 0000 0000 l o n g <>......
4300 0000 0000 0000                     C.......

Yep, as announced in their documentation linked before, each argument is put in a 8-bytes slot.

For other scenarios, as the main doesn't know foo is variadic, nothing good happens, e.g. :

[-17]                   67
[  8]                    0
[ 16]            544153708
[ 24]  4484459333298453268
[ 32]                28265
[ 40]                  -96
0000 0000 0000 0000 6c20 6f20 6e20 6720 ........l o n g
147b f002 0100 3c3e 696e 7420 0100 0043 .{....<>int ...C
a0b7 ef6c 0100 0000                     ...l....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant