Assembler for Swift developers

Ever wondered what really happens under the hood when you build that shiny Swift app? Here’s a confession: I used to treat assembly like dark magic—until I decided to embrace it. In this first installment of our three-part series, we’ll cover the bare essentials: what registers are, how to set up an Xcode command-line tool for assembly, and how to write a minimalist “Hello, Assembly!” program that prints to your terminal.

Why Bother with Assembly?

Before you scroll past, remember: understanding assembly isn’t about writing your entire app in it (though you could). It’s about gaining a deeper appreciation for how high-level languages like Swift and Objective-C map down to CPU instructions, how data flows, and—most importantly—how to debug tricky performance issues or low-level crashes. Plus, it’s undeniably fun to feel like the wizard pulling strings behind the curtain.

Registers: Your CPU’s Pocket Organizers

Imagine your CPU as a master chef with a very small countertop cluttered only with a handful of bowls. Those bowls are registers—the fastest storage your code can access. On Apple Silicon (ARM64), here’s the quick breakdown:

  • x0–x7: Argument registers (pass your parameters here) and return values
  • x8: Indirect result register (rarely used)
  • x9–x15: Temporary registers (scratch space)
  • x16–x17: Syscall registers (load your system-call number here)
  • x18: Platform register (preserved across calls)
  • x19–x28: Callee-saved (keep these intact if you call other functions)
  • sp (x31): Stack pointer (your local “scratchpad” memory)
  • pc: Program counter (points at the next instruction)

When we invoke a system call (like writing to the console), we load arguments into x0x1, etc., put the syscall number in x16, then issue svc 0—that’s us ringing the doorbell of the kernel and saying “Hey, can you do this for me?”

Setting Up an Xcode Command-Line Tool

Let’s get hands-on. We’ll create a minimal Xcode project that assembles our .s file and links it into a runnable binary.

  1. Launch Xcode and select File → New → Project…
  2. Under macOS, choose Command Line Tool, then Next.
  3. Name it HelloAsm, set Language to C (we’ll swap it out), and click Create.
  4. In the Project Navigator, delete main.c.
  5. Add a new empty file: File → New → File… → Other → Empty, name it main.s, and click Create.
  6. Select your project target, open Build Phases, and confirm main.s is listed under Compile Sources.

That’s it—Xcode will now treat main.s as the entry point for our assembly.

Writing Your First “Hello, Assembly!” Program

.section    __TEXT,__text
.global     _main

_main:
    // write(fd: Int, buf: UnsafePointer<UInt8>, count: Int)
    mov         x0, #0x1                  // stdout file descriptor
    adrp        x1, msg@PAGE              // page address of msg
    add         x1, x1, msg@PAGEOFF       // offset to msg
    mov         x2, #0x11                 // length of our string
    mov         x16, #0x4                 // syscall number for write
    svc         0

    // exit(status: Int)
    mov         x0, #0x0                  // status = 0
    mov         x16, #0x1                 // syscall number for exit
    svc         #0x0

    .section    __TEXT,__cstring
msg:
    .asciz      "Hello, Assembly!\n"

Line by line:

  • .section __TEXT,__text & .global _main
    Tell the assembler this is executable code and _main is our entry point.
  • mov x0, 1
    Put 1 (stdout) into register x0—our first syscall argument.
  • adrp/add
    Load the 64-bit address of msg into x1 (split across page boundaries).
  • mov x2, #0x11
    Message length (including the newline and null terminator).
  • mov x16, #0x4
    Syscall ID for write on macOS.
  • svc 0
    Switch to kernel mode—execute the syscall.
  • Repeat for exit.

By the way, we are using hexadecimal numbers, I hope you are familiar with conversion from decimal to binary and hexadecimal :)

Building and Running

Build and run in Xcode, you should see the output:

Hello, Assembly!

If you don’t, double-check that:

  • main.s is in Compile Sources.
  • No stray C files remain (they can override _main).
  • Your architecture matches your hardware (Intel vs. Apple Silicon).

Parting Thoughts

Congratulations! You’ve just spoken directly to your CPU in its native tongue. In our next installment, we’ll dive deeper into pointers, function calls, and the magic (and dangers) of the heap and stack. Until then, keep poking around in registers—you’ll be surprised how much clearer your debugging sessions become when you can see exactly where your data lives.

Artur Gruchała

Artur Gruchała

I started learning iOS development when Swift was introduced. Since then I've tried Xamarin, Flutter, and React Native. Nothing is better than native code:)
Poland