Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Running Swift without an OS

Running Swift without an OS

Bare Metal Swift: Build Your Own OS

Avatar for Kishikawa Katsumi

Kishikawa Katsumi

April 14, 2026

More Decks by Kishikawa Katsumi

Other Decks in Programming

Transcript

  1. r (MPCBMBOETUBUJDWBSJBCMFJOJUJBMJ[BUJPO r 4UBOEBSEMJCSBSZ SVOUJNF BOEBMMPUIFSMJCSBSJFTMPBEFE r 4UBDLBMMPDBUFE r 5ISFBETDSFBUFE

    8IBUTSVOOJOHCFIJOEUIJT import SwiftUI // We start here @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() } } } 8IBU)BQQFOT#FIJOE!NBJO
  2. r (MPCBMBOETUBUJDWBSJBCMFJOJUJBMJ[BUJPO r 4UBOEBSEMJCSBSZ SVOUJNF BOEBMMPUIFSMJCSBSJFTMPBEFE r 4UBDLBMMPDBUFE r 5ISFBETDSFBUFE

    8IBUTSVOOJOHCFIJOEUIJT import SwiftUI // We start here @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() } } } 8IBU)BQQFOT#FIJOE!NBJO
  3. ᶃ (FUUJOHUPNBJO  r 7FDUPSUBCMF NFNPSZJOJUJBMJ[BUJPO r #VJMEJOHUIFGPVOEBUJPOUPDBMMZPVS fi STUGVODUJPO

    ᶄ "GUFSNBJO  r /P3VO-PPQ OPUISFBET r :PVTUSVDUVSFUIFBQQMJDBUJPOTFYFDVUJPOZPVSTFMG 8IBU8FMM$PWFS #VJME:PVS0XO04
  4. ᶃ (FUUJOHUPNBJO  r 7FDUPSUBCMF NFNPSZJOJUJBMJ[BUJPO r #VJMEJOHUIFGPVOEBUJPOUPDBMMZPVS fi STUGVODUJPO

    ᶄ "GUFSNBJO  r /P3VO-PPQ OPUISFBET r :PVTUSVDUVSFUIFBQQMJDBUJPOTFYFDVUJPOZPVSTFMG 8IBU8FMM$PWFS #VJME:PVS0XO04
  5. ᶃ (FUUJOHUPNBJO  r 7FDUPSUBCMF NFNPSZJOJUJBMJ[BUJPO r #VJMEJOHUIFGPVOEBUJPOUPDBMMZPVS fi STUGVODUJPO

    ᶄ "GUFSNBJO  r /P3VO-PPQ OPUISFBET r :PVTUSVDUVSFUIFBQQMJDBUJPOTFYFDVUJPOZPVSTFMG 8IBU8FMM$PWFS #VJME:PVS0XO04
  6. 0x1000_0000 Flash Code, Boot2, Vector Table 0x2000_0000 RAM Variables, Stack

    0x4000_0000 Peripherals 0xD000_0000 SIO 0xE000_0000 System UART Serial I2C Display PWM Sound, LED GPIO LED on/off SysTick Timer .FNPSZ.BQQFE*0 "MMDPOUSPMMFEUISPVHINFNPSZ >_ OLED
  7. 0x1000_0000 Flash Code, Boot2, Vector Table 0x2000_0000 RAM Variables, Stack

    0x4000_0000 Peripherals 0xD000_0000 SIO 0xE000_0000 System UART Serial I2C Display PWM Sound, LED GPIO LED on/off SysTick Timer .FNPSZ.BQQFE*0 "MMDPOUSPMMFEUISPVHINFNPSZ >_ OLED CPU
  8. .FNPSZ.BQQFE*0 4BNFNFDIBOJTNPOGVMM04 BMMEFWJDFJTKVTUBOBEESFTT iOS / macOS Your App UIKit /

    SwiftUI / Foundation macOS / iOS Kernel Device Drivers Memory-Mapped I/O Bare Metal Your App No frameworks No OS No drivers Memory-Mapped I/O Display Network Storage Audio Sensors
  9. #PPU$PEFBU'JYFE"EESFTTFT 1MBDJOH$PEF8IFSF)BSEXBSF&YQFDUT*U 30.#PPUMPBEFS )BSEXBSFCVJMUJO #PPU 'MBTIDPO fi HVSBUJPO 7FDUPS5BCMF BMJTUPGBEESFTTFTUIF$16SFBETBUCPPU

    3FTFU)BOEMFS .FNPSZJOJUJBMJ[BUJPO "QQMJDBUJPONBJO 4XJGUXPSMECFHJOTIFSF Flash (ROM) 0x1000_0000 .boot2 0x1000_0100 .vector .text ...
  10. )PX4XJGU1MBDFT$PEFBU'JYFE"EESFTTFT !TFDUJPO !VTFE BOE!D @section(".boot2") // ← place at fixed

    address @used // ← don't optimize away let boot2: (...) = (0xB3, 0x21, ...) @section(".vector") // ← place at fixed address @used // ← don't optimize away let vectorTable: ( UInt32, // [0] Initial stack pointer @convention(c) () -> Void, // [1] Reset handler ... ) = ( 0x2004_0000, // Top of RAM resetHandler, // First function to call ... )
  11. )PX4XJGU1MBDFT$PEFBU'JYFE"EESFTTFT !TFDUJPO !VTFE BOE!D Main.swift Board.swift Startup.swift Source Files Compiler

    swiftc .o Main.o Board.o Startup.o Object Files (addresses not yet assigned) Linker ld memmap.ld Linker Script 01 10 .boot2 .text code + vector table .data .bss Binary (firmware.uf2) (addresses assigned)
  12. )PX4XJGU1MBDFT$PEFBU'JYFE"EESFTTFT !TFDUJPO !VTFE BOE!D @section(".boot2") // ← place at fixed

    address @used // ← don't optimize away let boot2: (...) = (0xB3, 0x21, ...) @section(".vector") // ← place at fixed address @used // ← don't optimize away let vectorTable: ( UInt32, // [0] Initial stack pointer @convention(c) () -> Void, // [1] Reset handler ... ) = ( 0x2004_0000, // Top of RAM resetHandler, // First function to call ... )
  13. )PX4XJGU1MBDFT$PEFBU'JYFE"EESFTTFT !TFDUJPO !VTFE BOE!D @section(".boot2") // ← place at fixed

    address @used // ← don't optimize away let boot2: (...) = (0xB3, 0x21, ...) @section(".vector") // ← place at fixed address @used // ← don't optimize away let vectorTable: ( UInt32, // [0] Initial stack pointer @convention(c) () -> Void, // [1] Reset handler ... ) = ( 0x2004_0000, // Top of RAM resetHandler, // First function to call ... )
  14. )PX4XJGU1MBDFT$PEFBU'JYFE"EESFTTFT !TFDUJPO !VTFE BOE!D let vectorTable: ( UInt32, // [0]

    Initial stack pointer @convention(c) () -> Void, // [1] Reset handler ... ) = ( 0x2004_0000, // Top of RAM resetHandler, // First function to call ... ) @c // ← C calling convention func resetHandler() { ... }
  15. MEMORY { ROM(rx) : ORIGIN = 0x10000000, LENGTH = 2048k

    RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k } SECTIONS { .boot2 : { KEEP(*(.boot2)) } > ROM .text : { *(.vector) *(.text*) } > ROM ... } -JOLFS4DSJQU 8IFSF4FDUJPOT(P
  16. MEMORY { ROM(rx) : ORIGIN = 0x10000000, LENGTH = 2048k

    RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k } SECTIONS { .boot2 : { KEEP(*(.boot2)) } > ROM .text : { *(.vector) *(.text*) } > ROM ... } -JOLFS4DSJQU 8IFSF4FDUJPOT(P Flash (ROM) RAM
  17. MEMORY { ROM(rx) : ORIGIN = 0x10000000, LENGTH = 2048k

    RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k } SECTIONS { .boot2 : { KEEP(*(.boot2)) } > ROM .text : { *(.vector) *(.text*) } > ROM ... } -JOLFS4DSJQU 8IFSF4FDUJPOT(P Flash (ROM) RAM @section(".boot2") @section(".vector")
  18. 8IBUUIF7FDUPS5BCMF4FUT6Q 4UBDL1PJOUFS3FTFU)BOEMFS Flash (ROM) Boot2 Vector Table [0] Stack Pointer

    [1] Reset Handler .text (code) .data (initial values) ... RAM .data .bss (free) Stack
  19. 8IBUUIF7FDUPS5BCMF4FUT6Q 4UBDL1PJOUFS3FTFU)BOEMFS Flash (ROM) Boot2 Vector Table [0] Stack Pointer

    [1] Reset Handler .text (code) .data (initial values) ... RAM .data .bss (free) Stack <>4FUVQUIFTUBDL
  20. 8IBUUIF7FDUPS5BCMF4FUT6Q 4UBDL1PJOUFS3FTFU)BOEMFS Flash (ROM) Boot2 Vector Table [0] Stack Pointer

    [1] Reset Handler .text (code) .data (initial values) ... RAM .data .bss (free) Stack <>4FUVQUIFTUBDL <>$BMMTSFTFUIBOEMFS
  21. @c func resetHandler() { Application.main() // ← You call main()

    yourself } @main struct Application { static func main() { // Your application code starts here } } 'JOBMMZ NBJO /PPOFDBMMTJUGPSZPV
  22. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: 0x4000_C000).clear(1 << 5) while Register(address: 0x4000_C008).load() & (1 << 5) == 0 {} // Configure GPIO25 (on-board LED) Register(address: 0x4001_4000 + 0x04 + 25 * 8).store(5) Register(address: 0xD000_0024).store(1 << 25) Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: 0xD000_0000).load() // Delay } Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to toggle } } }
  23. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: 0x4000_C000).clear(1 << 5) while Register(address: 0x4000_C008).load() & (1 << 5) == 0 {} // Configure GPIO25 (on-board LED) Register(address: 0x4001_4000 + 0x04 + 25 * 8).store(5) Register(address: 0xD000_0024).store(1 << 25) Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: 0xD000_0000).load() // Delay } Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to toggle } } }
  24. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: 0x4000_C000).clear(1 << 5) while Register(address: 0x4000_C008).load() & (1 << 5) == 0 {} // Configure GPIO25 (on-board LED) Register(address: 0x4001_4000 + 0x04 + 25 * 8).store(5) Register(address: 0xD000_0024).store(1 << 25) Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: 0xD000_0000).load() // Delay } Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to toggle } } }
  25. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: 0x4000_C000).clear(1 << 5) while Register(address: 0x4000_C008).load() & (1 << 5) == 0 {} // Configure GPIO25 (on-board LED) Register(address: 0x4001_4000 + 0x04 + 25 * 8).store(5) Register(address: 0xD000_0024).store(1 << 25) Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: 0xD000_0000).load() // Delay } Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to toggle } } }
  26. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: 0x4000_C000).clear(1 << 5) while Register(address: 0x4000_C008).load() & (1 << 5) == 0 {} // Configure GPIO25 (on-board LED) Register(address: 0x4001_4000 + 0x04 + 25 * 8).store(5) Register(address: 0xD000_0024).store(1 << 25) Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: 0xD000_0000).load() // Delay } Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to toggle } } }
  27. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: 0x4000_C000).clear(1 << 5) while Register(address: 0x4000_C008).load() & (1 << 5) == 0 {} // Configure GPIO25 (on-board LED) Register(address: 0x4001_4000 + 0x04 + 25 * 8).store(5) Register(address: 0xD000_0024).store(1 << 25) Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: 0xD000_0000).load() // Delay } Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to toggle } } }
  28. import _Volatile struct Register { let address: UInt32 /// Reads

    the register value. @inline(__always) func load() -> UInt32 { VolatileMappedRegister<UInt32>(unsafeBitPattern: UInt(address)).load() } /// Writes a value to the register. @inline(__always) func store(_ value: UInt32) { VolatileMappedRegister<UInt32>(unsafeBitPattern: UInt(address)).store(value) } ... }
  29. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: 0x4000_C000).clear(1 << 5) while Register(address: 0x4000_C008).load() & (1 << 5) == 0 {} // Configure GPIO25 (on-board LED) Register(address: 0x4001_4000 + 0x04 + 25 * 8).store(5) Register(address: 0xD000_0024).store(1 << 25) Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: 0xD000_0000).load() // Delay } Register(address: 0xD000_001C).store(1 << 25) // Flip the bit to toggle } } }
  30. @c func resetHandler() { Application.main() } @main struct Application {

    static func main() { // Unreset IO_BANK0 Register(address: resetsReset).clear(1 << ioBank0) while Register(address: resetsDone).load() & (1 << ioBank0) == 0 {} // Configure GPIO25 (on-board LED) Register(address: ioBankBase + ctrlOffset + ledPin * pinStride).store(funcSIO) Register(address: sioOutputEnableSet).store(1 << ledPin) Register(address: sioOutputXor).store(1 << ledPin) // Flip the bit to turn on while true { for _ in 0..<500_000 { _ = Register(address: sioBase).load() // Delay } Register(address: sioOutputXor).store(1 << ledPin) // Flip the bit to toggle } } }
  31. .FNPSZ4FDUJPO*OJUJBMJ[BUJPO .BLJOHHMPCBMTUBUJDWBSJBCMFTVTBCMF static let sioBase: UInt32 = 0xD000_0000 // non-zero

    initial value static let ledPin: UInt32 = 25 // non-zero initial value static var counter: UInt32 = 0 // zero initial value static var name: String? // nil = zero in memory ROM (Flash) .text (code) .data 0xD000_0000 25 ... .bss (size info only, no data stored) counter, name, ... ...
  32. .FNPSZ4FDUJPO*OJUJBMJ[BUJPO .BLJOHHMPCBMTUBUJDWBSJBCMFTVTBCMF UInt32 = 0xD000_0000 e Int32 = 25 e

    UInt32 = 0 ing? ROM (Flash) .text (code) .data 0xD000_0000 25 ... .bss (size info only, no data stored) counter, name, ... ... RAM .data (variables) .bss (zero-filled) ...
  33. .FNPSZ4FDUJPO*OJUJBMJ[BUJPO .BLJOHHMPCBMTUBUJDWBSJBCMFTVTBCMF Startup.swift @_extern(c, "__data_start") var __data_start: UInt8 @_extern(c, "__data_end")

    var __data_end: UInt8 @_extern(c, "__bss_end") var __bss_end: UInt8 // Linker symbols → RAM addresses RAM .data (variables) __data_start __data_end .bss (zero-filled) __bss_end ...
  34. func initializeMemorySections() { ... // Copy initial values from ROM

    to RAM var src = dataOrigin var dst = dataStart while dst != dataEnd { dst.pointee = src.pointee dst = dst.advanced(by: 1) src = src.advanced(by: 1) } // Zero out the rest while dst != bssEnd { dst.pointee = 0 dst = dst.advanced(by: 1) } } JOJUJBMJ[F.FNPSZ4FDUJPOT $PQZBOE[FSP fi MM
  35. func initializeMemorySections() { ... // Copy initial values from ROM

    to RAM var src = dataOrigin var dst = dataStart while dst != dataEnd { dst.pointee = src.pointee dst = dst.advanced(by: 1) src = src.advanced(by: 1) } // Zero out the rest while dst != bssEnd { dst.pointee = 0 dst = dst.advanced(by: 1) } } JOJUJBMJ[F.FNPSZ4FDUJPOT $PQZBOE[FSP fi MM @_extern(c, "__data_origin") var __data_origin: UInt8 @_extern(c, "__data_start") var __data_start: UInt8 @_extern(c, "__data_end") var __data_end: UInt8 @_extern(c, "__bss_end") var __bss_end: UInt8
  36. 3FBEZUP3VO .FNPSZJOJUJBMJ[FE NBJO DBOTUBSU @c func resetHandler() { initializeMemorySections() //

    ← Initialize memory Application.main() // ← Now constants work } @main struct Application { static func main() { // Unreset IO_BANK0 Register(address: resetsReset).clear(1 << ioBank0) while Register(address: resetsDone).load() & (1 << ioBank0) == 0 {} // Configure GPIO25 (on-board LED) Register(address: ioBankBase + ctrlOffset + ledPin * pinStride).store(funcSIO) Register(address: sioOutputEnableSet).store(1 << ledPin) ... } }
  37. @c func resetHandler() { initializeMemorySections() // ← Initialize memory Application.main()

    // ← Now constants work } @main struct Application { static func main() { // Unreset IO_BANK0 Register(address: resetsReset).clear(1 << ioBank0) while Register(address: resetsDone).load() & (1 << ioBank0) == 0 {} // Configure GPIO25 (on-board LED) Register(address: ioBankBase + ctrlOffset + ledPin * pinStride).store(funcSIO) Register(address: sioOutputEnableSet).store(1 << ledPin) ... } } 3FBEZUP3VO .FNPSZJOJUJBMJ[FE NBJO DBOTUBSU
  38. $PNQBSJTPOXJUIJ04 'VMM04 J04 NBD04 #BSF.FUBM #PPUTFRVFODF EZMEˠSVOUJNFˠ!NBJO 7FDUPS5BCMFˠSFTFU)BOEMFSˠNBJO 4UBDL 4FUVQCZ04

    4FUJOWFDUPSUBCMF (MPCBMWBSJBCMFT "VUPJOJUJBMJ[FE $PQZ30.ˠ3".ZPVSTFMG 4UBOEBSEMJCSBSZ "MSFBEZMJOLFE /POF OPTUEMJC
  39. static func main() { var board = Board() // Initialize

    clocks, GPIO, USB while true { let gpioState = board.buttons.poll() // Poll button states board.usb.poll() // Handle USB events if board.usb.configured { board.usb.sendXInputReport( // Send button states to host gpioState: gpioState ) } } } 4VQFS-PPQ 64#(BNF$POUSPMMFS
  40. $PPQFSBUJWF4DIFEVMFS 3FHJTUFSJOHBOE3VOOJOH5BTLT SCORE 01240 gameState fallInterval inputDebounce lineClearing musicPosition renderBuffer

    score / level buttonEdge pieceRotation softDropTimer animPhase noteFrequency i2cTransfer pwmDutyCycle ...
  41. func taskGameLogic() { ... } func taskRender() { board.display.clear() renderBoard()

    renderPiece() board.display.flush() // I2C transfer (heavy) } func taskMusic() { Music.advance() // Play next note } static func main() { var scheduler = Scheduler() scheduler.addTask(interval: 16, taskGameLogic) scheduler.addTask(interval: 16, taskRender) scheduler.addTask(interval: 8, taskMusic) scheduler.run() // → Never (doesn't return) } $PPQFSBUJWF4DIFEVMFS 3FHJTUFSJOHBOE3VOOJOH5BTLT
  42. $PPQFSBUJWF4DIFEVMFS 5BTLTDBOOPUCFJOUFSSVQUFE 0ms 8ms 40ms Music Game Logic Render (I2C

    display transfer) Button Input Music Music Button Input %FMBZFE %FMBZFE
  43. )ZCSJE"QQSPBDI 4DIFEVMFS *OUFSSVQUT Scheduler Interrupts 0ms 16ms 32ms Game Logic

    Render (I2C display) Game Logic Render Music Music Music Music Music Music Button Input Scheduler tasks Music (timer interrupt) Button Input (GPIO interrupt)
  44. $IPPTFUIF3JHIU5PPM 5FUSJTˠ)ZCSJEJTUIFSJHIU fi U r %JTQMBZVQEBUFTQFSJPEJD TDIFEVMFS  r 1JFDFGBMMJOHQFSJPEJD

    TDIFEVMFS  r #VUUPOJOQVUOFFETJOTUBOUSFTQPOTF (1*0JOUFSSVQU  r -JOFDMFBSBOJNBUJPOQFSJPEJD TDIFEVMFS  (BNF$POUSPMMFSˠ4VQFSMPPQJTFOPVHI r 3FBECVUUPOTUBUFTQPMMJOHJT fi OF r 64##-&USBOTNJTTJPOTFOEPOTUBUFDIBOHF r /POFFEGPSDPNQMFYUBTLNBOBHFNFOU
  45. 5BLFBXBZT r #FIJOE!NBJO UIFSFTNBDIJOFSZUIBUQSFQBSFTUIFFYFDVUJPO FOWJSPONFOU r 7FDUPSUBCMFT NFNPSZJOJUJBMJ[BUJPO SVOUJNFTUVCT ˠ8FDBOOPXXSJUFBMMPGUIJTJO4XJGU

    r "GUFSNBJO JUTBMNPTUOPSNBM4XJGU r 1JDLUIFSJHIUEFTJHOQBUUFSO TVQFSMPPQTDIFEVMFSIZCSJE  r 8SJUJOHFWFSZUIJOHJO4XJGUUPMFBSOIPXDPNQVUFSTBDUVBMMZXPSL ˠUIFQSJNBMKPZPGTPGUXBSFEFWFMPQNFOU