srk1nn.blog

about iOS development

Published on 22 Jan 2024

Calling Assembly from Swift

Introduction

I’ve recently read a book called Learn to Program with Assembly by Jonathan Bartlett. The book introduces assembly language as well as syscalls, stack, memory, etc. At the end of the book author shows how modern programming language features, such as object-oriented programming, work at the assembly level. I highly recommend this book for all developers.

So as I’m an iOS developer, I wondered – can we call assembly from Swift? Let’s try!

Disclaimer

⚠️ Don’t do this in production code, since assembly has poor legibility and isn't really maintainable. Also, remember that assembly is not cross-platform, so I will write an app for macOS to use x86_64 platform.

Writing assembly

For simplicity let’s write an app for adding integers. Sum calculation (the main logic for our app) will be written in assembly.

.globl _sum_numbers

.text

# rdi register contains x
# rsi register contains y
_sumnumbers:
    mov %rdi, %rax
    add %rsi, %rax
    ret

Here we define the global sum_numbers function to use it outside. And place the function body in the text segment of our program. In my experience, you need to start a function name with an underscore, but Swift won't take it into account. As I understand, it's so because of historical reasons.

According to the calling convention, the function takes arguments in registers. The first argument in rdi, second – rsi. On the first line, we move the first argument from rdi to rax register. Then add rsi to rax and store the result in rax, since it is used to return the function’s result. At the end, we call return.

Calling assembly

Now let’s go to Swift code and try to call sum_numbers somehow. We know, that function already exists in our binary. All we have to do is declare the function for the compiler. Swift doesn’t allow that, but Objective-C does!

We need to create a header file and declare sum_numbers function. Next, create a bridging header to transfer the header file to Swift.

// sum.h
int64_t sum_numbers(int64_t, int64_t);

// ConsoleApp-Bridging-Header.h
#import "sum.h"

We are done! Now we can call our function, written on assembly, from Swift.

print(sum_numbers(10, 100))  // 110
print(sum_numbers(-100, 20)) // -80