---
title: "A minimal kernel in Swift, running in QEMU"
slug: a-minimal-kernel-in-swift-running-in-qemu
url: https://listedarticles.com/articles/a-minimal-kernel-in-swift-running-in-qemu
canonical_url: https://carette.xyz/posts/minimal_swift_kernel_on_qemu/
content_type: tutorial
language: en
published_at: 2026-09-28T00:00:00.000Z
updated_at: 2026-10-08T02:11:24.428Z
author: "Antonin"
authored_by: human
publisher: "A journey into a wild pointer"
publisher_url: https://carette.xyz/
topics: ["Systems Programming", "Programming", "Tutorials"]
license: all-rights-reserved
word_count: 1870
reading_minutes: 8
citation: "Antonin, A journey into a wild pointer. \"A minimal kernel in Swift, running in QEMU.\" 28 Sept 2026. https://carette.xyz/posts/minimal_swift_kernel_on_qemu/ (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# A minimal kernel in Swift, running in QEMU

> Antonin walks through writing a tiny kernel in Embedded Swift that boots in QEMU: the Swift package and compiler setup, bridging Swift to assembly with the @c attribute, providing the missing runtime functions, describing the memory layout with a linker script, printing to the serial console, and finally echoing typed characters back as a simple prompt.

# A minimal kernel in Swift, running in QEMU

· 10 min read

**Update (28th of September 2026)**

Max Desiatov [told me](https://mastodon.social/@maxd/117348871405186390) that [the `@c` swift attribute](https://www.swift.org/blog/swift-6.3-released/#language-and-standard-library) should work, instead of declaring the functions using `@_cdecl`. The `@c` attribute marks a global function as a C function implemented in Swift, which formalizes the `@_cdecl` attribute. A link to the proposal is available [here](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0495-cdecl.md).
I did update the blog post and the code accordingly to that comment. Thanks Max!

I started a small experiment: writing a very basic kernel in Swift, and running on [QEMU](https://www.qemu.org).

The goal is obviously not to replace Linux or any other popular kernel, but just to have fun and understand what is required to make a program run without an operating system underneath it.

Actually I made a similar experiment years before with [arOS](https://carette.xyz/posts/im_writing_my_own_os/) 10 years ago.

For the moment, the kernel does only one thing: it prints a message through QEMU and then waits forever, which is enough for a first deep dive.

## Swift Embedded

The project uses [Embedded Swift](https://docs.swift.org/latest/documentation/embeddedswift/), which is a subset of Swift designed for environments where there is no operating system and no standard library available (in the usual sense).

My first `Package.swift` was very small:

```
// swift-tools-version: 6.4
import PackageDescription
let package = Package(
    name: "swift-kernel",
    platforms: [.macOS(.v14)],
    targets: [
        .executableTarget(
            name: "swift_kernel",
            swiftSettings: [
                .enableExperimentalFeature("Embedded"),
                .strictMemorySafety(),
                .treatAllWarnings(as: .error),
            ],
        )
    ]
)
```
And the first Swift file was almost insulting in its simplicity:

```
// Sources/swift_kernel/test.swift
print("Hello... world?")
```
I tried to run it:

```
> swift run
<unknown>:0: error: unable to load standard library for target 'arm64-apple-macosx27.0.0'
```
Oops. It seems like my current Swift compiler does not yet ship with the Embedded Swift standard library.

As I missed in [the documentation](https://docs.swift.org/latest/documentation/embeddedswift/installembeddedswift):

Since Embedded Swift is still experimental and not yet supported in public Swift releases, you’ll need to use a development toolchain.

So, let’s install the development toolchain and test again:

```
> swiftly install main-snapshot && swiftly use main-snapshot
> swift run
[...]
Hello... world?
```
Success!

At this point I had confirmed that Embedded Swift could compile and run a small program.

The next step was to compile for a machine with no operating system at all.

## QEMU setup

For this project I will test on a machine emulator called [QEMU](https://www.qemu.org/), which is one of the most famous machine emulators for hackers.

I am using QEMU’s `virt` machine on an Apple Silicon machine. The target is therefore `aarch64-none-none-elf`.

This target, designed [as a triple](https://wiki.osdev.org/Target_Triplet), describes a 64-bit ARM machine with no operating system and no environment provided by a C runtime.

When QEMU starts, the CPU is in a raw state.

Before it can safely execute Swift code, I need to **configure a stack** and **decide where the program starts**.

I will need a minimal assembly file that:

1. defines [the `_start` symbol](http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html) ,
2. sets up a stack for the first core,
3. jumps to our Swift entry function, `kernel_main` ,
4. and waits forever to keep the main running.

## Compiler setup

I have to tell the compiler that this is not a normal executable: the compiler must avoid linking the usual standard libraries, and the linker must use our linker script instead of a platform-specific one.

The project uses the following `toolset.json`, which will be passed to our Swift Package Manager (SPM):

```
{
    "schemaVersion": "1.0",
    "swiftCompiler": {
        "extraCLIOptions": [
            "-enable-experimental-feature", "Embedded",
            "-enable-experimental-feature", "Volatile",
            "-Xfrontend", "-no-allocations",
            "-Xfrontend", "-function-sections",
            "-Xfrontend", "-disable-stack-protector",
            "-Xlinker-driver", "-nostdlib",
            "-Xlinker-driver", "-fuse-ld=lld"
        ]
    },
    "linker": {
        "extraCLIOptions": [
            "-nostdlib",
            "-static",
            "--gc-sections",
            "--orphan-handling=error",
            "-T", "linker.ld",
            "-Map", ".build/kernel.map"
        ]
    }
}
```
Now, let’s build the project with the new target, the toolset, and Swift’s native build system:

```
> swift build --triple aarch64-none-none-elf --toolset toolset.json --build-system native
```
Unfortunately, the first linker attempt fails:

```
ld.lld: error: section type mismatch for .strtab
>>> <internal>:(.strtab): SHT_STRTAB
>>> output section .nonalloc: SHT_SYMTAB
ld.lld: error: section type mismatch for .shstrtab
>>> <internal>:(.shstrtab): SHT_STRTAB
>>> output section .nonalloc: SHT_PROGBITS
ld.lld: error: undefined symbol: putchar
ld.lld: error: undefined symbol: memmove
```
Hmm. What is going on here?

Because I am compiling for a bare-metal target (reminder: `none-none-elf`), there is no underlying operating system and no C standard library. Therefore there is no `putchar`, and there is no `memmove` either…

The section errors have a different origin.

With orphan handling enabled, the linker does not want to guess where ELF metadata should go. Therefore, the linker script must explicitly place the sections that I am keeping.

## Bridging Swift to assembly

I can’t use a normal Swift file with a `main` function. Instead, I expose a designated kernel entry function with `@c`, so that the assembly file can call it by its C-compatible name.

```
@c
func kernel_main() {
    print("Hello, Embedded Swift 😊")
    while true {} // Keep the program running
}
```
The assembly entry point lives in `Sources/boot/boot.S`:

```
.section .text.boot
.global _start
_start:
    /* Read the CPU ID. We only want Core 0 to run our Swift code. */
    mrs x1, mpidr_el1
    and x1, x1, #3
    cbz x1, 2f
    /* Park secondary cores in an infinite wait loop. */
1:  wfe
    b 1b
    /* Set up the stack for Core 0. */
2:  ldr x0, =__stack_top
    mov sp, x0
    /* Jump to our Swift entry point. */
    bl kernel_main
    /* Halt if we ever return. */
    b 1b
```
QEMU can start multiple virtual cores but, for now, only the first one should execute our kernel.

The other cores wait (using `wfe`), waiting for an event that it will not send yet.

## Providing the missing functions

The `print` implementation used by Embedded Swift needs a way to write at least one character.

On QEMU’s `virt` machine, the [ARMs PL011 UART](https://krinkinmu.github.io/2020/11/29/PL011.html) is mapped at `0x09000000`.

So `putchar` can write directly to that memory-mapped register:

```
// QEMU's PL011 UART register
let QEMU_UART_REGISTER: UInt = 0x0900_0000
@c
@unsafe func putchar(_ c: CInt) -> CInt {
    let uart = unsafe UnsafeMutablePointer<UInt8>(bitPattern: QEMU_UART_REGISTER)!
    unsafe uart.pointee = UInt8(c)
    return c
}
```
**This is not a screen driver** but a serial output driver.

QEMU will then display the bytes received **by the emulated UART** in the terminal (because I will execute it with the `-nographic` option).

The other missing function is `memmove`.

Swift’s low-level code needs basic memory operations, even when I am not using a normal standard library.
For this first experiment, I provide a small implementation that copies forwards or backwards, depending on whether the source and destination overlap:

```
@c
@unsafe func memmove(
    _ dest: UnsafeMutableRawPointer,
    _ src: UnsafeRawPointer,
    _ n: Int
) -> UnsafeMutableRawPointer {
    let d = unsafe dest.assumingMemoryBound(to: UInt8.self)
    let s = unsafe src.assumingMemoryBound(to: UInt8.self)
    if unsafe d < s {
        for i in 0..<n { unsafe d[i] = s[i] }
    } else if unsafe d > s {
        for i in (0..<n).reversed() { unsafe d[i] = s[i] }
    }
    return unsafe dest
}
```
This is the first point where the project stops being ordinary application programming.

Here, I am no longer calling an operating-system API to print a string, but I am implementing the lower-level functions that the language runtime needs in order to exist.

## Describing the memory layout

Now that I did implement the Swift code, the linker needs to know where the kernel is loaded and where each part of the executable belongs.

This is the role of `linker.ld`.

QEMU’s `virt` machine loads an AArch64 kernel at `0x40080000` in this setup:

```
ENTRY(_start)
. = 0x40080000;
```
The linker script then places the boot code first, followed by read-only data, initialized data, uninitialized data, and a stack:

```
.text : {
    KEEP(*(.text.boot))
    *(.text*)
}
.rodata : ALIGN(8) {
    *(.rodata*)
}
.data : ALIGN(8) {
    *(.data*)
}
.bss (NOLOAD) : ALIGN(16) {
    __bss_start = .;
    *(.bss*)
    *(COMMON)
    __bss_end = .;
}
.stack (NOLOAD) : ALIGN(16) {
    . += 0x20000;
    __stack_top = .;
}
```
The assembly code uses the `__stack_top` symbol to initialize the stack pointer.

The stack is currently *128 kB*, which is an **unreasonable** amount for a kernel that only prints one sentence, but it gives Swift some room to call functions while this project is still experimental.

Finally, the script explicitly places the ELF metadata sections. This avoids the orphan-section errors from the first linker attempt:

```
/DISCARD/ : {
    *(.comment)
    *(.debug*)
    *(.eh_frame*)
    *(.swift_*)
}
.symtab : { *(.symtab) }
.strtab : { *(.strtab) }
.shstrtab : { *(.shstrtab) }
```
## Hello, Embedded Swift

Now, let’s build it

```
> swift build --triple aarch64-none-none-elf --toolset toolset.json --build-system native
```
, launched with QEMU:

```
> qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -kernel .build/aarch64-none-none-elf/debug/kernel
```
and…

```
Hello, Embedded Swift 😊
```
Then it keeps running forever, because the kernel is trapped in its infinite loop.

This is not close to a useful kernel, but it is a real AArch64 ELF executable.

It starts by an assembly entry point, links without a standard library, and it calls Swift code on top of a manually configured stack.

For a first hello world, this is already enough.

## What comes next?

The project is intentionally unfinished. The next steps are not implemented yet, but they provide a direction for the rest:

1. clear the `.bss` section during boot,
2. hide the UART register behind a small Swift abstraction (I hardcoded QEMU ones for simplicity),
3. understand which allocation features Embedded Swift can support,
4. investigate interrupts and timers,
5. and eventually replace this infinite loop with something that can schedule actual work.

At some point, I may also want to experiment with a framebuffer, a real keyboard driver, and a minimal interactive environment. But before building a user interface, I need to understand the machine underneath it.

## Responding to a human

For now, the machine says hello. Printing a message is nice, but it is still a one-way conversation.

The PL011 UART also has a receive register.

Before reading from it, I need to check its flag register.
Bit 4, called `RXFE`, tells us whether the receive FIFO is empty.

If it is empty, we wait.

```
let QEMU_UART_FLAG_REGISTER: UInt = 0x0900_0018
@unsafe func readChar() -> UInt8 {
    let uart = unsafe UnsafeMutablePointer<UInt8>(bitPattern: QEMU_UART_REGISTER)!
    let flags = unsafe UnsafeMutablePointer<UInt8>(bitPattern: QEMU_UART_FLAG_REGISTER)!
    // The RXFE bit is set while the receive FIFO is empty.
    while unsafe (flags.pointee & 0x10) != 0 {}
    return unsafe uart.pointee
}
```
This is **polling**. The CPU repeatedly checks the UART until a character arrives and it is a very **inefficient** way to do something like that.
A real kernel would eventually use interrupts, but it is simple enough for a first implementation.

The kernel can now echo characters back to the terminal:

```
@c
func kernel_main() {
    print("Swift kernel ready.")
    let _ = unsafe putchar(62)
    let _ = unsafe putchar(32)
    var ignoreLineFeed = false
    while true {
        let character = unsafe readChar()
        if character == 13 {
            let _ = unsafe putchar(10)
            let _ = unsafe putchar(62)
            let _ = unsafe putchar(32)
            ignoreLineFeed = true
        } else if character == 10 {
            if !ignoreLineFeed {
                let _ = unsafe putchar(10)
                let _ = unsafe putchar(62)
                let _ = unsafe putchar(32)
            }
            ignoreLineFeed = false
        } else {
            let _ = unsafe putchar(CInt(character))
            ignoreLineFeed = false
        }
    }
}
```
The numbers `62` and `32` are simply the ASCII codes for `>` and a space.
I could create a small string-writing abstraction, but for the moment I prefer to keep the mechanism visible.

Running the same QEMU command now gives us a prompt:

```
Swift kernel ready.
> hello kernel
hello kernel
>
```
This is still a very small feature.

There is no line editor, no backspace handling, no command parser… and the CPU is busy waiting for every character because it uses polling instead of interrupts.

For a program that started with `print("Hello... world?")`, this feels like a good place to stop and start writing a TODO list to improve all of that.

The full code is available on my [private git space](https://carette.xyz/posts/free-of-github/): [https://git.carette.xyz/k0pernicus/swift-kernel](https://git.carette.xyz/k0pernicus/swift-kernel).

Have fun!
