In the first article in this series on developing for Apple Silicon Macs using assembly language, I built a simple framework AsmAttic to use equally the basis for developing ARM associates language routines. In that, I provided a brusque and simple sit-in of calling an associates routine and getting its event. This article starts to explain the mechanics of writing your own routines, by explaining the register architecture of ARM64 processors.

In that first article, I glibly produced a C wrapper of
extern double multadd(double, double, double)
to call an assembly language routine of
_multadd:
STR LR, [SP, #-16]!
FMADD D0, D0, D1, D2
LDR LR, [SP], #16
RET

Stepping through the lines of associates, that first saves a prepare of registers, performs the floating point operation I want, restores the registers, and returns. For whatsoever of that to make sense, you first need to understand the register architecture of the ARM64 processor.

The processor has three chief types of register: general-purpose, floating point (including SIMD) and special. When calling and running routines like this, you lot're near concerned with the first two, which are explained in detail in ARM's Procedure Call Standard (references below), and Apple tree's platform-specific document (references).

armregisterarch

(Tear-out PDF version: armregisterarch)

There are 31 general-purpose registers, each of which tin can exist used for 64- or 32-bit values. When used for 64-fleck, they're named X0 to X30, and for 32-bit they're W0 to W30.

There are 32 floating signal registers, each of which can be used for 128-bit values as V0 to V31, 64-fleck as D0 to D31, and 32-bit as S0 to S31.

For the sake of simplicity, in this series I'll default to using 64-bit values for integers and floating point wherever possible. This means that the registers I'm going to work with most normally are the X and D series, as in the example to a higher place. In higher language equivalents, they equate to data of types C long, arrow, Swift Int, C double and Swift Double.

The kickoff viii registers in each of these 2 types, X0-X7 and D0-D7, are used to pass arguments to assembly functions, and the beginning, X0 and D0, are used to return the result of an assembly routine. They are used in order: the first non-floating indicate statement will be passed in X0, the 2nd in X1, and so on; the first floating bespeak argument will exist passed in D0, the 2d in D1, and so on.

It'due south important to remember that these are used according to the register grouping. For a C wrapper of
extern double burble(long, double, long, double)
the first long will exist passed in X0, the get-go double in D0, the 2nd long in X1, and the second double in D1, with the result being returned in D0, as information technology's a double.

This is the simplest way of passing arguments to an associates routine, in which the value is used, and is placed in the register ready for the routine to access: 'call past value' (or 'pass by value'). The only event returned, in either X0 or D0, is a single value too.

Passing arrays, structures and other arguments which tin't simply be put into a single register requires a different method, in which a arrow to the data is passed: 'call by reference'. This is also used to enable a routine to return more than 1 result: when an argument is passed every bit a pointer to a floating signal number, for example, if the routine changes the value referenced by that pointer, so that changed value is available to the code which calls that routine.

Let'due south suppose that a floating point routine takes three doubles representing x, y and z co-ordinates, and I desire information technology to render iii changed doubles representing those co-ordinates transformed into a projected infinite. As only ane floating point number tin be returned as a result, in register D0, what we could do instead is laissez passer those co-ordinates past reference,
extern void transform(*double, *double, *double)

In Swift, we might call that in turn as
transform(&x, &y, &z)
to pass the addresses of those Doubles.

In an assembly routine, calling by value is simplest, as the values are bachelor directly from the designated registers. Calling past reference is a bit more complicated, though, as before the routine can access values information technology first has to load them from the address passed. And that's where my next article starts.

References

Code in Associates for Apple Silicon with the AsmAttic app (previous commodity on this weblog)
Procedure Call Standard for the Arm 64-bit Architecture (ARM) from Github
Writing ARM64 Code for Apple Platforms (Apple)
Stephen Smith (2020) Programming with 64-Bit ARM Assembly Language, Apress, ISBN 978 1 4842 5880 4.
Daniel Kusswurm (2020) Modern Arm Assembly Language Programming, Apress, ISBN 978 ane 4842 6266 5.
ARM64 Teaching Set Reference (ARM).