- Board renamed docs/plan/00-kanban.md -> docs/00-status.md and rebuilt: ▶ NEXT PLAN pointer (iteration 4 — emitter, corpus, `woc build`) then six buckets — stories, in progress, done, pending, discarded, learnings. It covered only the Rust runtime before, so the whole OOP track was invisible. All 16 inbound refs repointed; `Kanban:` banners renamed to `Status:`. - New discarded.md (settled rejections with reasons: inheritance, `abstract`, Money/SKU/Float, Dynamic/cast/macro/extern, AOT-to-C, Menhir, shared engine state) and learnings.md (plumbed≠enforced, vacuous goldens, exit-0-wrong- output, malloc-path ASan trick, deferred checks that never reach the VM). - RECOVERED docs/plan/exploration/blue-green-vm/00-vision.md — gone from disk, never committed (gitignored path), cited by five docs incl. principle 12. Root cause was broader: all seven forward-roadmap plans in docs/superpowers/plans/ were untracked and ignored, on one disk only. Dropped the docs ignore rules with a do-not-re-add note; added __pycache__/*.pyc. - Repaired broken links across docs/, 270 -> 36: fixes a regression from the earlier reference/ -> .dev/reference/ move (relative paths at ../../ and deeper were skipped), plus depth and reorg drift. The 36 residual point at content that does not exist and need decisions, not paths. - New spec docs/superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md, applied: `and`/`or` verdict row; Part 3 gains `env` (six modules), swaps time.mono for iso/local, adds 22 bare core builtins; throw/time.mono/is cut (0 uses in the sample). Plan 8: Task 2 gains and/or, Task 5 drops throw, abstract+`is` task deleted, 8/9 renumber to 7/8. Plan 9 gains core builtins. Plan 10 gains the 307 -> 0 diagnostic gate. WO-E205 re-filed unreachable-by- design. types.ml header drops its false satisfaction-set claim. 00-code- review.md reduced to a stub — its rival Phase 1-4 roadmap retired.
7.2 KiB
01 — Go's runtime assembly, catalogued
The Go runtime ships ~72 TEXT functions in asm_amd64.s alone, ~43 in sys_linux_amd64.s, and per-architecture variants of both for 386, arm, arm64, loong64, mips(64)x, ppc64x, riscv64, s390x, wasm. This doc inventories them by purpose so a reader can map each Go asm concern to the writeonce equivalent (spoiler: usually "Rust stdlib does it"). Follow-on reading: 02-writeonce-stance.md.
All paths are inside reference/go/src/runtime/.
Scheduler & stack switching — asm_<arch>.s
One file per arch, everything that has to break Go's calling convention. The x86_64 version lives at asm_amd64.s.
| Go symbol | What |
|---|---|
runtime·gogo(SB) |
Jump to a goroutine's saved program counter on its stack. The machine-level act of resuming a suspended goroutine. |
runtime·mcall(SB) |
Call a function on the g0 stack (the OS-thread's real stack). Used by anything that may block indefinitely — the calling goroutine gets parked. |
runtime·systemstack(SB) |
Transient jump to g0 for a single function (GC operations, scheduler code) and back. |
runtime·morestack(SB) |
Stack-growth trampoline. Preamble-injected by the compiler when a function's stack usage exceeds the current segment; calls back into Go to allocate more. |
runtime·asmcgocall(SB) |
Enter C code. Switches from Go stack to the OS thread's C stack, marshals arguments, handles re-entry if C calls back into Go. |
runtime·cgocallback(SB) |
Re-enter Go from C. The inverse of the above. |
runtime·memmove(SB) |
Optimised memcpy; falls into SSE/AVX paths for large regions. Competes with (and often beats) the C library's version on specific inputs. |
runtime·memequal(SB) |
Fixed-length memory compare; used by map key comparisons and interface equality. |
runtime·memclrNoHeapPointers(SB) |
Fast zero-fill for regions the GC doesn't need to scan. Autovectorised; avoids Go-level write-barrier overhead. |
runtime·jmpdefer(SB) |
defer unwinding. Manually adjusts the frame pointer to jump into a deferred function as if it had been called at a different site. |
runtime·asyncPreempt(SB) |
Preemption entry point (Go 1.14+). A signal handler rewrites the target goroutine's PC to point here; on resume it parks and runs the scheduler. |
Why it's asm: every function above manipulates state (stack pointer, program counter, register contents) that Go's language semantics don't let you express.
Atomics & barriers — internal/runtime/atomic/atomic_<arch>.s
Lives at internal/runtime/atomic/atomic_amd64.s (and arch variants). Wrappers around arch-specific instructions:
| Go symbol | x86 instruction | Purpose |
|---|---|---|
·Load / ·Loadp / ·Load64 |
MOV |
Acquire load. On x86, plain MOV is already acquire-ordered; on ARM the same operation needs LDR + DMB ISH. |
·Store / ·Store64 / ·StoreRel |
XCHG or MOV + MFENCE |
Release store. |
·Xchg / ·Xchg64 |
XCHG |
Atomic swap. |
·Xadd / ·Xadd64 |
LOCK XADD |
Atomic fetch-and-add. |
·Cas / ·Cas64 / ·Casp |
LOCK CMPXCHG |
Compare-and-swap. |
·And / ·Or / ·And8 / ·Or8 |
LOCK AND / LOCK OR |
Atomic bit manipulation. |
Why it's asm: the instruction per operation differs per architecture. Go abstracts them behind a consistent sync/atomic surface.
Syscall trampolines — sys_<os>_<arch>.s
On Linux-x86_64 that's sys_linux_amd64.s — 43 TEXT functions. Each is a short wrapper: move args into the kernel's register layout, execute SYSCALL, convert rax into a Go return value + error.
| Go symbol | Linux syscall |
|---|---|
runtime·write / runtime·write1 |
write(2) |
runtime·read / runtime·pread |
read(2) / pread64(2) |
runtime·closefd |
close(2) |
runtime·open |
openat(2) |
runtime·futex |
futex(2) |
runtime·clone |
clone(2) — M (OS thread) creation |
runtime·rt_sigaction / runtime·rt_sigprocmask |
Signal plumbing |
runtime·sigreturn |
Return from signal handler |
runtime·sched_yield |
Voluntary preemption |
runtime·mmap / runtime·munmap / runtime·madvise |
Memory operations |
runtime·epollcreate1 / runtime·epollctl / runtime·epollwait |
The epoll trio |
runtime·exit / runtime·exitThread |
Process / thread termination |
Why it's asm (in Go): libc's syscall wrappers check for cancellation, TLS, errno state — overhead Go's scheduler can't afford between entersyscall and exitsyscall. Go needs precise control over when the goroutine is "in a syscall" so the M can be detached from the P.
Note: writeonce does use libc's syscall wrappers — we don't have a scheduler to detach, so the overhead is irrelevant. See the stance doc for the full rationale.
Signal handling — sigtramp in sys_<os>_<arch>.s
runtime·sigtramp (same file as the syscalls) is the entry point the kernel jumps to when a signal fires. It saves the pre-signal register state, switches to the g0 stack, calls into runtime.sigtrampgo, then restores on return. Needs asm because the ABI between kernel and user signal handler is rigid and arch-specific.
Writeonce avoids this entirely by using signalfd — signals become fd reads on the epoll loop, no handler ever runs. See ../linux/04-signalfd.md.
Cgo bridge — cgo_<os>_<arch>.s
Files like cgo/asm_amd64.s. Machine-code marshalling between Go's register convention and C's SysV AMD64 ABI. Needed because Go's calling convention uses stack slots differently from C's register passing.
Writeonce doesn't cross language boundaries — Rust is the only language in the binary; libc is already in Rust's register convention via extern "C". No cgo bridge needed.
Timers & monotime — time_<os>_<arch>.s
Small files providing the absolute-minimum latency paths for nanotime() and friends. On modern Linux they use vDSO entries (__vdso_clock_gettime) that bypass the syscall boundary.
Writeonce uses std::time::Instant::now() which internally invokes the same vDSO through glibc — already optimised, no added value in rolling our own.
ASAN / MSAN / race detector — asan_<arch>.s, race_<arch>.s
Hookable entry points for sanitisers. Not relevant to writeonce.
What's NOT in asm
Everything else in Go's runtime is plain Go: the scheduler's policy (proc.go), the garbage collector (mgc.go etc.), netpoll dispatch (netpoll.go — Go code; the platform-specific backends like netpoll_epoll.go are also pure Go that call into the asm epollwait trampoline). The asm is strictly the three categories in 00-overview.md: calling-convention-breaking operations, arch-specific instructions, and syscall trampolines.
The three categories writeonce also needs a solution for — but writeonce gets all three from Rust stdlib + libc. The next doc catalogues those mappings.