A minimal bare-metal kernel in Embedded Swift runs on QEMU

A blog post walks through a hobby experiment: writing a very basic kernel in Swift and running it in the QEMU machine emulator. The author says the goal is not to replace Linux or any other popular kernel, but to have fun and understand what it takes to run a program with no operating system underneath. The author made a similar experiment about ten years ago, called arOS. At first the kernel only prints a message through QEMU and then waits forever.

The project uses Embedded Swift, which the author describes as a subset of Swift for environments with no operating system and no standard library in the usual sense. The first Package.swift was small: swift-tools-version 6.4, an executable target, and the experimental "Embedded" feature, strict memory safety and warnings treated as errors. A plain print("Hello... world?") failed at first, because the installed Swift compiler did not ship the Embedded Swift standard library. The documentation says Embedded Swift is still experimental and not supported in public releases, so a development toolchain is needed. The author installed the main-snapshot toolchain with swiftly, and the program then ran on the host.

The real target is QEMU's virt machine on an Apple Silicon computer, with the triple aarch64-none-none-elf: a 64-bit ARM machine with no OS and no C runtime. The CPU starts in a raw state, so a small assembly file (boot.S) defines the _start symbol, reads the CPU ID, parks every core except Core 0 in a wfe loop, sets the stack pointer from a __stack_top symbol, and calls the Swift function kernel_main. A toolset.json passes the compiler and linker options to Swift Package Manager: the Embedded and Volatile experimental features, no allocations, function sections, no stack protector, no standard libraries, static linking with lld, garbage collection of sections, orphan handling set to error, and a custom linker.ld script.

The first link attempt failed. The linker reported section type mismatches for .strtab and .shstrtab, plus undefined symbols putchar and memmove. The missing symbols are expected on a bare-metal target, since there is no C standard library. The section errors come from orphan handling: the linker will not guess where ELF metadata should go, so the linker script has to place the kept sections explicitly.

To fix the missing symbols, the author wrote both functions in Swift. putchar writes a byte to the PL011 UART data register, which is memory-mapped at 0x09000000 on the virt machine. This is a serial output driver, not a screen driver, and QEMU shows the bytes in the terminal when launched with -nographic. memmove is a simple loop that copies forwards or backwards depending on whether source and destination overlap. The author notes that this is where the project stops being ordinary application programming: instead of calling an OS API, the code implements the low-level functions the language runtime needs. Both functions, and kernel_main, are exported with the @c attribute. In an update dated 28 September 2026, the author says Max Desiatov pointed out that @c, which marks a global function as a C function implemented in Swift and formalizes @_cdecl, should be used instead of @_cdecl; the post and code were changed to match.

The linker script loads the kernel at 0x40080000, which the author says is where QEMU's virt machine loads an AArch64 kernel in this setup. It places boot code first, then read-only data, initialized data, uninitialized data (.bss) and a stack. The stack is 128 kB (0x20000), which the author calls unreasonable for a kernel that prints one sentence but gives Swift room to call functions. The script discards .comment, debug, .eh_frame and .swift_ sections and explicitly places .symtab, .strtab and .shstrtab, which clears the orphan errors.

Built with swift build --triple aarch64-none-none-elf --toolset toolset.json --build-system native and launched with qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -kernel ..., the kernel prints "Hello, Embedded Swift" with a smiley emoji and then loops forever. The author says it is not close to a useful kernel, but it is a real AArch64 ELF executable that starts from an assembly entry point, links without a standard library and runs Swift code on a manually configured stack.

The post says the project is intentionally unfinished and lists next steps that are not implemented yet: clear the .bss section at boot, hide the hardcoded QEMU UART register behind a small Swift abstraction, work out which allocation features Embedded Swift can support, investigate interrupts and timers, and eventually replace the infinite loop with something that can schedule work. The author may also try a framebuffer, a real keyboard driver and a minimal interactive environment.

A later section adds input. The PL011 UART has a receive register, and bit 4 of its flag register (RXFE, at 0x0900_0018) says whether the receive FIFO is empty. A readChar function loops until that bit clears and then reads the character. The author admits this polling is very inefficient and that a real kernel would eventually use interrupts, but it is simple enough for a first version. The kernel can then echo typed characters back, printing a "Swift kernel ready." banner and a "> " prompt, with handling for carriage return and line feed in kernel_main.

Key facts

  • The kernel is written in Embedded Swift for aarch64-none-none-elf, runs in QEMU's virt machine on Apple Silicon, and prints "Hello, Embedded Swift" over the PL011 UART at 0x09000000 before looping forever.
  • Embedded Swift needed a development toolchain (main-snapshot via swiftly), because the installed compiler lacked the Embedded Swift standard library.
  • The author had to supply putchar and memmove in Swift, and write a linker script (load address 0x40080000, 128 kB stack) that places ELF metadata sections explicitly to satisfy orphan handling.
  • A small boot.S parks every core except Core 0, sets the stack and calls kernel_main; a later section adds polling-based UART input that echoes characters.
  • The author calls the project intentionally unfinished: clearing .bss, a UART abstraction, allocation, interrupts, timers and scheduling are listed as not yet implemented.

Why it matters

This is a hobby experiment, not a product or a breakthrough, and the author says so. Its value is as a worked example of what Embedded Swift can do without an operating system: the language runs on a bare-metal ARM target once the author supplies a boot stub, a linker script and the few C-level functions (putchar, memmove) that the runtime expects. The author also frames it as a way to understand the machine underneath, and mentions a similar experiment with arOS about ten years ago.

Who it affects

Mainly Swift developers curious about low-level and bare-metal work, and anyone learning how a program starts with no OS: stack setup, a linker script, memory-mapped UART output. It does not affect users of any existing operating system; the author states the goal is not to replace Linux or any other popular kernel.

How to use it

The post is written as a step-by-step recipe. Install a Swift development toolchain (the author used swiftly install main-snapshot && swiftly use main-snapshot), set up a Swift package with the Embedded experimental feature, add a boot.S, a linker.ld and a toolset.json, and build with swift build --triple aarch64-none-none-elf --toolset toolset.json --build-system native. Then run it with qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -kernel .build/aarch64-none-none-elf/debug/kernel. The code uses the @c attribute rather than @_cdecl, following the update after Max Desiatov's comment.

How solid is it

The post is a first-person account with full code snippets and the actual linker error output, and the author reports the build and the QEMU run working, including the greeting and the echo example. The claims are about a personal experiment, not a benchmark or a study. The retelling covers the article up to the start of the echo loop in kernel_main.

Risks and caveats

Embedded Swift is still experimental and, per the documentation the author quotes, not supported in public Swift releases, so a development toolchain is required and details may change. The kernel is far from useful: it prints a message, loops forever, hardcodes QEMU's UART addresses and does not clear .bss. Input uses polling, which the author calls very inefficient. The stack is 128 kB, far more than a one-sentence kernel needs. The author says it was run in QEMU on an Apple Silicon machine, and the post says nothing about real hardware. Interrupts, timers, allocation and scheduling are listed as future work.

“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.”

— the post's author