I'd like to share pico-bare-swift, a collection of bare-metal Raspberry Pi Pico (RP2040) examples written entirely in Embedded Swift, with zero lines of C. Not even the vector table, boot2, or startup code. The only non-Swift file is the linker script.
What made this possible
Placing data in specific linker sections. ARM Cortex-M requires the vector table and boot2 to be placed at specific addresses via linker sections. This used to require GCC's __attribute__((section(...))), but @section (SE-0492) now lets us do it in Swift:
@used
@section(".vector")
let vectorTable: (
UInt32,
@convention(c) () -> Void,
@convention(c) () -> Void,
@convention(c) () -> Void
) = (
0x2004_0000,
resetHandler,
defaultHandler,
defaultHandler
)
Accessing linker-defined symbols. The startup code needs addresses defined in the linker script (__data_start, __bss_end, etc.) to initialize memory sections. In C you'd write extern char __data_start; and use &__data_start. Now @_extern makes this possible in Swift:
@_extern(c, "__data_start") nonisolated(unsafe) var __data_start: UInt8
@_extern(c, "__data_end") nonisolated(unsafe) var __data_end: UInt8
@_extern(c, "__bss_end") nonisolated(unsafe) var __bss_end: UInt8
(Requires -enable-experimental-feature Extern.)
Exporting C-callable symbols. Hardware and the linker expect specific C symbol names like Reset_Handler. @c (SE-0495) exports Swift functions under those names:
@c(Reset_Handler)
func resetHandler() {
initializeMemorySections()
Application.main()
}
One gotcha: GOT in ROM. The Swift compiler always generates PIC code, which creates GOT (Global Offset Table) entries for @_extern variables. The GOT lands in .data (RAM) by default, but our startup code needs these symbols before .data is initialized. This is a chicken-and-egg problem. The fix was moving .got to ROM in the linker script, which is safe for static binaries.
The repository has 8 progressive examples from a minimal LED blink to I2C OLED display with button input. Feedback welcome!