September 25th, 2020

# Swift Calling Conventions on ARM64: Float / Double

## Learn what registers Swift uses for floating point numbers

This is the 2nd post in a series on how Swift’s calling conventions work on ARM64. If you have not read my first post on Swift’s calling conventions for `Int` and `Bool` types, please read that first.

To try out the examples in this article, please make sure you have done the following set up first:

Item #3 is very important. If you set a breakpoint in Xcode’s UI the following steps may not work.

## The Code We Will Investigate

In this post we will investigate the calling conventions used in the following code. In the iOS app project you created, replace the contents of `ViewController.swift` with the following:

``````import UIKit

class ViewController: UIViewController {

print(testFloat(a: 100.111, b: 100.222))
print(testDouble(a: 200.111, b: 200.222))

print(testFloatInt(a: 300.111, b: 300.222, c: 300333, d: 300444))
print(testIntFloatDoubleInt(a: 400111, b: 400.222, c: 400.333, d: 400444))
}
}

func testFloat(a: Float, b: Float) -> Float {
return a + b
}

func testDouble(a: Double, b: Double) -> Double {
return a + b
}

func testFloatInt(a: Float, b: Float, c: Int, d: Int) -> Float {
return a + b + Float(c + d)
}

func testIntFloatDoubleInt(a: Int, b: Float, c: Double, d: Int) -> Double {
return Double(a) + Double(b) + c + Double(d)
}``````

Before continuing, make sure you can build and run the project.

## Background Information

The base standard provides for passing arguments in general-purpose registers (`r0`-`r7`), SIMD/floating-point registers (`v0`-`v7`) and on the stack. For subroutines that take a small number of small parameters, only registers are used.

Technically the document does say that floating-point arguments will be stored in registers `v0`-`v7`, but its actually a little more complicated than that. Each `v<n>` register holds 128bits, and there are different registers which reference different chunks of this memory.

These docs explain how these other registers work:

The mapping between the registers is as follows:
`d<n>` maps to the least significant half of `v<n>`
`s<n>` maps to the least significant half of `d<n>`
`h<n>` maps to the least significant half of `s<n>`
`b<n>` maps to the least significant half of `h<n>`

This means that the register `d0` maps to the least significant 64 bits of `v0`. Similarly `s0` maps to the least significant 32 bits of `d0` (transitively, `s0` also maps the least significant 32 bits of `v0`).

Since `Float`s only need 32 bits, Swift will store them in `s<n>` registers. Similarly Swift will use `d<n>` registers to store `Doubles` since they need 64 bits.

Armed with this knowledge, we should expect that `Float` arguments will be stored in registers `s0`-`s7`. Let’s try it out.

## Float

### Argument Values

Use the approach described here to place a breakpoint on `testFloat`, and run the project until the breakpoint triggers. Then run the following commands in LLDB:

``````(lldb) register read s0 s1 -f f
s0 = 100.111
s1 = 100.222``````

This confirms our hypothesis! We can find `Float` arguments in the `s0`-`s7` registers.

We can also confirm the relationship between `s0`, `d0`, and `v0` below:

``````(lldb) register read s0 -f h
s0 = 0x42c838d5
(lldb) register read d0 -f h
d0 = 0x0000000042c838d5
(lldb) register read v0 -f h
v0 = 0x00000000000000000000000042c838d5``````

Notice how the larger registers contain the same data, just padded.

### Return Value

Just like we learned in my previous article, `Float` return values will be stored the same way that the first `Float` argument is stored. This means that a `Float` return value should be stored in `s0`.

Let’s confirm:

``````(lldb) thread step-out
(lldb) register read s0 -f f
s0 = 200.333``````

Great! This is exactly what we expect. Just to remind you, `thread step-out` will execute the rest of the current function, pop the call stack, and then pause the debugger. At this point, the return value should be correctly stored in the intended register.

## Double

### Argument Values

`Double` works exactly the same as `Float` execpt that values are stored in the 64bit `d0`-`d7` registers. This means that the arguments to `testDouble` will be stored in `d0` and `d1`.

You can verify this by setting a breakpoint on `testDouble` and running:

``````(lldb) register read d0 d1 -f f
d0 = 200.111
d1 = 200.222``````

### Return Value

The return value works exactly the same as with `Float`, execpt it uses the `d0` register.

You can use these commands to try it out:

``````(lldb) thread step-out
(lldb) register read d0 -f f
d0 = 400.333``````

## Mixed Argument Types

What happens if a function has mixed argument types?

This works as you would probably expect. `Float`/`Double` arguments are stored in the floating point registers, and `Int`/`Bool` arguments are stored in the general purpose registers.

Let’s try it out to see what happens:

### Float, Int

Set a breakpoint on `testFloatInt` and run the following commands.

``````(lldb) register read s0 s1 -f f
s0 = 300.111
s1 = 300.222
(lldb) register read x0 x1 -f d
x0 = 300333
x1 = 300444``````

Notice how the `Int` arguments are stored in registers `x0`, and `x1` even though they are the 3rd, and 4th arguments respectively. This is because they are the 1st and 2nd `Int` arguments.

Also notice how Swift chose to put the floaing point values in the floating point registers `s<n>` even though the general purpose registers `x<n>` would have had enough space to store these values.

### Int, Float, Double Int

Let’s try out a more complex example in which integer and floating point values are interleaved.

Set a breakpoint on `testIntFloatDoubleInt` and run the following commands.

``````(lldb) register read x0 -f d
x0 = 400111
(lldb) register read s0 -f f
s0 = 400.222
(lldb) register read d1 -f f
d1 = 400.333
(lldb) register read x1 -f d
x1 = 400444``````

Again, Swift puts the 1st and 2nd `Int` values in `x0` and `x1` respectively.

Also notice how the first floating point value is in `s0`, but the 2nd one is in `d1`. This is beacause the `s<n>`, and `d<n>` registers actually just represent a smaller section of a `v<n>` register. Once Swift uses `s0` for one argument it means that `d0`, and `v0` are used up too. It must store the next floaing point argument in `s1` / `d1`, etc.

## Summary

I hope this article helped explain how Swift stores its floating point arguments on ARM64! If you’ve read my first article too you should now understand how Swift stores `Int`, `Bool`, `Float`, and `Double` types as arguments and return values.

`Int` and `Bool` values are stored in the `x<n>` registers, and floaing point values are stored in the `v<n>` registers (or the smaller variants if possible).

In my next article I’m planning to cover how Swift stores `Strings` on ARM64.

## Troubleshooting

• Make sure you use the approach desribed here to set breakpoints. Setting breakpoints using the UI or using `breakpoint set --name <func_name>` may not work because LLDB may not pause the function on the first instruction. If other instructions execute, there’s a chance that the register values may get overwritten.

• Make sure you are running this code on a real iOS device. If you run this code on the iOS simulator, Swift will likely be running on a x86 CPU which has a different calling convention.

• If something is still unclear or not working, I would be happy to help! Feel free to DM me on twitter or send me an email.