Experimental Go 1.24.5 runtime bring-up for Nintendo Switch / Horizon, using libnx and CGO. This is a runtime spike, not a complete Go platform port. Not affiliated with Nintendo, the Go project, devkitPro, or Eden.
A single integrated nine-stage suite has completed on a physical Switch: bootstrap, C file I/O, CGO callbacks, channels at GOMAXPROCS 1 and 3, timers, arena boundaries, stack/GC, and a 120-second stability workload. The earlier Eden matrix and its known failures are documented separately in validation/README.md. Do not interpret one hardware run as production readiness or full standard-library support.
- A complete patch against a pinned upstream Go revision, including runtime, syscall stubs, CGO/TLS integration, and target registration.
- Standalone C/libnx reference and Hello Go NRO builds with ASET/NACP metadata.
- Identified, flushed test reports and a verbose on-device test console.
- An isolated Eden runner with hashes, timeout handling, and explicit rejection of incomplete reports, crashes, or missing worker-shutdown confirmation.
- A hardware report without console credentials, keys, firmware, or personal paths.
Use Linux, Git, Bash, Python 3, a bootstrap Go installation, and a separately
installed devkitPro toolchain
with libnx, Switch tools (elf2nro, nacptool), and PIC newlib/libsysbase.
No SDKs or compiler binaries are bundled. See the tested versions below.
Use paths without whitespace: CGO linker flags are whitespace-delimited.
git clone https://github.com/type0labs-dev/go-horizon.git
cd go-horizon
export GOROOT_BOOTSTRAP=/path/to/bootstrap-go
bash prepare-go.sh
export DEVKITPRO=/opt/devkitpro
bash spike/build-c.sh
bash spike/build-hello.sh
python3 spike/check-elf.pyprepare-go.sh checks the patch checksum and exact upstream revision, refuses
an existing go/, applies the patch, and builds the host toolchain. It does
not modify your system Go. The port is pinned to Go 1.24.5; this repository
does not provide ongoing upstream security maintenance for that old version.
Defaults use $DEVKITPRO/devkitA64 and $DEVKITPRO/tools/bin. Override as needed:
export DEVKITA64=/path/to/devkitA64
export HORIZON_TOOL_PREFIX=/path/to/aarch64-none-elf-gcc/bin/aarch64-none-elf-
export HORIZON_SWITCH_TOOLS=/path/to/switch-tools/binThe original tests used Go 1.24.5, Arm GNU 14.2.Rel1 / GCC 14.2.1,
libnx 4.12.0, and devkita64-newlib 4.6.0.20260123-4. These are observed
versions, not a guarantee that every other SDK combination works.
The builds set CGO_ENABLED=1, GOOS=horizon, GOARCH=arm64,
GOARM64=v8.0, and GOEXPERIMENT=nospinbitmutex.
If your SDK does not contain lib/pic/libsysbase.a, obtain compatible
dependencies rather than adding syscall wrappers that call themselves.
hello.build.json records toolchain versions and library/ELF/NRO hashes.
Use your existing homebrew environment in application mode, not applet mode: the Go arena alone is 512 MiB. This project does not install or modify firmware. Back up important data and treat this as experimental software.
-
Copy
spike/hello/hello.nrotosdmc:/switch/hello/hello.nro. -
Create
sdmc:/switch/hello/request.txtwith these three lines, replacing the first with a new identifier for every run:my-unique-run-001 all interactive -
Open Hello Go - Horizon spike. Watch the nine stages and the stability counters. Allow roughly 2–3 minutes without suspending the console.
-
At the final screen, press + to finish. Then retrieve
sdmc:/switch/hello/result.txt.
The test reads request.txt (supplied by you) and writes result.txt and a
64 KiB payload.bin in that test directory. It overwrites previous results,
so archive them before starting again. The standalone reference.nro uses
the same directory; set the requested case to c for the C reference.
Success requires a matching RUN identity, all nine CASE PASS lines,
EXIT: Go workers joined; returning through libnx, a final SUMMARY: PASS,
and an observed normal return to the menu. A PASS written before a crash does
not approve a run. A runtime/kernel crash may bypass the final screen;
save the report and photograph any error. The report cannot itself prove an
OS exit status or that the menu resumed successfully.
all runs the stages once in one process. It does not reproduce the
83-process repeated Eden matrix. Individual cases are boot, cgo,
channels1, channels3, timers, arena, memory, and stability.
Omit the third request line for no console display/pause. The negative
shutdownblocked case is deliberately excluded from all.
Supply your own Eden CLI and a graphical X11/XWayland session. No keys or firmware are copied into the isolated test profiles.
export EDEN=/path/to/eden-cli
python3 spike/run-eden.py spike/hello/hello.nro --case all --timeout 240 --display-test
python3 spike/test-all.py # 80 short runs + 3 x 120-second runs
python3 spike/test-runner.py # mocked acceptance/rejection checks
python3 spike/summarize.py spike/results/suite-YYYYMMDD-HHMMSS.json--display-test exercises console rendering without waiting for controller
input. Each real run archives artifacts under ignored spike/results/.
VSync is explicitly overridden in the isolated profile. A C reference failure
stops the matrix; later failures are collected and always reject the suite.
An incomplete matrix never establishes viability. Do not publish raw cores
or emulator profiles without reviewing their contents for private data.
- Fixed 512 MiB arena, 39-bit address-space assumptions, polling semaphores, no asynchronous goroutine preemption, and unsupported syscalls returning ENOSYS.
- Timers are monotonic; civil time still derives from uptime. Local timezone
is UTC; timezone-file loading is unsupported. File tests use C/libnx, not
a complete
osport. Network, game rendering/audio, and performance remain unvalidated. - Normal
mainremains on the original OS thread. On exit, registered workers stop cooperatively at Go wait points; the port joins them, frees resources, restores TPIDR_EL0, and returns through libnx. The registry is limited to 256 threads over a run; this is not general thread-retirement support. - Joins have a five-second deadline, but a negative busy-loop test ended in an outer Eden timeout without confirming that internal deadline. Never assume arbitrary noncooperative code can be shut down cleanly. Panic and off-main exits still terminate the process rather than returning to the loader.
- External C-created callback threads, custom CGO traceback context, and arbitrary standard-library packages are not supported by this evidence.
- Hardware coverage is one integrated run on one console; firmware/model details were not collected. Additional independent reports are welcome.
Start with CONTRIBUTING.md. Repository-owned code is BSD-3-Clause under LICENSE. Go-derived changes retain the Go Authors' notices, Go license, and patent grant. See THIRD_PARTY.md. No copyrighted game content is included.