[llvm-dev] __attribute__((apple_abi)): targeting Apple/ARM64 ABI from Linux (and others)

Tim Northover via llvm-dev llvm-dev at lists.llvm.org
Thu Oct 8 02:15:09 PDT 2020


Hi Adrien,

On Thu, 8 Oct 2020 at 08:28, Adrien Guinet via llvm-dev
<llvm-dev at lists.llvm.org> wrote:
> * this supposes that the original targeted CC is Apple ARM64 AAPCS. In its current form,
> there is no way to support for instance vector calls (see for instance
> https://github.com/aguinet/llvm-project/commit/c4905ded3afb3182435df30e527955031cb0d098#diff-f124368bac3e5d7be20450aa83b166daR218)

I'm afraid I don't understand this point.

> * the fact that we can't target Apple's vector calls ABI shows that having one
> CC_AArch64Apple (as CC_Win64 exists) calling convention might not be the right
> implementation of this "apple_abi" attribute. Has someone better suggestions?

Needing two calling conventions seems really odd to me, unless it's
for genuinely different ABI slices (arm64 vs arm64e or arm64_32 for
example), and even there I'm not sure.

> The fact that va_start/va_end works by using the Linux ABI from a function whose arguments
> use the Apple ABI seems completely magical to me, so if someone knows why this work I
> would also be interested!

It's a series of coincidences conspiring together, I think. Linux's
varargs ABI doesn't change from the normal one, so functions have to
store all GPRs and vector registers that might contain arguments (as
well as where stack args start), and va_list describes where they were
stored:

typedef struct {
  void *stack;
  void *gr_top;
  void *vr_top;
  int gr_offs;
  int vr_offs;
} va_list;

This is what you're getting with your "va_list" declaration. While the
Darwin one is just a double pointer, but conceptually

typedef struct {
  void *stack;
} va_list;

because all anonymous args go on the stack there on Darwin.

That means when you call (Darwin's) va_start in your vprintf function
it "correctly" initializes the first field of that struct, leaving the
rest garbage. The gr_offs and vr_offs fields decide whether to use
gr_top/vr_top or stack to actually get the argument, and in this case
if gr_offs happens to be >= 0 it'll "correctly" use the stack to
retrieve everything. I'm guessing that happens to be the case for
simple programs (quite possibly the stack is still zero-initialized if
this is a trivial test-case).

You're also getting very lucky in that a Darwin varargs function
changes how much of the stack each argument uses, bringing it in line
with the normal AAPCS (otherwise the entire forwarding enterprise
would be doomed and you'd have to implement significant chunks of
vprintf to repack the arguments).

So, at a high level what you'll *want* to do to correctly forward from
Darwin to Linux is make sure that always happens: initialize gr_offs
and vr_offs to 0 to begin with so only the stack is available (I'd
also set the *_top fields to NULL for good measure). Take the time to
be grateful you're not trying to go the other way, too!

Now, back to your previous question...

> * For variadic functions (which are among the functions that have different ABIs), GCC and
> Clang have __builtin_ms_va_list. My understanding is that we should have the Apple
> equivalent, but I'm not sure to completely understand what's at stake here. Said
> differently, is this builtin used to make sure we use the va_list type of the Apple ABI,
> should the need arise to forward it to another function that uses the Apple ABI?

That, together with __builtin_ms_va_arg and __builtin_ms_va_start, are
for if you have a Linux-side function that wants to make use of a
va_list or anonymous args coming from Darwin code in a relatively
agnostic way. I think what you're doing (here at least) is so
intimately tied to bridging the two ABIs that using it would just be a
fig-leaf.

Cheers.

Tim.


More information about the llvm-dev mailing list