Skip to content

Latest commit

 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

swift-embedded-nintendo-64

Embedded Swift running on the Nintendo 64, targeting the VR4300 CPU (MIPS III).

Each example builds a working N64 ROM. The Swift code is compiled on the host with a MIPS-enabled swiftc, lowered to a MIPS ELF object via llc, then linked against C code compiled by the appropriate N64 GCC toolchain.

Examples

nusys/ — Calling Swift from C (NuSYS SDK)

3D rotating cube using the proprietary NuSYS / crashsdk toolchain. The graphics pipeline (gfx.swift), scene stages (stage0.swift, stage1.swift), and entry point (lib.swift) are all Swift. C handles static data and matrix math that must call N64 SDK float functions.

Uses @c @implementation (Swift 6.3) to implement functions declared in C headers.

Screenshot 2026-06-29 at 8 13 38 AM

libdragon/ — Swift + libdragon

Animated color grid using the open-source libdragon SDK and its n64.mk build system — a completely different toolchain from the NuSYS example. Swift drives libdragon's rdpq fill API; C owns libdragon init and the frame loop.

Screenshot 2026-06-29 at 8 12 53 AM

Cross-toolchain gotchas

Two hard constraints apply to all examples:

1. No floats across the Swift↔C boundary. LLVM (swiftc) and N64 GCC disagree on the o32 float-argument ABI: LLVM passes trailing floats after a pointer in FP registers ($f13/$f14); GCC expects them in GPRs ($a1/$a2). Pass IEEE-754 bit patterns as UInt32/u32 instead and reinterpret with a union on the C side.

2. ≤4 register arguments per Swift↔C bridge call (libdragon only). libdragon uses -mabi=o64 (8-byte stack slots); the Swift object is o32 (4-byte stack slots). Arguments beyond the first four spill to the stack and are read at the wrong offset. Pack multiple values into a single uint32_t to stay within four registers.

Conditional moves (movn/movz) — fixed by llc flags

By default, LLVM lowers Swift if/ternary/min/max on integers to select IR, which the MIPS backend emits as movn/movz — MIPS IV instructions the VR4300 does not have. Executing them raises a Reserved Instruction exception.

The fix is two llc -mattr flags that disable the MIPS IV conditional-move subset inherited by MIPS32:

-mattr=+mips3,+noabicalls,+gp64,+fpxx,+nooddspreg,-mips4_32,-mips4_32r2

Both compile-swift.sh scripts already include these flags. With them, Swift conditionals compile to branches and there are no restrictions on using if/else, ternaries, min, max, or abs in Swift code.

Building

Each example has its own build.sh. Requirements:

  • A MIPS-enabled Embedded Swift toolchain (swiftc + llc) — the standard release toolchains do not include MIPS; you need a custom build or one that ships MIPS embedded Swift modules.
  • Docker — for the N64 GCC toolchain and SDK (pulled automatically on first run).
cd nusys     && ./build.sh   # → build/calling-swift-from-c.z64
cd libdragon && ./build.sh   # → libdragon-swift.z64

About

Embedded Swift for Nintendo 64

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages