SwiftOS — a from-scratch kernel and userland in Embedded Swift

I've always wanted to really understand how an operating system works underneath — not the textbook block diagram, but the actual machinery: how a core comes up from reset, how address spaces get isolated, how a binary becomes a running process. I decided the most honest way to learn it was to build one. And I wanted to find out how far Embedded Swift could go if you took it all the way down to the metal.

The result is SwiftOS: a small but real operating system written almost entirely in Embedded Swift for 64-bit ARM. It boots on QEMU virt, and — the part I'm still a little amazed by — on a real Hetzner Cloud ARM VM, where it's currently serving its own website.

The Embedded Swift side (the part I think this forum will care about)

  • Toolchain: the swift.org 6.3.2-RELEASE toolchain, which ships an embedded stdlib for the aarch64-none-none-elf target — exactly what I build against.
  • Build flags, roughly:
    -target aarch64-none-none-elf -enable-experimental-feature Embedded -wmo -parse-as-library -Osize -Xllvm -mattr=+strict-align,-neon -Xfrontend -function-sections -import-objc-header kernel/arch/aarch64/io.h
  • Freestanding: no Foundation, no full stdlib. The kernel is value types + Unsafe* pointers at the lowest level, ~Copyable structs with deinit for resource ownership, and classes used sparingly and only after the heap is up (ARC isn't free).
  • Volatile MMIO goes through a tiny C bridge imported via -import-objc-header (io.h); boot and exception vectors are assembly. Almost everything above that is Swift.
  • A few things that bit me and might save someone else time:
    • You need ld.lld, not GNU ld — Embedded Swift's protected empty Array/String singletons land in a section GNU ld mishandles (and it clears a spurious RWX-segment warning too).
    • String/Unicode pulls in libswiftUnicodeDataTables.a from the toolchain — you have to link it explicitly.
    • print() and String output lower to putchar, so the first thing the userland needs is a one-line putchar shim over the UART/syscall.
    • Embedded heap allocations want 16-byte alignment — worth knowing when you're writing the first allocator over sbrk.

What the OS actually does

  • Real MMU isolation — one address space per process, capability-based handles instead of ambient authority.
  • A native userland written in Swift — its own coreutils (ls, ps, top, a calculator/REPL, …), console-login, and sshd, all on our own svc syscall ABI.
  • An in-kernel TCP/IP stack — DHCP, TCP, UDP, DNS, HTTP, and TLS.
  • A three-tier filesystem — an immutable, signed, read-only base image; a RAM tmpfs scratch tier; and a persistent writable /data tier with honest fsync/fdatasync (durable enough to back SQLite).
  • SMP — it schedules across multiple cores (tested at -smp 4), with cross-CPU TLB shootdown and spinlock-protected kernel state.
  • No Linux ABI, static linking only, no dynamic loader. Our own POSIX-like syscall surface; everything gets recompiled. There's a newlib port so "real" C/C++ software can link.

Getting real software to run

  • nginx 1.30.2, statically linked against newlib + OpenSSL, serving HTTPS. This is what's serving the site above.
  • Node.js 24.16.0 running on top of V8 in --v8-lite-mode (jitless — which neatly sidesteps W^X on a kernel that doesn't hand out RWX pages). node --version and node -e "console.log(6*7)"42 both work. It was a long road: a full aarch64-elf GCC 16.1 toolchain with libstdc++ built from source to satisfy V8's C++ runtime. (npm packaging is the next step.)

Running on real hardware

QEMU is forgiving; real hardware is not. Bringing SwiftOS up on a bare Hetzner Cloud ARM VM meant going from device tree + virtio-mmio + GICv2 to ACPI firmware tables (RSDP→MADT/MCFG/SPCR/GTDT, no DT fallback), GICv3, PCIe ECAM enumeration + virtio-pci, and DHCP over virtio-net-pci — then booting headless straight to sshd. That surfaced four bugs that only show up on real hardware (my favorite: PCIe ECAM and the 64-bit virtio window have to be mirrored into every process's page tables, or the kernel can touch the NIC but a userland process like sshd faults the moment it does TX/RX). All of it is gated by a regression test that boots the exact Hetzner topology.

Caveats, because this audience will (rightly) ask

It's minimal and there are rough edges. It started single-core; SMP is recent. Some things stay in C/asm on purpose — third-party code (busybox, newlib), the MMIO/boot/syscall bridges, and a couple of measured toolchain-limitation cases — but the design rule is Swift by default, C only with a documented reason.

I learned an enormous amount doing this — about ARM, virtual memory, drivers, the network stack, and how much "obvious" behavior we take for granted in mature kernels. I'd love feedback from people who actually know this domain: anything I've clearly got wrong, things you'd design differently, or Embedded Swift corners I should be using better. And if there's interest, I'm happy to write up specific parts in more depth — the allocator, the capability model, the SMP bring-up, or the V8-on-a-hobby-kernel saga.

55 Likes

This is absolutely incredible.

1 Like

Absolutely incredible :partying_face::partying_face:

If Swift had inline assembly, it would have been more Swift :sleepy_face:

3 Likes

I’m very impressed! At first I thought this would be another little toy kernel, but it totally isn’t.

What are the system requirement to run this on real hardware? Could it run on a Raspberry Pi?

1 Like

Thank you very much!

Agreed - that’s the one spot where it isn’t pure Swift: since Embedded Swift can’t emit privileged instructions, system-register access, barriers and MMIO go through a tiny C shim (and boot/vectors/context-switch are .S files). Native inline asm would let me fold most of that C back into Swift, so it really would be more Swift.

1 Like

Honestly, I haven’t tried it on a Pi because I don’t own one. In theory the core should run on it. The open question is the I/O layer — the Pi’s interrupt controller and real device drivers instead of virtio. If you’ve got a Pi and feel like experimenting I’d be genuinely grateful to hear what happens - I’m very curious.

Easiest first step is to watch it boot in QEMU:

git clone https://github.com/asaptf/swift-os

cd swift-os
make newlib busybox
make build base-image build/virt.dtb
make run

(You’ll need the Swift toolchain, qemu-system-aarch64, and an aarch64-elf GCC/binutils + lld.)

1 Like

Have you seen this package: M on X: "【新作ライブラリ】SwiftでAsmを利用して関数を実装することのできるライブラリ Cとかのinline asmのノリでSwiftコード中にasmを埋め込むことがきます. https://t.co/lvZdk2fCKX" / X

He has some other interesting ones as well!

Edit: direct link to package GitHub - p-x9/swift-asm-macro: A Swift macro that lets you write inline assembly and implement a Swift function. · GitHub

3 Likes

This is awesome. I wish it were built in!

1 Like

It also surfaces syntax errors in the assembly, it seems pretty capable already!

Genuinely clever, and +1 it should be built in. Catch for SwiftOS: it emits into a Mach-O __TEXT,__text section (Apple-only), but the kernel’s freestanding aarch64-none-elf with its own linker script, so it won’t drop in as-is. The C shim stays simpler for now.

2 Likes

For next days I'm planning to work on this: swift-os/docs/SWIFTCUBE_DESIGN.md at main · asaptf/swift-os · GitHub

2 Likes

One thing I hate about Unix is how it's originally a telegram/telephone network control system, so it has first-class unprintable control characters built right into the main character set you must use for everything. You can literally still type these control characters in macOS terminal and a lot of them still work: control-H does backspace, control-L does form feed (wipes the terminal), etc. And for awhile control-G would even still "ring the bell" (OS would beep).

I hope you're making a clean break from the telegram!

Cool project man. Definitely gonna check it out!

Your beef is with ASCII, not UNIX.

4 Likes

Yeah…

My beef is with UNIX for using ASCII and its descendants. It's so ingrained into the system that you can never purge it.

But someone creating a new system might have that opportunity.

Unicode subsumed ASCII. On purpose. Your actual beef is with operating systems whose terminal apps maintain compatibility with specific ascii characters that have historically been treated as control codes.

Unless one has a truly stupendous number of fonts, there will always be characters that are not printable.

I believe that interactive terminal apps (like nano or htop) are impossible without control characters. A hypothetical new OS that don't have control characters probably won't have interactive terminal apps.

I think maybe we don’t need to derail this thread with a random discussion about text encoding; let’s just end that here.

In fact, maybe I ought to split that all of that out into a new thread.

9 Likes

A bring-up surprise worth sharing: OpenSSL wouldn't seed its DRBG on the Hetzner VM — no virtio-rng, and Neoverse-N1 has no RNDR, so getentropy() had nothing to return. I fixed it with a small in-kernel jitter-entropy source behind SYS_RANDOM. The offer still stands — happy to write up any one piece in depth (the allocator, the capability model, or the SMP bring-up) if it'd be interesting.

2 Likes