Go: A Documentary

OS layer, stacks and panics

Part I · 48 threads in 3 era slices, from Go 1.0 to Go 1.28. Each thread gives its design, then a per-release timeline of what landed (Done) and what was still open. Release labels in a timeline link to that release's page.

● = the thread had substantive commits in that cycle; hover for the stage summary, click to jump to it in the release page.

Thread map, Go 1.0 – 1.17

Thread1.01.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.17Status at go1.17 (or at the end of its era slice)
OS interface, stacks and panics (Go 1.0–1.4)
Segmented stacks to contiguous (copied) stacks●●●●●still the design at go1.17 (stack growth by copying, 2 KB minimum, no segment chain) → continues as Stack growth, stack bounds and the crash stack (1.18+)
Stack-split prologue, nosplit budget and C-on-Go-stack detection●●●●the prologue check and nosplit budget persist (budget size changed); StackPreempt-style guard poisoning persists → continues as Stack growth, stack bounds and the crash stack (1.18+)
Goroutine preemption (stackguard0, 10 ms and GC preemption)●●replaced by asynchronous (signal-based) preemption in a later release; the cooperative stack-check mechanism remains as a fallback → continues as Signals and async-preemption machinery (1.18+)
Panic, defer, recover and Goexit●●●●●replaced by open-coded defers and the new panic loop in later releases (1.14, then 1.22) → continues as Defer, panic and recover re-implementation (1.18+)
Tracebacks, crash output and GOTRACEBACK●●●●●gentraceback is the single stack walker (becomes the unwinder iterator in 1.21) → continues as Unwinder: gentraceback → unwinder iterator, inline unwinder, pcvalue cache (1.18+)
Symbol tables, pclntab and runtime.Caller/FuncForPC●●●●pclntab is still the design (format changed in 1.2 and 1.16/1.18) → continues as Symbol tables, runtime.Caller and CallersFrames (1.18+)
Signals: handling, os/signal, foreign threads●●●●●still the design (handlers installed with sigaction, sigqueue for os/signal) → continues as Signals and async-preemption machinery (1.18+)
Threads, locks and the OS scheduler interface●●●●●still the design (futex/sema based lock_futex.go, lock_sema.go) → continues as Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN (1.18+)
cgo runtime support and non-Go threads●●●●●still the design in outline (cgocall/asmcgocall, cgocallback, extra M) → continues as C-to-Go callbacks: extra-M reuse, system-stack bounds, init gating (1.18+)
TLS, g/m registers and shared-library linking●●●●●still the design in outline (g in TLS or a reserved register) → continues as Library entry points and runtime initialization (1.18+)
Clocks, vDSO and timers●●●●vDSO clock_gettime still used on Linux → continues as vDSO, vgetrandom and fast clocks (1.18+)
Windows runtime port●●●●●still VEH-based exception handling → continues as Windows runtime port (1.18+)
Platform ports: OS and architecture matrix●●●●●these ports persist except plan9/solaris variants carried through later work; nacl removed later → continues as New, ported and removed platforms (1.18+)
Integrated network poller: OS back-ends●●●●still the design (epoll, kqueue, IOCP, solaris ports) → continues as Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN (1.18+)
Runtime C-to-Go conversion and the Go calling convention in C (1.4)●●completed; no C left in the runtime → continues as Library entry points and runtime initialization (1.18+)
Startup, auxv, randomness and security-adjacent runtime details●●still present → continues as Security hardening: AT_SECURE, std fds, AT_RANDOM, gated debug features (1.20+)
OS interface, stacks and panics (Go 1.5–1.17)
C-to-Go conversion of the runtime's OS, arch and signal layers ([dev.cc])●done in 1.5; the all-Go layout (os_$GOOS.go, signal_$GOOS_$GOARCH.go, sys_$GOOS_$GOARCH.s, defs_.go, one sigctxt per OS/arch) is still the structure at go1.17.
New and ported OS/arch targets, 1.5–1.17●●●●●●●●●●●linux/{ppc64,ppc64le,arm64,mips,mipsle,mips64,mips64le,s390x,riscv64}, android/{386,arm,arm64,amd64}, js/wasm, aix/ppc64, darwin/arm64 (iOS) and ios, illumos, freebsd/arm64, netbsd/arm64, openbsd/{arm,arm64,mips64}, plan9/arm exist; nacl, darwin/arm, darwin/386, dragonfly/386 removed; windows/arm64 landing → continues as New, ported and removed platforms (1.18+).
Goroutine stack growth, shrinking, GC stack barriers and stack objects●●●●●●●●●●●●still the design at go1.17: contiguous stacks copied by copystack, shrunk during concurrent mark, stack barriers gone, stack objects used for address-taken locals → continues as Stack growth, stack bounds and the crash stack (1.18+).
Tracebacks, symbol tables and runtime.Callers/CallersFrames●●●●●●●●●●●●●gentraceback is still the single stack walker, now inline-aware, TOPFRAME/SPWRITE-driven → continues as Unwinder: gentraceback → unwinder iterator, inline unwinder, pcvalue cache (1.18+) and Symbol tables, runtime.Caller and CallersFrames (1.18+).
Defer, panic, recover and Goexit (runtime side)●●●●●●●●●●●three defer kinds (heap/stack records via deferproc/deferprocStack, open-coded defers via funcdata bits), all processed by gopanic with a _defer chain; GOEXPERIMENT=regabidefer makes deferred calls func() → continues as Defer, panic and recover re-implementation (1.18+).
Signals: masks, forwarding, sigaltstack and the unified Unix signal layer●●●●●●●●●●●●●one signal_unix.go with sighandler, sigtrampgo, adjustSignalStack, sigfwdgo; libc sigaction under cgo; M-targeted signals for preemption → continues as Signals and async-preemption machinery (1.18+).
Asynchronous preemption: signal plumbing, injected calls and per-arch support●●●●still the design at go1.17 → continues as Signals and async-preemption machinery (1.18+).
cgo runtime support: pointer checks, callbacks, extra Ms, C-to-Go call path●●●●●●●●●●●●the simplified C→Go path (crosscall2 → cgocallback → cgocallbackg → _cgoexp_GoF), cgocheck=1 pointer rules, extra-M list with batch creation → continues as C-to-Go callbacks: extra-M reuse, system-stack bounds, init gating (1.18+).
Shared-library and buildmode entry points (c-archive, c-shared, plugin, shared)●●●●●●●●●●per-arch _rt0__lib entry points exist for linux, darwin, windows, android, ios; init runs on a new thread and the loader thread returns → continues as Library entry points and runtime initialization (1.18+).
vDSO, time sources and process-wide randomness●●●●●●●●●●●vDSO used for clock_gettime on linux/{amd64,386,arm,arm64,mips64x,ppc64x}; Darwin/Windows use libc/KUSER_SHARED_DATA; fastrand seeded from OS randomness → continues as vDSO, vgetrandom and fast clocks (1.18+).
Darwin and OpenBSD move to libc; Solaris and AIX libcall; syscall wrapper layer●●●●●●●macOS and iOS, Solaris/illumos and AIX go through libc via libcCall/asmcgocall trampolines, and openbsd/{amd64,386,arm,arm64} link libc and use pthreads; Linux/FreeBSD still use raw syscalls → continues as Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN (1.18+).
Windows runtime●●●●●●●●●●●●●Windows 10 long-path support, arm and arm64, high-resolution waitable timers, QPC/KUSER time, SuspendThread preemption, stdcall via syscall_syscall, callbacks via compileCallback → continues as Windows runtime port (1.18+).
CPU feature detection (internal/cpu), auxv parsing and GODEBUG=cpu.*●●●●●●●●internal/cpu is the single detection layer (x86, arm, arm64, ppc64x, s390x, mips64x) with GODEBUG=cpu.feature=off/on → continues as CPU feature detection, GOAMD64 checks and cpu GODEBUG options (1.18+).
Assembly-level runtime entry points: memequal/cmp/IndexByte, hashing, atomics and ABI wrappers●●●●●●●●●●●●●hand-written assembly in runtime, internal/bytealg, runtime/internal/atomic; ABI0/ABIInternal wrappers generated for cross-package assembly references; register ABI prerequisites in place → continues as the register-ABI work of 1.18+ (see Library entry points and runtime initialization).
Debugger function calls and conservative preemption frames●●●●still the design at go1.17 (debug calls run on a new goroutine with conservative scanning of the interrupted frame) → continues as Signals and async-preemption machinery (1.18+).
Thread lifecycle, physical page size and scheduler-facing OS hooks●●●●●●●●still the design at go1.17 → continues in Signals and async-preemption machinery (1.18+).
Plan 9 and NaCl ports●●●●●●Plan 9 still supported (notes as signals, env cache); NaCl removed in 1.14 → continues as New, ported and removed platforms (1.18+).

Thread map, Go 1.18 – 1.28

Thread1.181.191.201.211.221.231.241.251.261.271.28Status at HEAD
Runtime: OS and platform layer, stacks, panics and tracebacks (Go 1.18–1.28)
Defer, panic and recover re-implementation●●●●●●●●●●open-coded + linked defers share one state machine (_panic.nextDefer/nextFrame); recovery transfers to the PC of deferreturn; recover validity is decided by unwinding; still being tuned in 1.28 (linked-defer matching)
Unwinder: gentraceback → unwinder iterator, inline unwinder, pcvalue cache●●●●●unwinder (traceback.go) and inlineUnwinder are the only stack-walk implementations; gentraceback deleted in 1.21
Symbol tables, runtime.Caller and CallersFrames●●●●●●●_func entries are module-relative offsets, findfunc has a single-text-section fast path; documented Caller/Frames semantics
Frame pointers: explicit NOFRAME, FP maintenance and FP unwinding●●●●●all amd64 runtime assembly uses explicit NOFRAME; FP is saved/adjusted across stack copies, signals, cgo and injected calls; FP unwinding used by the tracer
Stack growth, stack bounds and the crash stack●●●●●●●"morestack on g0" prints a traceback on a dedicated crash stack on nearly every arch; stack guard tuned per OS/mode
C-to-Go callbacks: extra-M reuse, system-stack bounds, init gating●●●●●●●●●●●the M created for a C thread is cached in a pthread key until thread exit; g0 bounds re-estimated per callback; init check is an atomic bool
Library entry points and runtime initialization●●●●●●●c-shared/c-archive start-up is largely shared Go code; per-arch assembly reduced
Security hardening: AT_SECURE, std fds, AT_RANDOM, gated debug features●●●●●●isSecureMode/tainted-binary protections in place; page tracer behind GOEXPERIMENT; startup randomness overwritten
runtime/secret and data-independent timing (DIT)●●●●secret.Do implemented under GOEXPERIMENT guard; linux/amd64, arm64 and (1.28) loong64; goroutines spawned inside inherit secret state
vDSO, vgetrandom and fast clocks●●●●●●●●Linux getrandom through vDSO on all arches the kernel supports; per-M state pool
Signals and async-preemption machinery●●●●●●●●●●●signal handling uniform across Unix with libc sigaction under cgo on more arches; preemption saves AVX-512/SVE state
Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN●●●●●●●●Linux raw syscalls live in internal/runtime/syscall/linux; Windows/darwin generic syscalln in the syscall package
Windows runtime port●●●●●●●●●●Windows 10 minimum, windows/arm removed, loader-provided TLS, high-resolution timers, SEH-aware crashing
New, ported and removed platforms●●●●●●●●●●added linux/loong64, freebsd/riscv64, openbsd/riscv64, openbsd/ppc64, wasip1/wasm; removed windows/arm, openbsd/mips64; linux/ppc64 moved to ELFv2
CPU feature detection, GOAMD64 checks and cpu GODEBUG options●●●●●●●●●●internal/cpu detects features for amd64 (AVX-512 family, SHA-NI), arm64 (SHA3/SHA512, LSE, DIT, SVE/SVE2, Neoverse IDs), riscv64 (extensions, VLENB, vector crypto, zbc), loong64 (HWCAP, LAMCAS); auxv exposed

OS interface, stacks and panics (Go 1.0–1.4)

Reader: Claude Sonnet. Cites 962 of 1,120 commits in this slice; every non-noise commit is cited, plus 18 larger commits the noise filter had set aside, which a separate gap reader described. Read the design-heavy messages and subject lines.

Release digest for this slice
  • Go 1.0 — Everything from the first 2008 runtime through Go 1: segmented stacks, signals and tracebacks, Linux/Darwin/ARM/386/Windows/Plan 9/FreeBSD/OpenBSD ports, cgo and runtime/cgo, mutex unification, vDSO-free time, GOMAXPROCS and thread startup.
  • Go 1.1 — Non-Go threads can call Go (6c976393ae), fast time sources, integrated netpoll, NetBSD and FreeBSD/ARM ports, new symbol table encoding, GOTRACEBACK=crash.
  • Go 1.2 — Preemptive scheduling via stackguard0, rewindmorestack prologue, 8 kB segments and the 1 GB stack limit, -shared/external linking on ARM, dragonfly, lazy extra M, wrapper-aware recover.
  • Go 1.3 — Contiguous stacks and their bug fixes, VEH on Windows, Solaris and NaCl ports, monotonic clocks, SetPanicOnFault.
  • Go 1.4 — Stack copying becomes the only mechanism with a 2 KB minimum; panic/defer, symtab, signal queue and locks converted to Go under the new C calling convention; Android and NaCl/ARM; PC/traceback semantics for Callers.

Segmented stacks to contiguous (copied) stacks

Span: Go 1.0 → Go 1.4 · Status at go1.17: still the design at go1.17 (stack growth by copying, 2 KB minimum, no segment chain) → continues as Stack growth, stack bounds and the crash stack (1.18+)

Continues in: Goroutine stack growth, shrinking, GC stack barriers and stack objects (Go 1.5 → Go 1.17)

Design docs & discussion

Design. The first scheme, from 2008, is Ken Thompson’s “segmented stacks AND goroutines” (751ce3a77a): every goroutine starts on a small segment, each function prologue compares SP against a per-G stack guard, and a failing check calls morestack, which allocates a new segment, records the old one in a Stktop header (the “stack top” record: old base, old guard, argument pointer, free flag), copies the incoming arguments over, and runs the function there. On return the function lands in lessstack/oldstack, which pops the segment. The linker (6l pass.c) inserted the check; d3204ef19f and 44f65c8f08 taught the traceback to hop across segments, and later 4b376ef328 made it work through an active lessstack. This is the “hot split” design: a loop that straddles a segment boundary pays an alloc/free per iteration, which is why the segment size became a tuning knob.

During Go 1.2 the default segment size was changed from 4 kB to 8 kB (408238e20b) as an explicit stopgap: the message reports GobEncode -64% and Template -17% on amd64, notes that demand paging means the unused second 4 kB is not resident, and says “khr’s new stacks will avoid needing to make this decision at all”. It was briefly reverted to the old value for the dev cycle and then set back to 8 kB (6aee29648f). Go 1.2 also added a hard limit on stack growth, 1 GB on 64-bit and 250 MB on 32-bit (757e0de89f), so runaway recursion dies with a diagnostic instead of eating the machine.

Contiguous stacks arrive in 1.3. The main commit is 1665b006a5 (“grow stack by copying”, 2014-02-26): GOCOPYSTACK=0 disables shrinking (b8d40172ce); 1a89e6388c consolidates all stack-size and segment-origin bookkeeping in stackalloc/stackfree after finding three bugs (g->stacksize accounting wrong across copystack/shrinkstack, stktop->free not maintained, a FixedStack block that came from the heap being freed into the stack cache); 5daffee17f fixes the size check that looked only at the top segment; 84570aa9a1 rounds stack sizes to powers of two; 1f4d2e79b0 adds efence support. Copying needs exact pointer maps: cfb347fc0a fixes the PC used for liveness lookup (the instruction before the return address, matching what the GC does), 14d2ee1d00 adds a “continuation PC” for frames stopped by a fault (pc after the last deferproc CALL, or 0) because liveness is recorded only at CALLs, and fc6753c7cd/3d1c3e1e26 make defers copyable.

Go 1.4 commits to copying and deletes the old scheme. 15b76ad94b (“assume precisestack, copystack, StackCopyAlways, ScanStackByFrames”) removes the fallbacks (348 insertions, 2893 deletions in 48 files): the Stktop chain, M.moreargp/moreargsize/moreframesize/cret, lessstack, oldstack, many amd64 morestack variants, and replaces stackguard/stackbase/stack0/stacksize/syscallstack/syscallguard/forkstackguard in G by simple bounds (lo, hi). The split prologue shortens because frame/arg sizes are no longer passed. 6c934238c9 lowers the minimum stack to 2 KB (8 KB on Windows, 4 KB on plan9), cutting 100,000 goroutines on linux/amd64 from about 819 MB to 245 MB. The mechanics around it: 92eb1e1600 frees the stacks of Gdead goroutines at GC time, 9198ed4bd6 lets the onM frame be copied (so the goroutine that triggers GC can be shrunk), d6d7170de4 fixes a race between stack shrinking (during mark) and stack-cache flushing, e844f53a01 stops scanning frames/args conservatively (crash instead), and 193daab988 forbids conservative data/bss objects in the linker.

Timeline.

Stack-split prologue, nosplit budget and C-on-Go-stack detection

Span: Go 1.0 → Go 1.4 · Status at go1.17: the prologue check and nosplit budget persist (budget size changed); StackPreempt-style guard poisoning persists → continues as Stack growth, stack bounds and the crash stack (1.18+)

Continues in: Goroutine stack growth, shrinking, GC stack barriers and stack objects (Go 1.5 → Go 1.17)

Design. In Go 1.2 6fa3c89b77 changes the prologue so that after call morestack there is an extra jmp start; the jump is never executed by the CPU. runtime.rewindmorestack decodes it to find the function start, and morestack returns directly to the start label. Effects: newstack can save a coherent goroutine state at function entry, so a traceback at a split overflow works with no special cases; the check is repeated, which doubles as a verification that the new stack is big enough; and rewinding makes preemption possible because the goroutine can resume on its original stack and re-run the check. 031c107cad fixes the linker for large-frame split plus preempt check. Runtime assembly marked NOSPLIT must fit a fixed budget that cmd/ld checks statically (failures such as d83688165a, ddc01d5b06 show the call-chain accounting for notetsleep); the budget grows through Go 1.4: fe91006a02 (+32 bytes), 397bdb216f (to 192), b6951bcc0b (split limit again).

In 1.4 the C-to-Go conversion needs to know that no C code runs on a Go stack. c81a0ed3c5 changes the C prologue (liblink/obj5.c, obj6.c, obj8.c) to compare against a different G word, stackguard1: it holds the normal guard on g0 and gsignal, and ^0 on ordinary goroutine stacks, so the check fails and calls morestackc, which aborts with a message about C code on a Go stack. This is dynamic and covers function pointers; it immediately found sigpanic being pushed onto Go stacks from the signal handlers. 32ecf57d22 rejects onM calls from the gsignal stack (onM assumes it runs on curg or g0, and scalararg/ptrarg are not reentrant); d16a2ad09b makes traceback look past onM and fakes an mstart caller on the rented g0 stack; 1d550b87db allows crashing from the gsignal stack; 12308d5a0b fixes the throwsplit check, which ran on g0 where it was never set. For assembly functions that appear mid-stack, 99f7df0598 makes cmd/gc turn a Go prototype into myfunc.args_stackmap (two bitmaps: inputs live at entry; inputs+outputs after GO_RESULTS_INITIALIZED) and adds GO_ARGS, NO_LOCAL_POINTERS in funcdata.h; 653fb6d872 makes GO_ARGS the default for functions beginning with a middle dot.

Timeline.

Goroutine preemption (stackguard0, 10 ms and GC preemption)

Span: Go 1.2 → Go 1.2 · Status at go1.17: replaced by asynchronous (signal-based) preemption in a later release; the cooperative stack-check mechanism remains as a fallback → continues as Signals and async-preemption machinery (1.18+)

Continues in: Asynchronous preemption: signal plumbing, injected calls and per-arch support (Go 1.14 → Go 1.16)

Design docs & discussion

Design. Go 1.2 is the first release with a preemptive scheduler, built from the stack check. f5becf4233 adds stackguard0 to G: it is what the prologue compares against; the real bound stays in stackguard. Setting stackguard0 = StackPreempt makes the next function call enter morestack, and newstack recognizes the sentinel as a preemption request instead of a growth request. 354ec51666 adds the preemption function (unused at first), d67e7e3acf adds lr, ctxt, ret to Gobuf so a goroutine can be rescheduled mid-prologue, 1e112cd59f makes stoptheworld issue preemption requests every 100 us (“the last patch for preemptive scheduler”), and bc31bcccd3 preempts any goroutine that runs longer than 10 ms (sysmon-driven). ef12bbfc9d disables preemption during deferreturn, fd23958f49 fixes defer leaks, f70a19f085/4eb17ecd1f fix goroutine status corruption when entersyscall’s lock call splits the stack.

Timeline.

Panic, defer, recover and Goexit

Span: Go 1.0 → Go 1.4 · Status at go1.17: replaced by open-coded defers and the new panic loop in later releases (1.14, then 1.22) → continues as Defer, panic and recover re-implementation (1.18+)

Continues in: Defer, panic, recover and Goexit (runtime side) (Go 1.5 → Go 1.17)

Design. In the C runtime, panic.c keeps two stacks per G: g->defer (Defer records, LIFO) and g->panic (Panic records). gopanic runs deferred calls from g->defer; recover is legal only when called directly by the deferred function, and gorecover verifies this through an argp comparison against the Panic. 0de71619ce (Go 1.1) aggregates defer allocations (Defer records allocated in batches and kept on a free list), removing the per-defer allocation and about 30% of the time in BenchmarkDefer (165 to 113 ns, allocs 1 to 0); 72e83483a7 shows cgo calls allocating Defer on the stack.

Two correctness issues dominate the history. First, wrapper methods: for defer iface.M() the compiler-generated wrapper adds a second frame, so recover() returned nil. 7276c02b41 (1.2) adds g->panicwrap, which wrappers bump on entry and decrement on exit, and propagates the Stktop.panic flag forward on stack split. 8473695797 (1.4) throws this out as “fatally flawed” and replaces it with Panic.argp, adjusted on wrapper entry only, so unwinding need not undo anything. Second, copying stacks: f95beae61d makes the GC and copystack both use traceback over the Defer chain, keeps the executing Defer on the list until its arguments are copied (fixing two races and a dangling-pointer bug in copystack), and removes dabort and the untyped allocation; the new tests were TestDeferPtrsPanic, TestDeferPtrsGoexit, TestPanicUseStack.

Go 1.4 converts it all to Go: f44073785a (deferproc/deferreturn), 8217b4a203 (panic and recover, first as panic1.go so the diff was readable, merged in 3a3d47db37), 7006aafdcd preallocates index/slice panic errors, 523aa93288 moves panicindex/panicslice. Semantics fixes: dd740343e0 stack-allocates the Panic struct in 1.3 so panicking does not malloc; f946a7ca09 fixes heap corruption and Defer leaks in recursive panics; 15274e5c9b documents that Goexit cannot be recovered; 0306478fe5 skips already-started defers when Goexit and panic interact; e6708ee9b1/94bdf13497 clear Defer fields before unlinking; 36207a91d3 (with 060a988011 on Windows) makes defer of a nil func panic at the defer statement, and 5032a7dc0c (1.0) gives a distinct message for calling a nil func value.

Timeline.

Tracebacks, crash output and GOTRACEBACK

Span: Go 1.0 → Go 1.4 · Status at go1.17: gentraceback is the single stack walker (becomes the unwinder iterator in 1.21) → continues as Unwinder: gentraceback → unwinder iterator, inline unwinder, pcvalue cache (1.18+)

Continues in: Tracebacks, symbol tables and runtime.Callers/CallersFrames (Go 1.5 → Go 1.17)

Design. Tracebacks start as a hand-written frame walk in rt2_amd64.c (aeb43984ec, 2008). a5433369aa uses the pc/ln table to print file:line per frame; d040d26863 and 2b39165f1e set and fix the format (all frames; previously stopped one frame early); ec913c42b3 backs PCs up to the call line. 3835e01d97 prints all goroutines, 324cc3d040 (2011) records the creation PC and prints “goroutine created by”. 48bd13911d (2012) uses GOTRACEBACK=2 to include runtime frames without recompiling; 5146a93e72 (1.1) adds GOTRACEBACK=crash (traceback including runtime frames, then crash for a core dump), with Windows and Plan 9 handlers adapted; 0097d30c97 (1.3) unblocks signals before a core dump.

Subsequent work makes the walker robust: c3e54f0988 and 63f38d62ac handle calls through nil function pointers, 7081e67565 non-standard ARM call sequences, f58ba18a08 nascent goroutines and closures; 13507e0697, 58f12ffd79 morestack/lessstack; 326ae8d14e traceback in cgo programs; 89c5d17878, 92b4741728 gdb breakpoints in x86 traceback; cc84176108 prints the g0 stack when throwing on it with GOTRACEBACK>=2; 2fb87ddb59 prints the goroutine header on faults; fa4984d535 shows runtime.panic.

The 1.4 round is about PC semantics. a22c11b995 adds a trap flag to gentraceback: previously “never subtract 1 from the initial PC” was right only for signals; now traceback defaults to “starting with a return PC” and tracebacktrap is used from the signal handlers. a5a0733144 makes the top-most return PC goexit+PCQuantum (goexit implemented in assembly so the first instruction can be skipped) so that PC-1 is never in a different function, which removed the last special case for runtime.Callers consumers. 3de7ba1873 makes PC/SP fields uintptr; 7ba41e9972 moves traceback helpers to traceback.go; f95beae61d and e844f53a01 rely on traceback for defers and conservative-scan removal.

Timeline.

Symbol tables, pclntab and runtime.Caller/FuncForPC

Span: Go 1.0 → Go 1.4 · Status at go1.17: pclntab is still the design (format changed in 1.2 and 1.16/1.18) → continues as Symbol tables, runtime.Caller and CallersFrames (1.18+)

Continues in: Tracebacks, symbol tables and runtime.Callers/CallersFrames (Go 1.5 → Go 1.17)

Design. From 2008 the linker emits a pc/line table and a symbol table read directly by the runtime (symtab.c, a5433369aa). Early fixes deal with whole-package compilation producing symbol-table “history stacks” (84852fe220), the first function’s line (a343e5ceb1), and call-by-return functions (63810f840f). 610757b155 (2012) deletes a duplicate pcln walker; 091191336a, cf37254b1c fix crashes in Caller.

In Go 1.1, c8dcaeb25d re-encodes the table “without changing semantics”: symbol addresses become full-pointer-size host-endian words (so the host linker, gcc, can fill them and pick addresses above 4 GB) and other values (frame sizes) become varints; for godoc on OS X/amd64 the table shrinks 8% (1,546,276 to 1,425,668 bytes). 46d7d5fcf5 hides the symbol table from the GC; 83445fdcc3 (1.2) uses persistentalloc for the symtab to reduce heap. In Go 1.4 symbol handling moves to Go: 0be59730fd adds symtab.go (FuncForPC, Func.FileLine etc.), 97f8386af7 converts symtab.c, 3d128dbf96 removes holes in FUNCDATA tables, 3e62d2184a fixes an endianness assumption decoding ftab. a5a0733144/a22c11b995 define when callers subtract 1 from PCs.

Timeline.

Signals: handling, os/signal, foreign threads

Span: Go 1.0 → Go 1.4 · Status at go1.17: still the design (handlers installed with sigaction, sigqueue for os/signal) → continues as Signals and async-preemption machinery (1.18+)

Continues in: Signals: masks, forwarding, sigaltstack and the unified Unix signal layer (Go 1.5 → Go 1.17)

Design. Signals appear in 2008 as a fault-reporting mechanism (aeb43984ec, 7b210c5b2f, 3311e1b673 dump registers) and become a panic mechanism in 2010: 5963dbac08 turns divide-by-zero and nil dereference into runtime panics via a sigpanic the handler pushes on the faulting goroutine’s stack; the g at signal time is passed to the handler (690291a2c0); a67258f380 fixes proper signal handling (no init on g0) and 4aaf948f90 masks signals during the handler on OS X. b5866494ea (2009-12) adds os/signal; 08579c26dd makes the runtime crash on most signals unless os/signal is imported; 807605d0fc catches all signals only if os/signal is in use; 35586f718c (2012-02) adds selective handling; cb4428e555 (1.1) adds Stop and care with SIGHUP; 3d8ebefbbe permits default behaviour of SIGTSTP/TTIN/TTOU; b9e051e82d ignores SIGSETXID (33) on GNU/Linux; d9fc789df2 makes SIGSYS notifiable on FreeBSD. The signal queue becomes lock-free in Go 1.1 (91484c6c48 fixes a sigqueue crash) and is converted to Go in 1.4 (651d0cf204, redone as 81ed684a71).

Signal masking is a recurring bug source: 240b1d5b44, 224f05ba88 (2012-02) mask signals around thread creation, 1fc9a17c7e/9eeb90945e do the same in runtime/cgo, b0891060ae (FreeBSD “double fault” if a signal lands before minit installs the handler), 098b9dcf2f (openbsd), 4eb7ba743d (darwin: sigprocmask is process-wide, so two concurrent thread starts could block all signals forever, including SIGSEGV for nil derefs), 41183d015d replaces sigprocmask by pthread_sigmask in cgo, 699dbb60b7 always sets the signal mask before pthread_create.

Mixed-language programs (cgo, SWIG, C++ threads) forced handling for threads without an M: 532dee3842 (1.1) discards SIGPROF on non-Go threads through runtime.badsignal(int32); b23691148f and e49f945603 print an error for other signals; 2f1ead7095 (1.2) “correctly handle signals received on foreign threads” by queueing them to sigqueue instead of crashing, deleting about 160 lines of per-OS code; f322c78692 fixes a crash in badsignal caused by a split prologue; ae73b90369 makes ARM call badsignal. e56c6e7535 (1.3) adds debug.SetPanicOnFault so a program can recover from memory faults (mmap’d files, address probing), hardened by 1569628725.

Timeline.

Threads, locks and the OS scheduler interface

Span: Go 1.0 → Go 1.4 · Status at go1.17: still the design (futex/sema based lock_futex.go, lock_sema.go) → continues as Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN (1.18+)

Continues in: Thread lifecycle, physical page size and scheduler-facing OS hooks (Go 1.5 → Go 1.16)

Design. Early threading was per-OS: clone+futex on Linux (770b872752), Mach bsdthread_create on darwin, semaphores. 5ff12f871f (2008) uses kernel semaphores only under real contention; 0365b989a4 allocates Mach semaphores lazily. efc86a74e4 makes $GOMAXPROCS mean number of CPUs running Go code rather than number of threads.

In 2011 Dmitriy Vyukov reworked the locks: 4e5086b993 (Linux) is a hybrid spin-then-block mutex designed because runtime critical sections are tiny and pure blocking mutexes cause context switches; ee24bfc058 unifies mutexes across OSes into two generic implementations, lock_futex and lock_sema, with each OS supplying a few callbacks (futex wait/wake, semacreate/semasleep), eliminating channel finalizers (d770aadee5 removed them first for Linux/FreeBSD/Plan 9; chan creation fell from 1132 to 381 ns). On Darwin the old lock made BenchmarkChanContended-2 take 6735 ns versus 263 ns afterwards. openbsd locking 060ffabd18; ae9b661fa8 uses private umtx ops on FreeBSD; 9fe8681df6 UMTX_OP_WAIT_UINT; 8ba5c5593f works around a Linux futex kernel bug.

Thread-start protocol: d1bafffa4b lets goroutines run during init and 6808da0163 then locks the main goroutine to the main OS thread during init (programs wanting this for main.main call LockOSThread). In Go 1.1 e6a3e22c75 starts all threads with runtime.mstart, a0955a2aa2 splits minit into mpreinit (on parent, may allocate) and minit (child, cannot), and 1e063eea38 prepares M’s without an mcache for the new scheduler. Other OS-facing work: 4f308edc86 uses sched_getaffinity for NumCPU; 0629354bd3 handles out-of-threads on Linux; a574822f80 disables Windows dynamic priority boosting; OpenBSD thread work (8aa2591b13, and 689d5b9163 using __tfork).

Timeline.

cgo runtime support and non-Go threads

Span: Go 1.0 → Go 1.4 · Status at go1.17: still the design in outline (cgocall/asmcgocall, cgocallback, extra M) → continues as C-to-Go callbacks: extra-M reuse, system-stack bounds, init gating (1.18+)

Continues in: cgo runtime support: pointer checks, callbacks, extra Ms, C-to-Go call path (Go 1.5 → Go 1.17)

Design. cgo begins in 2009 (fdc4b4a47f FFI start, 12fc217336 checkpoint, 133a158bd8 “cgo working on darwin+linux amd64+386”, with 8c/8l dynamic loading and a libcgo). In 2010 b07b04d35f takes over as runtime/cgo, replacing libcgo (98b2d7062e), adds export (2d0ff3f1a6, 2e20386fc7) and SWIG callbacks (17d50d8664). The central design, from f9ca3b5d5b (2011-03): scheduler/cgo reorganization introduces runtime.mcall(f), which runs f on the m->g0 stack and replaces the scheduler loop (gosave/gogo), eliminating fake states (Grecovery, Gstackalloc); the Go->C path is cgocall -> asmcgocall, the C->Go path is cgocallback -> cgocallbackg running on the m->curg stack accounted against $GOMAXPROCS (between exitsyscall and entersyscall). The old scheme broke because gogo back into the scheduler loop used a lowered g0->sched.sp that no longer pointed at a valid frame. 72e83483a7 speeds a cgo call from 669 to 330 ns by allocating the Defer on the stack.

Go 1.1 makes C threads first-class: 86d509b463 and 6c976393ae (“allow cgo callbacks on non-Go threads”, 448 additions across 16 files) let a thread created by C call into Go by borrowing an M (needm/dropm), with signal stack handled via signalstack(p==nil); 3d2dfc5a7b adds cgocallback_gofunc taking a Go func value; e9a3087e29 tracks memory allocated by non-Go code so a poorly timed GC cannot collect it. 1.2 refines: 34c67eb24e creates the extra M lazily, on first cgo call, so deadlock detection works for programs that import net or C but don’t use them (and under the race detector); 397ba2cb4a/647eaed93b wrap C.malloc. 1.4 rewrites stubs: 54138e1ac3 generates cgo wrappers in Go, cb767247ca refactors asmcgocall, 47f251c1ce and 1b6807bb06 handle G stack copies during cgo (return value location), c81a0ed3c5 replaces C callbacks, e1364a6d0e fixes cgo_topofstack, 685204747d fixes _cgo_allocate(0), c4efaac15d unrecovered panic on an external thread.

Timeline.

TLS, g/m registers and shared-library linking

Span: Go 1.0 → Go 1.4 · Status at go1.17: still the design in outline (g in TLS or a reserved register) → continues as Library entry points and runtime initialization (1.18+)

Continues in: Shared-library and buildmode entry points (c-archive, c-shared, plugin, shared) (Go 1.5 → Go 1.17)

Design. From the start g and m were “extern register” slots (TLS on x86, R9/R10 on ARM). 386 first used %fs, then bbcb91a3a7 (2009) moved to %gs as required for NaCl and standard for TLS. On Linux/386 2d6ae385e1 uses an Xen-friendly ELF TLS sequence; on Darwin 386, 2f0cae46d8 replaced i386_set_ldt by thread_fast_set_cthread_self; Windows used ArbitraryUserPointer (9569c67a6b). 1564b984a5 removes a duplicate GS setup. In Go 1.1 36b414f639 and dfc22e29ec change the amd64/386 startup convention so _rt0_$GOARCH_$GOOS sets DI=argc, SI=argv and jumps to _rt0_amd64, matching the C main signature, so Go can be linked against a libc main.

Shared libraries: 45233734e2 (1.2, Elias Naur) merges runtime.g and runtime.m TLS pseudo-symbols into runtime.tlsgm, moves TLS code from gcc-compiled to internally-compiled code using MRC on ARM with 5l fallbacks, adds -linkmode external to 5l, and implements -shared (6l emits a shared library): TLS uses the initial-exec model (faster than local dynamic, available because Linux reserves static TLS space), .init_array registers the Go runtime with cgo at load time, and cgo functions get “hidden” visibility to avoid GOT calls. e0db7fae87 adds .note.GNU-stack. Android (1.4): a36348008c adds GOOS=android (linux treated as android for file matching), and 12b990ba7d “TLS emulation for android”: since Linux’s linker TLS relocation and Darwin’s fixed pthread key slot both fail late in an Android process, the runtime calls pthread_key_create, takes the first slot, and stores it in runtime.tlsg, a normal variable. 89f185fe8a removes the m register entirely (code uses g->m), freeing ARM R9, required because NaCl ARM cannot use R9; benchmarks showed no change (Go 1 macrobenchmarks within noise). b4bfa6c964, 4769f87e0d, d8fd8d89ea (1.2: use m and g names instead of R9/R10 in ARM assembly), 0b36211cfb.

Timeline.

Clocks, vDSO and timers

Span: Go 1.0 → Go 1.4 · Status at go1.17: vDSO clock_gettime still used on Linux → continues as vDSO, vgetrandom and fast clocks (1.18+)

Continues in: vDSO, time sources and process-wide randomness (Go 1.5 → Go 1.17)

Design. Time source work, mostly for time.Now and runtime.nanotime. c19b373c8a (2011) adds CPU profiling timers via setitimer (8dee872963). 3b860269ee adds runtime timers for package time. Go 1.1 brings fast paths: 3a66bc415e/c7be4defe3 read OS X time from the comm page, cutting BenchmarkNow from 197 to 33 ns (amd64) and 364 to 38 ns (386), arguing it also makes CPU profiles more accurate because OS X delivers profiling signals on exit from system calls; 024a92c1da and 4022fc4e21 call the Linux vDSO (gettimeofday, then clock_gettime, giving true nanosecond resolution, 27.4 to 24.4 ns), with 2637777527 duplicating nanotime to avoid vDSO stack overflow on a small stack; 7777bac6e4 uses clock_gettime for Linux 386/arm, FreeBSD, NetBSD, OpenBSD. Go 1.3 introduces monotonic clocks for timers (86c976ffd0 linux 386/amd64, 7206f50f71 arm, 36013e4a22 FreeBSD, 3734663805 OpenBSD, b4dc91e386 dragonfly); 350a8fcde1 restores Unix time for MemStats.LastGC after that change. Go 1.4: 74b62b4864 monotonic on Windows, 13d0b82bc8 time.now in assembly on plan9/solaris/windows, 1db4c8dc41 fixes vDSO lookup to use the dynamic hash table, 2fe9482343 revives fake time for nacl, with 138b5ccd12 disabling it in some cases.

Timeline.

Windows runtime port

Span: Go 1.0 → Go 1.4 · Status at go1.17: still VEH-based exception handling → continues as Windows runtime port (1.18+)

Continues in: Windows runtime (Go 1.5 → Go 1.17)

Design. Windows 386 (6bfe5f55f4, Hector Chu, 2010-01: 600 lines) is a Windows-DLL based port using syscall stubs; e00795859b adds mingw-style syscall to call DLLs; scheduler runs on the OS stack during syscalls (c1e20720f7, f95a2f2b97). Exceptions: aae5f91213 (2011-01) implements SEH; f0b8f84d37 adds callbacks (syscall.NewCallback); 1723fbe13e a console ctrl handler for SIGINT; f83609f642 adds windows/amd64 (2011-06) with Win64 stack alignment fixes (a6e60916c1, fbdec642a9) and 9b011500c0, acfb0a1584, e753512e2d for exception handling, the ctrl handler and cgo callbacks. In Go 1.3 Russ Cox found that SEH cannot work with Go’s stack usage: records must live on the OS stack, are interpreted by position, and need active maintenance at cgo switches (1249d3a518 fixes a dangling-SEH bug after Go->C->Go panics). 3750904a7e and a837347dd9 replace SEH by vectored exception handling (VEH), “like a Unix signal handler”, dropping 7 #ifdefs; side effect: windows/386 requires Windows XP or later (Windows 2000 dropped). 2dc7552f57, a1778ec146 ignore exceptions from non-Go code / foreign threads, 17a108ba07/e9ecd4aec5 handle all exceptions.

Windows also gets runtime-integrated networking: ea15104110 (a hint to the scheduler when GetQueuedCompletionStatus will block, for BenchmarkTCP4Persistent 4004000 to 33906 ns), 6ea7bf253c moves netpoll into the runtime, 65834685d3 uses GetQueuedCompletionStatusEx if present, and 277a7b22f1 tolerates its absence. Callback and syscall plumbing: 4216203bcf removes then 7f8a5057dd restores NewCallbackCDecl; 05a5de30f0 stops generating code at runtime in NewCallback; 1.4 38ce599494 converts NewCallback to Go, 2eccf0d18f converts syscall_windows.c to Go, 73f5010dc6 allows calling stdcall from Go, 201cfe4afb runs the sighandler on g0; a574822f80 disables dynamic priority boosting (2+ s timer starvation on a uniprocessor server).

Timeline.

Platform ports: OS and architecture matrix

Span: Go 1.0 → Go 1.4 · Status at go1.17: these ports persist except plan9/solaris variants carried through later work; nacl removed later → continues as New, ported and removed platforms (1.18+)

Continues in: New and ported OS/arch targets, 1.5–1.17 (Go 1.5 → Go 1.17)

Design. The runtime is split into architecture-dependent and OS-dependent files since 2008-06 (aeb43984ec “factor into architecture-dependent and -independent pieces. ditto OS”), reorganized into amd64/, darwin/, linux/ subtrees in 2009-03 (fcd76f7dc9, 878822f355, 8ee041dc24) with godefs-generated defs.h (08cfcd1dd6, 209865be7c), later flat os_*.c, sys_*.s, defs_*.h naming (e9d62a6d81, “refactor os-specific code”, 1.1), and dd2abe5152 (2011-11) converts runtime/syscall defs from godefs to cgo. Go 1.0 inherits: Linux/darwin amd64 (2008–2009), 386 (2009-03), ARM (2009-05 onward: 7943556970, be639b9a51, kernel cas 9ad9742157, softfloat 7c2b1597c6 precise float64, Android hardware 52c549fc9b), FreeBSD (0489a260da, b3f538a4fd), nacl (2009-09; removed 2010-12 by 0c54225b51 and restored in 1.3), Windows (above), Plan 9 (99a10eff16), tiny/embedded experiments (da9bc7ae7d, removed 434f1e32a0), OpenBSD (21ac258e07 amd64, 9ef17990c2 386), NetBSD (26089cfe25, 728c16cf13).

Go 1.1 adds NetBSD working (5a043de746, fb32d60cd1, ARM 37aba1aa77), FreeBSD/ARM (366268aa79), Plan 9 64-bit (a72bebf6e1), cgo on NetBSD/OpenBSD/FreeBSD-ARM. Go 1.2 adds dragonfly amd64 (ac00524beb), 386 (465ba6b78c), FreeBSD/ARM EABI (649a2a9be5) and the FreeBSD/amd64 INT 0x80 workaround (555da73c56: the SYSCALL fast path is broken for restarting syscalls, with a C reproducer; filed as PR 182161). Go 1.3 adds Solaris (a46b434931, 50df136483) and Native Client (4a000b9d51, “nacl import round 2”: amd64p32 and x86-32 runtime/syscall support, networking, bootstrap, per the go13nacl design), plus openbsd ABI break (471763f657). Go 1.4 adds Android (a36348008c), Plan 9/amd64 (decd810945, 70f928698b), NaCl/ARM (d1177ed40d), Solaris cleanup (b4c28d9180, e77c2fe313), and GOARM fixes (66795e8ba1).

Timeline.

Integrated network poller: OS back-ends

Span: Go 1.1 → Go 1.4 · Status at go1.17: still the design (epoll, kqueue, IOCP, solaris ports) → continues as Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN (1.18+)

Design. The OS-facing half of the netpoll that Dmitriy Vyukov added with the new scheduler: netpoll.goc (common) plus netpoll_epoll.c, netpoll_kqueue.c, netpoll_windows.c, netpoll_stub.c. 0bee99ab3b (Darwin) and 49e0300854 (Linux) land together in 2013-03: BenchmarkTCP4Persistent falls 93% on linux/amd64 (115647 to 7782 ns) and 51% on darwin; at high thread counts OneShot got 30% worse (16/32 threads). ARM follows (1d64d04da5, -38% to -43% OneShot), then FreeBSD/OpenBSD (c5732c8526), NetBSD (c4cdd35e6e), FreeBSD/ARM (e82614e5be), Windows (6ea7bf253c, 7-9% faster, 65834685d3 batch dequeue). In 1.4 91a670d179 converts common code, epoll and kqueue to Go (Windows and Solaris stayed C);

Timeline.

Runtime C-to-Go conversion and the Go calling convention in C (1.4)

Span: Go 1.4 → Go 1.4 · Status at go1.17: completed; no C left in the runtime → continues as Library entry points and runtime initialization (1.18+)

Continues in: C-to-Go conversion of the runtime’s OS, arch and signal layers ([dev.cc]) (Go 1.5 → Go 1.5)

Design. In 1.4 the runtime starts being rewritten in Go. The enabling step is 25f6b02ab0 (2014-08-27): the C compilers adopt the Go calling convention (results on the stack after arguments, except ... functions), making it possible to rewrite C functions one at a time “without worrying about which languages call that function”. All runtime assembly was updated from 40(SP) to name+10(FP) references with Go prototypes for every assembly function, so go vet runtime checks them; vet found many wrong MOVL/MOVQ widths in the openbsd and other less-used ports. 012ceed914 makes onM and mcall take Go func values; 4aa50434e1 adds gomallocgc; d4df63c3e8 converts type algorithms (alg.print and alg.copy deleted); 3a7f6646cf locks; 50199d7b35 env; 47d6af2f68 chanrecv; 39ffa8be78 Stack; b4c28d9180 syscall_solaris; 91a670d179 netpoll; 7ba41e9972 traceback bits; 9a75c74836, f611ae1c75 generate Go struct definitions from C headers; 0a7c7ac80e makes g->atomicstatus atomic with a Gscan bit to support the concurrent GC. Supporting conventions: cb040d59b9/e8fc362049 use textflag.h, 99f7df0598, 193daab988.

Timeline.

Startup, auxv, randomness and security-adjacent runtime details

Span: Go 1.0 → Go 1.4 · Status at go1.17: still present → continues as Security hardening: AT_SECURE, std fds, AT_RANDOM, gated debug features (1.20+)

Continues in: CPU feature detection (internal/cpu), auxv parsing and GODEBUG=cpu.* (Go 1.9 → Go 1.17)

Design. a642ca4930 (1.1) decodes the ELF auxiliary vector on Linux/ARM (AT_RANDOM, AT_HWCAP, AT_PLATFORM) to seed the map hash with a per-process random number (only AT_RANDOM used), d1e06dab7c removes an alignment assumption; 8e765da941 seeds fastrand; a5d4024139 and db53d97ac4, ee66972dce in the hash function (AES hash) are perf details. 5146a93e72 GOTRACEBACK. 44fd1d1a6a gives clear messages for DYLD_INSERT_LIBRARIES on Darwin.

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.0 · 1.1 · 1.2 · 1.3 · 1.4

OS interface, stacks and panics (Go 1.5–1.17)

Reader: Claude Sonnet. Cites 1,427 of 1,750 commits in this slice; every non-noise commit is cited, plus 15 larger commits the noise filter had set aside, which a separate gap reader described. Read the design-heavy messages and subject lines. The “Other notable changes” section was generated from the subjects of leftover commits, so its items are lists rather than explanations.

Release digest for this slice
  • Go 1.5 — The all-Go runtime ([dev.cc] conversion), the power64/ppc64, arm64, darwin/arm(64), Android, openbsd/arm ports, GC stack barriers, the first signal-mask policy and forwarding, shared-library entry points and the first cgo pointer plumbing.
  • Go 1.6 — cgo pointer checks (GODEBUG=cgocheck), GOTRACEBACK=single, runtime/internal/{sys,atomic}, the pcvalue cache, mips64 and android/{386,amd64,arm64} ports, c-archive/c-shared signal handling and addmoduledata for shared builds.
  • Go 1.7 — concurrent-safe stack shrinking during concurrent mark, s390x port, extra-M batching for cgo callbacks, plan9/arm, symbolic C backtraces, set_thread_area on 386, signal names in panics.
  • Go 1.8 — cheaper defer (-42%) and defer-free cgo calls, mips/mipsle, plugin runtime support, unified Unix signal code, libc sigaction under cgo, physical page size query, SIGPIPE in libraries.
  • Go 1.9 — inline-aware tracebacks/Callers/CallersFrames, stack barriers removed, monotonic time.Now, internal/cpu created, 2 MB Windows stacks, allocManual.
  • Go 1.10 — threads can exit, vDSO on linux/386 and amd64 on large stacks, DT_GNU_HASH, unified entry points, previous-handler signal death and late handler forwarding, never allocate in unrecoverable panic.
  • Go 1.11 — Darwin/iOS syscalls through libc, js/wasm, debugger call injection, VDSO traceback, vDSO on arm/arm64, internal/bytealg, GODEBUGCPU experiment, graceful g0 overflow handling.
  • Go 1.12 — stack objects, windows/arm and AIX ports, GODEBUG=cpu.*, libSystem-backed syscall on macOS, MADV_FREE, ABI hygiene for runtime symbols.
  • Go 1.13 — aix/ppc64 completed, netbsd/openbsd arm64, illumos, TOPFRAME, PCDATA/FUNCDATA re-ordering, Android TLS offset made dynamic, physHugePageSize.
  • Go 1.14 — signal-based asynchronous preemption on all main archs, open-coded defers’ runtime support, freebsd/arm64 and linux/riscv64, NaCl removed, signal-stack mlock workaround.
  • Go 1.15 — conservative-scanned debug calls, darwin/arm and 386 removed, open-coded defer stale-entry fix, larger stack growth for large frames, Plan 9 env cache.
  • Go 1.16 — C→Go call path simplified, GOOS=ios, openbsd/amd64 and arm64 on libc, syscall.AllThreadsSyscall, high-resolution Windows sleep, mlock workaround reverted, M-reentrant nanotime1.
  • Go 1.17 — register-ABI preparations in assembly, windows/arm64 port, cgo.Handle, openbsd/386 and arm on libc, SPWRITE/TOPFRAME-driven traceback, regabidefer prerequisites.

C-to-Go conversion of the runtime’s OS, arch and signal layers ([dev.cc])

Span: Go 1.5 → Go 1.5 · Status at go1.17: done in 1.5; the all-Go layout (os_$GOOS.go, signal_$GOOS_$GOARCH.go, sys_$GOOS_$GOARCH.s, defs_*.go, one sigctxt per OS/arch) is still the structure at go1.17.

Earlier history: Runtime C-to-Go conversion and the Go calling convention in C (1.4) (Go 1.4 → Go 1.4)

Design docs & discussion

Design. The [dev.cc] branch removed all C from package runtime, so the Plan 9 C compiler (cmd/cc) was no longer needed to build it. Russ Cox’s bulk conversions were done with an automated tool “and then modified only as necessary to make it compile and run”: panic and stack code (panic.c, stack.c to Go, d98553a727), runtime/cgo (9f99d531a0), operating-system support code (e785e3acf8), arch-specific .c/.h (9204821731), defs_$GOOS_$GOARCH.h to defs_*.go (580ef3e4af), and assembly adapted to the Go-built go_asm.h (15ced2d008). The signal handlers overused macros and could not be converted automatically; a sigctxt type was defined per OS/arch with a common implicit interface used by the arch-independent handler (2d917c0c26). cmd/dist stopped generating special headers, compiles Go sources before the rest of the package so the compiler can write go_asm.h, and TLS information moved into src/runtime/go_tls.h (0d49f7b5fc).

Each OS port was then converted on its own: FreeBSD (aac17fd4e1), Solaris (e088e16256; memory management consolidated with the BSDs, assembly thunks replaced by the new //go:linkname; the first step built but did not run because binaries no longer linked libc), openbsd/amd64 and 386 (ba603c3100, 9ad6b7e322), dragonfly (1d05d5880e, 6ddc2cb80c), netbsd (cfc8099a9a, 0d76887433, 7d516079de), Windows (b27c0618eb, ab4578adef), NaCl (ad8179281d), Plan 9 (e9c57d8a2d), Android/ARM (d3526ea0f6). GOOS/GOARCH-specific constant files are now produced with go generate (0fe444d3e8). Residual C-isms were then deleted: strcmp-style helpers (7b36227002), unneeded C headers (57cefa657d), thunk_windows.s (20dce04742), cgocall/asmcgocall (2858b73843), cgo_allocate (6a2b0c0b6d), gogetcallerpc/gogetcallersp (7abdc90fe3), and the stackguard1 field that had distinguished C code on Go stacks (ab0535ae3f).

Timeline.

New and ported OS/arch targets, 1.5–1.17

Span: Go 1.5 → Go 1.17 · Status at go1.17: linux/{ppc64,ppc64le,arm64,mips,mipsle,mips64,mips64le,s390x,riscv64}, android/{386,arm,arm64,amd64}, js/wasm, aix/ppc64, darwin/arm64 (iOS) and ios, illumos, freebsd/arm64, netbsd/arm64, openbsd/{arm,arm64,mips64}, plan9/arm exist; nacl, darwin/arm, darwin/386, dragonfly/386 removed; windows/arm64 landing → continues as New, ported and removed platforms (1.18+).

Earlier history: Platform ports: OS and architecture matrix (Go 1.0 → Go 1.4)

Design. A port adds rt0_*.s, asm_$GOARCH.s, sys_$GOOS_$GOARCH.s, defs_*, a sigctxt in signal_$GOOS_$GOARCH.go, and an os_$GOOS.go with osinit/newosproc/minit. From 1.6 per-arch constants live in runtime/internal/sys and atomics in runtime/internal/atomic (67faca7d9c, 432cb66f16, d3498c5abd, 80d0b98d80). Each new port repeats the same checklist: link-register frame rules, memory barriers in cas, cputicks, startup randomness from auxv AT_RANDOM, vDSO time, signal frames, cgo (crosscall2, _cgo_sys_thread_create), TLS, addmoduledata for shared builds, and later async-preemption injection.

Timeline.

Goroutine stack growth, shrinking, GC stack barriers and stack objects

Span: Go 1.5 → Go 1.17 · Status at go1.17: still the design at go1.17: contiguous stacks copied by copystack, shrunk during concurrent mark, stack barriers gone, stack objects used for address-taken locals → continues as Stack growth, stack bounds and the crash stack (1.18+).

Earlier history: Segmented stacks to contiguous (copied) stacks (Go 1.0 → Go 1.4) · Stack-split prologue, nosplit budget and C-on-Go-stack detection (Go 1.0 → Go 1.4)

Design docs & discussion

Design. After the 1.3 contiguous-stack switch, 1.5 added GC stack barriers to avoid re-scanning whole stacks at mark termination: during the first scan, at exponentially spaced points, return PCs are overwritten with a stub (stackBarrier) that records how far the stack has unwound and jumps to the saved PC; the second scan only goes as deep as the lowest barrier that has not been hit (faa7a7e8ae). A 50 MB-heap deep recursion that cost ~150 ms to rescan fell to 10–20 ms mark termination (goscheme from 100–500 ms). The barriers reserved part of every stack (firstStackBarrierOffset), which interacted with shrinking (7387121ddb accounts for the guard when halving) and with cgo (e2bb03f175 forbids a barrier in cgocallback_gofunc’s frame because that slot holds sched.pc). In 1.7, shrinkstack was made safe against concurrent channel operations by locking all channels the G is blocked on and CAS-ing pointers in send/receive slots (276b177771), and shrinking moved from STW mark termination (~5 ms/MB copied + 2 µs/shrink) to the concurrent mark phase (f11e4eb5cc, da153354b2). aed861038f (1.7) adds a stack free list, and 0cbf8d13a7 (1.6) reuses large stack spans (a program that grew to 1 GB stayed at ~30 MB).

Stack barriers became unnecessary once the hybrid write barrier meant stacks are not rescanned, and were removed in 1.9 (d089a6c718, ~1100 lines). Stack-copy cost work: pcvalue cache (see tracebacks), faster readvarint/stackmapdata (aafd96408f), iterating only set bits in adjustpointers (5af0b28a73, 2e7e57770c, 31cfa7f2f2), no write barrier in adjustpointers (a5c3bbe0b4) or in newstack/gogo (shading g.sched.ctxt at scan instead, 3beaf26e4f), and rewindmorestack removed because per-PC SP deltas let the prologue’s re-jump be recorded as frame size 0 (4c9a372946). Go 1.12’s “stack objects” (cbafcc55e8) record every address-taken local in a FUNCDATA and compute liveness at scan time by marking from pointers found in the frame and defer records, replacing “ambiguously live” conservativeness. 1.17 changes growth sizing (b1760f3a27 from 1.15 grows more than 2x when the frame is large; 287c5e8066 fixes the growth loop so copystack is not called redundantly).

Timeline.

Tracebacks, symbol tables and runtime.Callers/CallersFrames

Span: Go 1.5 → Go 1.17 · Status at go1.17: gentraceback is still the single stack walker, now inline-aware, TOPFRAME/SPWRITE-driven → continues as Unwinder: gentraceback → unwinder iterator, inline unwinder, pcvalue cache (1.18+) and Symbol tables, runtime.Caller and CallersFrames (1.18+).

Earlier history: Tracebacks, crash output and GOTRACEBACK (Go 1.0 → Go 1.4) · Symbol tables, pclntab and runtime.Caller/FuncForPC (Go 1.0 → Go 1.4)

Design. gentraceback walks frames using the _func table, a per-PC SP-delta pcvalue stream and PCDATA/FUNCDATA (stack maps, args maps). Cost is dominated by pcvalue decoding; since big stacks usually repeat a few recursive functions, 1.6 puts a small randomly-replaced cache in front of the lookup, threaded from stack scan, adjust and traceback (beedb1ec33); 1.12 refines it to [2][8] entries with a cheap associative index, inserting new entries at the front for recursive stacks (7d6b5e340c).

In 1.9 the compiler’s inlining tables are consumed: tracebacks show inlined frames (781fd3998e), runtime.Callers counts logical rather than physical frames by encoding a partial skip in a pseudo-PC skipPleaseUseCallersFrames+n stored in pcs[1] (ee97216a17), CallersFrames expands inlined calls (7bf0adc6ad, iteratively 7f32d41e5d, non-escaping variant 38521004ed), and FuncForPC behaviour with inlining is documented (d58125ecd2, 58ae050055, a776087ee3). 1.10 hides <autogenerated> wrapper methods (e97209515a). 1.13 makes Frames.Next delay file/line lookup (c77a9e0aa5), and re-orders PCDATA/FUNCDATA indices by use frequency, shrinking binaries ~0.5% (4aeac68c92); 1.16 compacts the FUNCDATA indices (f96b62be2e). 1.13 and 1.17 add the TOPFRAME function attribute: runtime.goexit marks the top of stack on link-register archs (aafe257390), 1.17 uses it for all top-of-stack functions and moves mstart to assembly (54da3ab385, a1ffbe9c69, 5ecd9e34df), and replaces the “clever” prediction of whether a profile traceback is safe with a FuncInfo SPWRITE flag (fbe74dbf42). 01f05d8ff1 makes asmcgocall save traceback context like systemstack, removing a special case (1.17). GOTRACEBACK=single becomes the default (bf1de1b141, 1.6) with none/single/all/system/crash names.

Timeline.

Defer, panic, recover and Goexit (runtime side)

Span: Go 1.5 → Go 1.17 · Status at go1.17: three defer kinds (heap/stack records via deferproc/deferprocStack, open-coded defers via funcdata bits), all processed by gopanic with a _defer chain; GOEXPERIMENT=regabidefer makes deferred calls func() → continues as Defer, panic and recover re-implementation (1.18+).

Earlier history: Panic, defer, recover and Goexit (Go 1.0 → Go 1.4)

Design docs & discussion

Design. Until 1.13 every defer was a heap-chained _defer record with a copied argument frame. 1.8 made the common path cheap: deferproc/deferreturn no longer switch to the system stack on every call (only the slow allocation/free paths do), they are nosplit so preemption and stack growth cannot intervene without acquirem/releasem, and zero-size and pointer-size frames are special-cased (f8b2314c56: runtime defer benchmark -42%, Lock/defer-Unlock overhead 61.4 ns to 27.9 ns; getcallersp as Go helps, d211c2d377). Cgo calls dropped the defer that unwound endcgo, folding it into the callback defer (441502154f). Go 1.14’s open-coded defers (compiler/linker/runtime change that also added funcdata FUNCDATA_OpenCodedDeferInfo, be64a19d99/dad616375f, outside this slice’s list) make the common case inline code; the runtime side is that panic/Goexit must find those frames: gopanic scans the stack for frames with the funcdata, adds a _defer entry on the fly for each (addOneOpenDeferFrame) so open-coded and record defers run in correct order and a “started” state can be stored; on recover the stale entries have to be removed from the chain. Fixes during 1.14/1.15/1.16 show the subtlety: newdefer must not link the record to g0 when called on the system stack (825ae71e56), repeated panic/recover/re-panic left stale entries (fae87a2223), nil deferred function must fault in deferreturn() (22d28a24c8), dumpgoroutine (1f3339f441), and in 1.16 entries must be removed in all cases after recover, aborting the removal loop at any started defer (a5a5e2c968, which explicitly leaves bug 43920 “a more complex fix” open). With GOEXPERIMENT=regabidefer (1.17) deferred functions have the fixed signature func(), so reflectcall of defers becomes a direct closure call (c847932804), frames of defer/go are asserted empty (e0ce0af6ef), and the deferreturn dummy argument is dropped (585b52261c).

Crash handling: startpanic_m never allocates (2edc4d4634), fatalpanic is split into fatalthrow and fatalpanic and the final crash performed outside systemstack so debuggers see a sane stack (b1d1ec9183), and unrecoverable panic handling runs on the system stack (9d59234cbe, 7f1b2738bb, 60a9e5d613). throw on out-of-range index inside runtime (f43aa1df70), and runtime.Goexit uses goexit1 (ad731887a7).

Timeline.

Signals: masks, forwarding, sigaltstack and the unified Unix signal layer

Span: Go 1.5 → Go 1.17 · Status at go1.17: one signal_unix.go with sighandler, sigtrampgo, adjustSignalStack, sigfwdgo; libc sigaction under cgo; M-targeted signals for preemption → continues as Signals and async-preemption machinery (1.18+).

Earlier history: Signals: handling, os/signal, foreign threads (Go 1.0 → Go 1.4)

Design. The policy settled in 1.5: a Go program always needs synchronous signals (SIGSEGV, SIGBUS, SIGFPE, the _SigPanic set) enabled; signal.Notify needs at least one thread with the signal unblocked. msigsave saves each thread’s mask in m.sigsave at needm/newm; minit unblocks only essential signals (_SigUnblock: synchronous ones, SIGILL, SIGTRAP, SIGPROF, SIGSTKFLT), unminit restores the saved mask, and a helper thread is started on first Notify to keep notifications deliverable (84cfba17c2); a follow-up unblocks glibc’s setxid/cancel signals (ffd7d31787, 675eb72c28). Programs started with SIGHUP/INT/QUIT/ABRT/TERM blocked now ignore them. 1.5 adds signal forwarding to a handler installed before Go’s: forwarded iff a non-SIG_DFL handler existed, the signal is synchronous (SEGV/BUS/FPE), and it occurred on a non-Go thread or in cgo code (5c8fbc6f1e, Linux; darwin/amd64 a5b693b431), with SIGSETXID/SIGCANCEL on the signal stack (675eb72c28) and re-raise on non-Go threads (872b168fe3). 1.6 extends this to c-archive/c-shared (no unnecessary handlers fbdfa99246, synchronous install 21b4f234c7, restore original disposition a7d2b4d7ce, e24b2445b4), preserves/adjusts the signal stack on C threads (a7cad52e04, f7e51c1320, f8e6418637), and fixes exit status when killed by a signal (2c67c8c303). 1.8 collapses duplicated per-OS handlers into signal_unix.go (9e028b70ea, ab552aa3b6, 159a90b93a, 343bec53c7, d15295c679, c2735039f3, ac24388e5e, e2e11f02a4, 097a581dc0, fdc167164e, f05cd4cde5, eb268cb321), and uses libc sigaction under cgo (1f605175b0, fe057c1478); SIGPIPE is handled in c-archive/c-shared and non-Go SIGPIPE forwarded (d24b57a6a1, refined 78074f6850). 1.9 uses atomics for fwdSig and an immutable sigtable (87ad863f35); 1.10 forwards crashing signals to late-registered handlers and makes the process die via the previous handler so Crashlytics-style handlers see SIGABRT (5500c9ce27, c3189cee71), restores the Go-allocated gsignal stack in unminit after an OS-provided one replaced it (292558be02, which on 1.9 meant two handlers on one stack), and never frees OS-created signal stacks (44d9e96da9). 1.14 makes Go always use a Go signal stack in non-cgo programs (73d20f8186), factors sigtrampgo (d29c14f3d2) and crashes if a signal arrives with a bad G and no extra M (03c58c9dcc); a kernel bug in Linux 5.2–5.4.1 corrupting vector registers when the signal stack was not faulted in is worked around by mlocking the top of every signal stack (8174f7fb2b, 1c8d1f45ba, 69614c0d0e, 9d4717d5f1), made non-fatal (b851e51160, 71218dbc40) and reverted in 1.16 (92bda33d27).

Timeline.

Asynchronous preemption: signal plumbing, injected calls and per-arch support

Span: Go 1.14 → Go 1.16 · Status at go1.17: still the design at go1.17 → continues as Signals and async-preemption machinery (1.18+).

Paths not taken: Loop preemption by compiler-inserted checks and page faults (superseded)

Earlier history: Goroutine preemption (stackguard0, 10 ms and GC preemption) (Go 1.2 → Go 1.2)

Design docs & discussion

Design. Go 1.14’s non-cooperative preemption splits into (1) delivering a signal to the right thread and (2) injecting a call at the interrupted PC. For (1), suspendG marks the G for preemption and sends its M a “wake up and look around” SIGURG; the handler checks whether the M runs a G with a preemption request at a safe point, counting received preemption signals so suspendG can detect a failed attempt and retry without a back channel (62e53b7922). Targeted M signals were added OS by OS: Linux (334291d1f6, tgkill), BSDs (8714e39497), libc-based OSes (42aab4b0af); Windows uses SuspendThread (b89b4623eb). For (2), sigctxt.pushCall pushes a call to asyncPreempt at the signalled site (2d031dc559, 386/amd64 first), also used to clean up preparePanic; asyncPreempt saves all registers; non-amd64 archs must clobber one register to return from the injected call (REGTMP on arm64/mips/s390x/riscv64, CTR on ppc64) so those sequences are marked non-preemptible (ee330385ca restarts/preempts some instruction sequences; 2ff746d7dc ARM, 1b0b980904 ARM64, a930fede73 MIPS/MIPS64, 374c2847f9 PPC64, 933bf75eda S390X, b89f4c6720 riscv64). MIPS CALL is not atomic: a signal can see updated LR with stale PC, so no preempt there (a930fede73). Debugger call injection reuses the same safe-point machinery (see separate thread).

Timeline.

cgo runtime support: pointer checks, callbacks, extra Ms, C-to-Go call path

Span: Go 1.5 → Go 1.17 · Status at go1.17: the simplified C→Go path (crosscall2 → cgocallback → cgocallbackg → _cgoexp_GoF), cgocheck=1 pointer rules, extra-M list with batch creation → continues as C-to-Go callbacks: extra-M reuse, system-stack bounds, init gating (1.18+).

Earlier history: cgo runtime support and non-Go threads (Go 1.0 → Go 1.4)

Design docs & discussion

Design. Pointer-passing rules from proposal #12416 are enforced dynamically: cmd/cgo wraps pointer arguments in cgoCheckPointer and GODEBUG=cgocheck=0 disables it (9dcc58c3d1, 1.6), extended to exported-function returns (0e2c635788), unsafe.Pointer(&s[0]) (d91ec5bb40), strings (e97096661e), C pointers inside the arena range (dd6753a6f2), and finally optimized (e85ffec784, ~30% + 20% + 15%). The runtime cost of entering C was shaved: no defer in cgocall (441502154f), no lockOSThread per call (332719f7ce), correct liveness across the “time warp” of a callback (f6bff1d587). Callbacks from C threads use an “extra M” pool: needm takes one, needextram asks to allocate a replacement, and 1.7 makes it create as many as threads waiting, fixing a thundering herd on FreeBSD (-74% wall time of CgoCallbackGC) (50048a4e8e); dropm ordering fixes on non-x86 (c02aa463db), minimized time between lockextra/unlockextra (efd93a412e), and signals blocked before the M is allocated (368c401164). In 1.16 the C→Go call path was redesigned: instead of cgo generating an argument struct with exactly the Go stack layout and reflectcalling a generated function, cgo now generates a Go function that takes a struct pointer, unpacks it and makes a normal call; cgocallback_gofunc is merged into cgocallback and reflectcall is no longer needed on this path (30c1887873). 1.17 unifies C→Go ABI transitions in macros (7ad496b6f5) and calls cgocallbackg indirectly (d3853fb4e6).

Timeline.

Shared-library and buildmode entry points (c-archive, c-shared, plugin, shared)

Span: Go 1.5 → Go 1.17 · Status at go1.17: per-arch _rt0_*_lib entry points exist for linux, darwin, windows, android, ios; init runs on a new thread and the loader thread returns → continues as Library entry points and runtime initialization (1.18+).

Earlier history: TLS, g/m registers and shared-library linking (Go 1.0 → Go 1.4)

Design. -buildmode=c-archive/c-shared needs the runtime to start inside a host process: the library entry point (_rt0_$GOARCH_$GOOS_lib) creates an OS thread to run the regular runtime init, returns immediately to the loader thread, and the first cgo call waits for init to finish (e8694c8196, linux/amd64 first, then ARM 93644c9118, android/arm d1eee2cebf, darwin/amd64 b0a85f5d93, darwin/arm 6e3a6c4d38, darwin/arm64 e8b7133e9b, linux/386 9c1868d06d, linux/arm64 73ff7cb1ed, ppc64le 8a1dc32447, darwin/386 47ccf96a95). Entry points must save callee-saved registers (357a013060, ca4ea81d86, fb30270037, ea4b785ae0), align the stack (10c8b2374f, 26015b9563, 48754592e0), set R0 on ppc64le (9aea0e89b6), and in 1.10 the three amd64 and 386/arm exe/lib entry points are unified (cf3f771203, 30cb30e596, 378de1ae43, d92aaa9707), with hasmain in moduledata (c58b98b2d6). -buildmode=shared and plugins need addmoduledata (arm64 4e3deae96d, arm 1b4d28f8cf, ppc64le 1ccefcd1b8, 386 09d7de8d61, fixes be0cb9224b, 87a51a0787) and module bookkeeping: activeModules slice swapped atomically because a plugin’s moduledata is visible before initialized (54ec7b072e, f096b5b340), reordering main first (b531eb3062), duplicate itabs (81a61a96c9), module typemaps visible to GC (f4c7a12c2c), non-throwing duplicate open (d8ae2156fe), PIC for vsyscall helper on 386 (bc3f14fd2a, 6056cc5df6, 7af0839e11), PIC rules on ppc64 (6deb3c0619), TLSG_IS_VARIABLE etc.

Timeline.

vDSO, time sources and process-wide randomness

Span: Go 1.5 → Go 1.17 · Status at go1.17: vDSO used for clock_gettime on linux/{amd64,386,arm,arm64,mips64x,ppc64x}; Darwin/Windows use libc/KUSER_SHARED_DATA; fastrand seeded from OS randomness → continues as vDSO, vgetrandom and fast clocks (1.18+).

Earlier history: Clocks, vDSO and timers (Go 1.0 → Go 1.4)

Design docs & discussion

Design. The Linux vDSO ELF parser (vdso_linux.go, originally amd64 only) found __vdso_clock_gettime; 1.10 adds DT_GNU_HASH (9065c3bf34), splits arch specifics (4477107b7b, c14dcfda6b) and brings linux/386 (af40cbe83c: 6.89x faster time.Now, 5–10x for the vDSO path). Calling C vDSO code from a goroutine stack is unsafe: GCC stack probes in hardened kernels need a page of stack, so amd64 vDSO calls switch to g0/gsignal and align the stack (a158382b1c, 1.10); 1.11 records caller PC/SP in m.vdsoPC/vdsoSP so SIGPROF can traceback through VDSO instead of reporting _ExternalCode (419c06455a), then linux/arm (51b027116c) and arm64 (ea59ebd338). 1.12 adds ppc64x (dbd8af7472), 1.14 mips64x (210e3677f9), and ARM/ARM64 save g on the gsignal stack before VDSO calls because the vDSO may clobber the g register (758eb020f7), unless already handling a signal (75c839af22), plus nanotime1/walltime1 made reentrant by saving/restoring previous vdso PC/SP (a93a4c1780, 1.15), frame-pointer and R11 fixes (21713f41d1, 2ee2c6232c, 3eab754cd0), PPC64 r30 clobbering by the kernel’s new C vDSO (16e82be454, 1.17), and a fallback to real syscalls when neither vDSO nor vsyscall exist (3caaaddffd, 1.16; mips64 return-code check df81a15819). Other clocks: Darwin mach_absolute_time for monotonic nanotime (e546b295b8), monotonic+wall fetched together on Darwin and Windows (e4371fb179, with 0e3355903d monotonic time.Now in 1.9), FreeBSD fast clock_gettime incl. HPET (744ccbb852, 58c231f244, a9fc375258), NetBSD/Solaris monotonic (2065685664, 66fcf45477, 2df5cdbadf), clock_gettime instead of gettimeofday on darwin (ae76f6e962), fastrand in Go (d03c124860) and consistent OS-seeded fastrand across archs (97d0505334), startup randomness from AT_RANDOM on arm/arm64/mips64x via a common auxv parser (3aba41d6c3, a2eded3421, 2e32efc44a, c955bb2040, 90addd3d41).

Timeline.

Darwin and OpenBSD move to libc; Solaris and AIX libcall; syscall wrapper layer

Span: Go 1.5 → Go 1.17 · Status at go1.17: macOS and iOS, Solaris/illumos and AIX go through libc via libcCall/asmcgocall trampolines, and openbsd/{amd64,386,arm,arm64} link libc and use pthreads; Linux/FreeBSD still use raw syscalls → continues as Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN (1.18+).

Design docs & discussion

Design. Because Apple, Solaris and OpenBSD do not promise a stable syscall ABI, Go moves syscalls to libc calls issued from the system stack. On Darwin 1.11 starts with thread creation via pthread (b7f1777a70), exit (21656d09b7), then open/close/read/write (16d1a8e6e3), raise, sigaction/sigprocmask (ec989337c5), nanotime and time.now (cc09212f59, 6c6e22e5a9), kevent (72c29fc8cd), semaphores (5b5473c6bf), and the same for iOS (c8d07295c5, 021c39d7a3, c824f540d9, cdffe9b1e3); the profiler is kept happy by wrapper frames (ef53de8ba4). 1.12 does the same for package syscall via libSystem (a3b01440fe, be5f646dab, 9eb383e8f0), 1.13 avoids _getdirentries64 (9da6530faa), 1.13 also uses dispatch semaphores (85d56c3096), a pipe to wake signal_recv (c485e8b559), and MADV_FREE_REUSABLE (abf8e355a8); 1.14 adds nonblockingPipe and netpollBreak (3b0aa546d2, 50f4896b72) and read/write returning negative errno (b653c878b1). 1.17 uses entersyscall in syscall_syscallX (700b73975e), marks wrappers ABIInternal and later abi.FuncPCABI0 (bfc7418e6d, c430313992). In 1.16, openbsd/amd64 is switched to libc and pthreads (d047c91a6c, dab3e5affe, 8634a234df, 928bda4f4a, b268b60774, 66ee8b158f, 725a642c2d), then arm64 (3d40895e36, 6c8fbfbdcf, cd176b3615, 376518d77f) and in 1.17 386 and arm (4893eee9dc, c3b2b04156, faff49aae6, 8e91458b19, 8ec8f6aa87, 83df4a590b, 603f43cbae, bedf2c4886). Solaris uses libcall structs with RawSyscall panicking (613f8cad90, f0939ba5b1, 4902778607, fe5ef5c9d7); AIX uses the AIX C ABI in asmcgocall (c1e2227505) and libcall on stack (4e2b4d7fb2, cbaa8e5f93).

Timeline.

Windows runtime

Span: Go 1.5 → Go 1.17 · Status at go1.17: Windows 10 long-path support, arm and arm64, high-resolution waitable timers, QPC/KUSER time, SuspendThread preemption, stdcall via syscall_syscall*, callbacks via compileCallback → continues as Windows runtime port (1.18+).

Earlier history: Windows runtime port (Go 1.0 → Go 1.4)

Design. Windows ABI calls go through asmstdcall using libcall structs (414444d416), argument sizes encoded as Foo%3 import decorations for external linking (6112e6e404), float arguments returned in XMM (450d0b2f30, 9c02c75639). exceptionhandler / lastcontinuehandler are architecture-independent in signal_windows.go (6e774faed7, d1af6bed84), with CTRL_* events mapped to SIGTERM (5d1a95175e). Stack sizing is a recurring theme: unified PE header and runtime reservation (da11a9dda3), 2 MB on amd64 always (c2c07c7989) and 1 MB on 386 (77ea9f9f31), query real size (99e9be8043, 52e782a2d6, d6b56bb301). Timers: reduce resolution when idle (11eaf42886), osyield via SwitchToThread (1f5b1b2b66), usleep with CreateWaitableTimerEx high resolution (515e6a9b12, 6965b01ea2), suspend/resume monitoring (d85072ff86, 056a3d1c6f, d2de9bd59c, d467f3bbc9). Callbacks: syscall.NewCallback fix for sub-word arguments by building a C→Go frame map from the signature (532c199ee5, bda37a0b8a), compileCallback for the ctrl handler (e512bc2cf0), externalthreadhandler eliminated (c19759aa48, 5421c37a1d), newm for profileloop (e69f02265c), set up read-only dummy TLS for needm on non-Go threads (31d2556273, fef5a15396), all necessary registers saved on entry (dba2eab826). Windows/ARM arrived in 1.12 and windows/arm64 in 1.17 (see ports).

Timeline.

CPU feature detection (internal/cpu), auxv parsing and GODEBUG=cpu.*

Span: Go 1.9 → Go 1.17 · Status at go1.17: internal/cpu is the single detection layer (x86, arm, arm64, ppc64x, s390x, mips64x) with GODEBUG=cpu.feature=off/on → continues as CPU feature detection, GOAMD64 checks and cpu GODEBUG options (1.18+).

Earlier history: Startup, auxv, randomness and security-adjacent runtime details (Go 1.0 → Go 1.4)

Design docs & discussion

Design. Before 1.9 each arch kept support_* flags in the runtime. internal/cpu (69972aea74) gathers detection into one dependency-free package; x86 detection (66bfbd9ad7, FMA 1a3230f281), ppc64x via HWCAP (6661cf6dfd), s390x facilities via STFLE and HWCAP; the runtime then uses internal/cpu for alginit, AVX/AVX2 memmove and IndexByte choices (954f651ccc, 6b5236ae53, e0ac5f540b, d0712096b3, ace5fa1a60, 91d326e734, b64e817853). GODEBUGCPU (compiler experiment debugcpu, 1.11: f045ddc624) lets one disable features by name; 1.12 makes the syntax off/on with warnings (4fb8b1de3c, 3e0227f6a0, 980340ade7) and moves it to GODEBUG=cpu.feature=value (2de53906e1). Detection widened: ARM hardware divide (2e8c31b3d2), FMA (bb3bf5bb53), CacheLinePad in place of sys.CacheLineSize (7b9c2c1950, 60f83621fc, 961eb13b67), AIX (cb07f492db), s390x API aligned with x/sys/cpu (1f17d61026, ba978f5fff), MIPS64x (e48a83f077), s390x/ppc64/AIX HWCAP moved in (f8aecbbff5, 96bd0b1d4c), arm64 consolidation (de932da453) and darwin/arm64 (c155931974), testing prints CPU label via internal/sysinfo (b7a2d413a3), FMA disabled without OSXSAVE (dda2991c2e). Per-arch asm: arm64 memmove with a flag preferring aligned loads/stores (d5388e23b5), IndexByte/memclr SIMD on arm64 (d259815ccb), arm64 crc32, 64 byte cache line on arm64 (e0e7c03d14), arm64 LSE atomics added in 1.17 (07ff596404) after being disabled on Android (efd229238a).

Timeline.

Assembly-level runtime entry points: memequal/cmp/IndexByte, hashing, atomics and ABI wrappers

Span: Go 1.5 → Go 1.17 · Status at go1.17: hand-written assembly in runtime, internal/bytealg, runtime/internal/atomic; ABI0/ABIInternal wrappers generated for cross-package assembly references; register ABI prerequisites in place → continues as the register-ABI work of 1.18+ (see Library entry points and runtime initialization).

Paths not taken: Splitting the runtime package with sockdrawer (superseded)

Design docs & discussion

Design. The runtime’s assembly layer is where OS/arch differences bottom out. 1.5–1.8 bring cmpstring/bytes.Compare in asm for arm (e2543ef62c), arm64 (ee349b5d77), ppc64 (32d3b96e8b) with pointer-equality shortcuts (1135b9d671, 686d44d9e0), tail calls into memeq/cmp bodies (c526f3ac10), unified memeq/memequal (bd70bd9cb2), eqstring replaced by memequal (3216e0cefa), and strings/bytes assembly for Index, Count, IndexByte with SSE/AVX2 (95333aea53, 0cff219c12, 44f1854c9d, d206af1e6c, 01cd22c687, 687abca1ea, 429bbf3312, 0bc14f57ec). Hashing: a better fallback hash mixing cityhash and xxhash ideas (cda0ea1c0e), DOS-resistant aeshash (91059de095, 731bdc5115, c83e6f50d9), aeshash stubs for arm64 (5b72b8c7a3), 1.14 calls aeshash directly (fbfb41e638), 64 bits of seed on arm64 (f484e9699d). memmove/memclr improvements on amd64 AVX (3607c5f4f1, d7507e9d11, b64e817853), arm64 (d5388e23b5, d259815ccb), 4-byte writes on amd64p32 (9f6df6c940), MIPS64 MSA (918d4d46cd). Atomics: runtime/internal/atomic was split out in 1.6 (67faca7d9c), crashes (later nice panics) on misaligned 64-bit ops (01b4ddb377, 526b2f85ce, 83317d9e3c, 7a71726b1f), ARM native CAS and barriers on ARMv7 (150b728675, 14f929af91, 4bd8040d47, 633b38c5d2, 97124af99a), ppc64 lightweight LoadAcq/StoreRel/CasRel (5c472132bf, 15ead857db), Load8/Store8 (4a4e05b0b1, d2101e5490), 32-bit And/Or (ad61343f88), Xchg/Xadd arm64 (3103495fa9). Ctz/Bswap in asm on 386 for the 1.7 bitmap allocator (9c8809f82a). 1.12 starts ABI hygiene: all runtime functions get correct ABI info with wrappers generated by dummy calls, and Go stubs for assembly callees (af1bfe0aa3), runtime.reflectcall accessed directly (4f3604d3f2). 1.17: reflect register ABI on amd64 (e0215315f5), panicIndex/panicSlice shims target ABIInternal then pass registers (306b2451c8, 96a6745088), spillArgs/unspillArgs stubs (c2358a1ae7, f8892147bd), ABIInternal PCs in assembly (ec721d92bf), g register use in amd64 assembly (7b0dfb177f, 63de211014, 22f9e1ccbc, 7cc6de59f2), reflectcall ABI0 (d8306ee1f9), gosave removed (8fa84772ba, b110a43628), huge-frame prologue simplified (ef3122e909).

Timeline.

Debugger function calls and conservative preemption frames

Span: Go 1.11 → Go 1.15 · Status at go1.17: still the design at go1.17 (debug calls run on a new goroutine with conservative scanning of the interrupted frame) → continues as Signals and async-preemption machinery (1.18+).

Design docs & discussion

Design. The runtime provides a protocol, debugCallV1 (amd64), letting a debugger such as Delve inject a call to a Go function at a safe point: the runtime handles live pointers in registers, stack growth and detecting unsafe conditions (c5ed10f3be, ~700 lines). Initially it used precise register maps at nearly all PCs (~5% of binary size); 1.15 switched to conservative scanning of the injected and interrupted frames after flushing pointer registers to the stack, with the call performed on a new goroutine that may grow its stack, so precise register maps are no longer needed (57d751370c), and go115ReduceLiveness/go115RestartSeq knobs removed in 1.16 (8414b1a5a4). debugCall32..debugCall65536 are whitelisted as safe points to allow nested calls (d016330241).

Timeline.

Thread lifecycle, physical page size and scheduler-facing OS hooks

Span: Go 1.5 → Go 1.16 · Status at go1.17: still the design at go1.17 → continues in Signals and async-preemption machinery (1.18+).

Earlier history: Threads, locks and the OS scheduler interface (Go 1.0 → Go 1.4)

Design. Threads created by the runtime could not exit; 1.10 adds exitThread and a freem list: in cgo mode and on Solaris/Windows (OS-managed thread stacks) mstart just returns; otherwise the M clears a flag when its g0 stack is safe to reap, allocm scans freem and frees stacks (eff2b2620d), with exitThread fixes per OS (5ddd3d588c, a7bb8d3eb8). 1.8 replaced the hard-coded 4 kB physical page assumption: assume 64 kB on ARM (d7de8b6d23), then query the OS (276a52de55, 6dda7b2f5f, 1b9499b069, 8ce844e88e, a0667be8ef), with physHugePageSize in 1.13 (1033065ee3). Misc: private futexes (07751f4b58), MADV_FREE (77f9b2728e), NOHUGEPAGE for released pages (7e1b61c718), GOTRACEBACK variants, NumCPU improvements (77d38d9cbe, 2b0ddb6c23, e19f184b8f, a714470cce, c55eeeb718, d79350bac7).

Timeline.

Plan 9 and NaCl ports

Span: Go 1.5 → Go 1.15 · Status at go1.17: Plan 9 still supported (notes as signals, env cache); NaCl removed in 1.14 → continues as New, ported and removed platforms (1.18+).

Design docs & discussion

Design. Plan 9 notes act as signals (d5d4ab7819 for os/signal, 3d032d7653 consolidates arch handlers, f85a05581e fixes handling, d093bf489b abort notes, bb59a1360a not enabling notes too early). Plan 9/ARM was added in 1.7 (d145456b16, 0de0cafb9f, bd7e084d7d); getenv/setenv use a local cache with POSIX semantics in 1.15 (af09ff1981); processes and exit races (10be797578, 7de86a1b1c, 6b59d61822, 967b9940b4, 5c2233f261, 68117a91ae, 640c0f3849, 5abfc97e84); asynchronous cancelable I/O for deadlines using the hangup note (adb384ad2c). NaCl: conversion to Go (ad8179281d), get_random_bytes (003dccfac4), systemstack traceback fix (108ec5f75a), asm fixes (e2103adb6c), and the port is removed in 1.14 in parts (a38a917aee, 03ef105dae).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.5 · 1.6 · 1.7 · 1.8 · 1.9 · 1.10 · 1.11 · 1.12 · 1.13 · 1.14 · 1.15 · 1.16 · 1.17

Runtime: OS and platform layer, stacks, panics and tracebacks (Go 1.18–1.28)

Reader: Claude Sonnet. Cites 804 of 1,159 commits; 120 uncited commits were judged noise. Based on commit messages; diffs were rarely read.

Defer, panic and recover re-implementation

Span: Go 1.18 → Go 1.28 · Status at HEAD: open-coded + linked defers share one state machine (_panic.nextDefer/nextFrame); recovery transfers to the PC of deferreturn; recover validity is decided by unwinding; still being tuned in 1.28 (linked-defer matching)

Earlier history: Panic, defer, recover and Goexit (Go 1.0 → Go 1.4) · Defer, panic, recover and Goexit (runtime side) (Go 1.5 → Go 1.17)

Design. The 1.18 work made every go/defer target an argument-less func() closure (the compiler wraps calls with arguments). That removed the arg-frame copying machinery: tracebackdefers, the siz argument of newproc/deferproc, variadic defer/go calls, and (after a revert) jmpdefer. deferreturn became a plain loop calling deferred closures, which also deleted per-arch assembly, the PPC64 TOC reload NOP (Ginsnopdefer) and several Wasm special cases. Open-coded defers (frame-local bitmask, funcdata describing slots) stay the fast path; on panic, addOneOpenDeferFrame materialises _defer records for frames that have open-coded defers.

In 1.22 gopanic, Goexit and deferreturn were rebuilt on a shared state machine (_panic.nextDefer, nextFrame, using the unwinder to walk to the next frame with defers), a net −177 lines in panic.go (9eb1d5317b). Follow-ups stopped deferreturn from invoking the unwinder (state for recovered open-coded frames is saved in g.param, 9869699c44), added a linked-defer fast path (51cb12e83b) and restore the caller frame pointer when recovering (b51a4dd6c4, f7a87e3299 for arm64). Range-over-func needed deferrangefunc/deferprocat so a defer in a loop body appends to the parent function’s list (e82cb14255).

In 1.25/1.26 recovery was simplified further: panic recovery now transfers control to the PC of the deferreturn call instead of the old “register-value handshake” in deferproc/deferprocstack (c2ae5c7443, go binary 0.2% smaller), recover() validity is determined by stack unwinding over logical (inlined) frames instead of incrementally tracking argp (7b50024330, 08376e1a9c), and a panic skips ahead to where the previous panic stopped looking for defers (50d988e4e0, which fixes a quadratic-ish repeated-panic walk) with gopanicFP dropped (e93b26688c). Semantics: panic(nil) became *runtime.PanicNilError (aa51c40b1c), whose message later got the runtime error: prefix (16e8e06751).

Timeline.

Unwinder: gentraceback → unwinder iterator, inline unwinder, pcvalue cache

Span: Go 1.18 → Go 1.23 · Status at HEAD: unwinder (traceback.go) and inlineUnwinder are the only stack-walk implementations; gentraceback deleted in 1.21

Earlier history: Tracebacks, crash output and GOTRACEBACK (Go 1.0 → Go 1.4) · Tracebacks, symbol tables and runtime.Callers/CallersFrames (Go 1.5 → Go 1.17)

Design docs & discussion

Design. gentraceback was one function with three output modes (print, fill PC buffer, callback) and three unwinding modes (physical Go frames, inlined frames, cgo frames). In the 1.20/1.21 cycles Austin Clements split it up: stkframe and its methods were consolidated into stkframe.go, arglen/argmap became methods (dbf442b1b2, 55ca6a20af), and the closure-context tracking for unstarted reflect stubs was dropped because go/defer of anything with arguments is now wrapped in a closure (8be94b82ab). Then unwinder was introduced with an iterator API (f758d648b0, fixes issue 54466), the cgo-context slice was replaced by an index to avoid write barriers (86b69ef329), all callback users were converted (2d99109cfc, stack copying −10 to −14% on StackCopy* benchmarks), cgo traceback moved into the unwinder (9872428a71) and gentraceback was deleted (3790ceca97), fixing a bug where the “additional frames elided” line could be skipped. Cumulative cost: Callers +5..+15% (unwinder not inlined) vs stack copy −4..−13%.

Inline expansion was factored into an inlineUnwinder API (f52bede354) used by showframe, traceback printing and runtime_expandFinalInlineFrame (d829b62681, 166e5ee4f2, f1855993f3). pcvalue lookups moved their cache from stack allocation to the M (4a8373c553, d367ec6a0e, with revert/reland around a reentrancy problem from the signal handler: 26e0660811, 5f674f64e4), and a self-check mode was added (e094e80f65).

Timeline.

Symbol tables, runtime.Caller and CallersFrames

Span: Go 1.18 → Go 1.27 · Status at HEAD: _func entries are module-relative offsets, findfunc has a single-text-section fast path; documented Caller/Frames semantics

Earlier history: Symbol tables, pclntab and runtime.Caller/FuncForPC (Go 1.0 → Go 1.4) · Tracebacks, symbol tables and runtime.Callers/CallersFrames (Go 1.5 → Go 1.17)

Design. The pclntab was made cheaper to consult. _func.entry became an offset (entryOff) resolved against the module (61a0a70113, f0c79caa13), which made Func.Entry slower (0.64ns → 2.62ns) but the common callers already held a funcInfo; lookup by *Func pointer (e54843f2f4), funcinl sentinel changed to ^0 (6c163e5ac9), and findfunc got a single-text-section fast path, simplified multi-section handling and textAddr work (5758c40ac8, 2478b6f6d6, 8238f82bf1, 6436f5c13d, 058fa255bc, 77bd0da688); funcdata hot loads were moved earlier (e82ed0cd83, StackCopyWithStkobj −6%). _func.nameoff/entryoff and inlinedCall.func_ were renamed (196003f9fd, 7ebaa6587b, d81ed4d621) and cmd/link/runtime share the ID constants in internal/abi (9754521157, 7843ca83e7, 639957eb66).

Timeline.

Frame pointers: explicit NOFRAME, FP maintenance and FP unwinding

Span: Go 1.21 → Go 1.28 · Status at HEAD: all amd64 runtime assembly uses explicit NOFRAME; FP is saved/adjusted across stack copies, signals, cgo and injected calls; FP unwinding used by the tracer

Design docs & discussion

Design. amd64 used an implicit heuristic: NOSPLIT assembly functions with no frame were treated as NOFRAME. Mateusz Qmuntal replaced it with explicit NOFRAME flags per OS (windows, darwin, linux, dragonfly, freebsd, netbsd, openbsd, solaris, plan9) and then removed the heuristic from the assembler (d20e688fcf), so frame pointers are reliably saved. That was a prerequisite for frame-pointer unwinding in the execution tracer (ba71817390: events on amd64 unwind via FP, inline expansion deferred to stack-table dump, escape hatch GODEBUG=tracefpunwindoff=1; PingPong −80% per event; FPCallers/cached 25ns vs Callers/cached 1.25µs, 25f9af6661). Correctness work followed because an unadjusted FP points into a freed/moved stack: adjustframe for systemstack_switch (2380d17aea), stack copy on arm64 (a41a29ad19), signal handlers on arm64 save FP (c00e9285ea), gp.syscallbp stored as unsafe.Pointer so it moves with the stack (54fe0fd43f), and later mcall/morestack/thread entry clear FP (9c1d19a183, 93fb2c9074, eda2e8c683, 163abc10f3 for riscv64). Tracing details belong to the tracing area.

Timeline.

Stack growth, stack bounds and the crash stack

Span: Go 1.18 → Go 1.28 · Status at HEAD: “morestack on g0” prints a traceback on a dedicated crash stack on nearly every arch; stack guard tuned per OS/mode

Earlier history: Segmented stacks to contiguous (copied) stacks (Go 1.0 → Go 1.4) · Stack-split prologue, nosplit budget and C-on-Go-stack detection (Go 1.0 → Go 1.4) · Goroutine stack growth, shrinking, GC stack barriers and stack objects (Go 1.5 → Go 1.17)

Design. Errors such as “morestack on g0” were notoriously hard to debug because printing on a bad stack is unsafe and SIGABRT tracebacks often omit the offending stack. 0262ea1ff9 reserves an emergency “crash stack” (g.stack switch in morestack), then throws with a normal traceback; per-arch support followed: ppc64x (aa9dd50095), riscv64 (cfe36fd122), mips64x (7dd9437133), wasm (369ce90d2c), s390x (cad6629144), loong64 (f719d5cffd), arm (727e8d3863), 386 (c764d56ff1), mips/mipsle (ff0bc4669e), with a cleanup (c89318667f). The original commit says “TODO: also handle morestack on gsignal and bad systemstack”. Stack guard accounting: the legacy 104-byte guard was removed (57236fe9f7), -race builds get extra guard (893964b972), stackGuardMultiplierDefault forced to 1 in cmd/dist (b12366bbb5), nosplit limit raised on OpenBSD (4da905bcf0), windows/386 reserves 4kB system stack (7eeb0a188e). Always-preempt maymorestack hook added for testing (35c7234601).

Timeline.

C-to-Go callbacks: extra-M reuse, system-stack bounds, init gating

Span: Go 1.18 → Go 1.27 · Status at HEAD: the M created for a C thread is cached in a pthread key until thread exit; g0 bounds re-estimated per callback; init check is an atomic bool

Earlier history: cgo runtime support and non-Go threads (Go 1.0 → Go 1.4) · cgo runtime support: pointer checks, callbacks, extra Ms, C-to-Go call path (Go 1.5 → Go 1.17)

Design. A C thread calling an exported Go function needs an “extra M” (needm/dropm), both involving signal-mask syscalls. doujiang24’s change binds the extra M to the C thread through a pthread_key_create key with a thread-exit destructor, skipping dropm on return to C: ~10x faster call (28x on darwin: 3395→121 ns/op; 6.5x on Linux: 1495→230) (ccad8a9f9c). It was landed, reverted and relanded repeatedly because of follow-up bugs (ARM asm, stack overflow when C had used more than the assumed 32 KB, leak, Illumos stack bound, TSAN false race when passing the G to _cgo_getstackbound, ppc64 cleanup): 7b874619be, then c426c87012 which passes a stack variable address to _cgo_getstackbound and adds a sanitizer regression test; M-binding also made needm ask pthread for real stack bounds (443eb9757c, reverted and reapplied within the series). Because the M and g0 now persist, stack bounds can go stale: C coroutine libraries swap stacks between calls, so bounds can be updated if no Go frame is below (a46b1ad357), always updated at callback because partial overlap breaks the fast path (1ffc296717), and in 1.23 saved and restored per nested callback and cached only if accurate (76a8409eb8; CgoCallbackMainThread 96.4µs → 0.1µs since pthread_getattr_np is slow on the main thread). In 1.27 the “wait for runtime init” channel receive on every callback gets an atomic-bool front (4f8aa414f0).

Timeline.

Library entry points and runtime initialization

Span: Go 1.18 → Go 1.27 · Status at HEAD: c-shared/c-archive start-up is largely shared Go code; per-arch assembly reduced

Earlier history: TLS, g/m registers and shared-library linking (Go 1.0 → Go 1.4) · Runtime C-to-Go conversion and the Go calling convention in C (1.4) (Go 1.4 → Go 1.4) · Shared-library and buildmode entry points (c-archive, c-shared, plugin, shared) (Go 1.5 → Go 1.17) · Assembly-level runtime entry points: memequal/cmp/IndexByte, hashing, atomics and ABI wrappers (Go 1.5 → Go 1.17)

Design. Each arch had its own _rt0_*_lib assembly. 1.26 unified entry code: Windows amd64/386 (fdea7da3e6), arm64 (45d6bc76af), ppc64/ppc64le (c9257151e5), loong64 (eb1c7f6e69), riscv64 (36863d6194), with fixes on AIX (f03c392295, 4ad5585c2c, b9693a2d9a) and a FreeBSD/arm64 entry revert (91435be153). In 1.27 “part of library initialization” moved to Go (24 files, +140/−227, 6bdfdb5f51; libInit uses asmcgocall_no_g, 78d5260426; AIX fix 0e31741044). Earlier: sigPreempt handler installed for c-archive/c-shared (a4b2c579e9), library mode on wasip1 (0b1494f5d2, _initialize instead of _start, one M retained), c-shared on loong64 (80eeec6979), Windows lib entry stack alignment (5fe3f0a265), and “debug settings initialised much earlier” so GODEBUG such as decoratemappings applies to the first mappings (300d9d2714, 1.26).

Timeline.

Security hardening: AT_SECURE, std fds, AT_RANDOM, gated debug features

Span: Go 1.20 → Go 1.24 · Status at HEAD: isSecureMode/tainted-binary protections in place; page tracer behind GOEXPERIMENT; startup randomness overwritten

Earlier history: Startup, auxv, randomness and security-adjacent runtime details (Go 1.0 → Go 1.4)

Design. 2496653d0a (CVE-2023-29403) added “tainted binary” detection modelled on glibc: Linux uses AT_SECURE; BSDs, darwin, Solaris/illumos use issetugid; AIX uses !(getuid()==geteuid() && getgid()==getegid()) (new getuid family on AIX). Two protections: open fds 0/1/2 on /dev/null if closed, and force GOTRACEBACK=none (no register/traceback dumps). The fd check was then applied to all Unix start-ups, not just setuid (d4dd1de19f, fds_unix.go; AIX/Solaris don’t check the fcntl errno, 12bd2445af). Other items: Windows crash-dump creation (353d5b6c53) was reverted because memory could be uploaded to Microsoft without a proposal (6dcf83d882, 2622235a99 earlier carved out Win7); page tracer GODEBUG=pagetrace lives behind a GOEXPERIMENT because of setuid risk (e4435cb844); startupRand (AT_RANDOM) is overwritten rather than zeroed since libc and cgo code may read it (9a44b8e15a); ChaCha8 backs runtime.rand so global math/rand is unpredictable (c29444ef39, +304 bytes/M); crypto/rand crashes if reads would fail (c050d42e1a); panic output is framed against traceback spoofing (69e75c8581).

Timeline.

runtime/secret and data-independent timing (DIT)

Span: Go 1.26 → Go 1.28 · Status at HEAD: secret.Do implemented under GOEXPERIMENT guard; linux/amd64, arm64 and (1.28) loong64; goroutines spawned inside inherit secret state

Design docs & discussion

Design. secret.Do(f) (a3fb92a710, a rebase of Keith Randall’s CL 600635): on return, clear the stack used by f and clear registers that may hold secrets; on stack growth in secret mode clear the old stack; heap objects allocated in secret mode are marked and zeroed as soon as freed; a panic inside is re-raised as if from Do, removing the secret function from tracebacks. Implementation covers signal handling, preemption and vDSO frames spilling on system stacks. Initially linux/arm64 and amd64 only. Files are guarded by the experiment (8d31244562), tests reorganised for shared mode and memory-validating modes (c079dd13c0, de456450e7, 841fafcd58), allocation caveats documented and guarantees loosened (516699848b). Goroutine behaviour was first changed (panic on spawn) then restored per the proposal (63fced531c), and 1.27 implemented goroutine inheritance of secret state (e26a373785) and fixed cgo crashes inside secret.Do (1dd2bef375). DIT is related: crypto/subtle.WithDataIndependentTiming used to lock the goroutine to its thread; now goroutines inherit DIT, the scheduler toggles DIT on the thread by comparing g.ditEnabled/m.ditEnabled, and cgocallbackg syncs state with C (af14f67911); speculation barriers after DIT (2244bd7eeb, restricted to Apple Silicon by fa8595702e); DIT assembly improved (0bd978d532); arm64 DIT detection (0826b90172, 1.24).

Timeline.

vDSO, vgetrandom and fast clocks

Span: Go 1.18 → Go 1.28 · Status at HEAD: Linux getrandom through vDSO on all arches the kernel supports; per-M state pool

Earlier history: Clocks, vDSO and timers (Go 1.0 → Go 1.4) · vDSO, time sources and process-wide randomness (Go 1.5 → Go 1.17)

Design docs & discussion

Design. eb6f2c24cd uses the Linux 6.11 vgetrandom vDSO: opaque per-thread state mmapped with vDSO-supplied flags, allocated in chunks (ideally ncpu rounded to a page), assigned to an M on first use and returned to the pool at M exit. crypto/rand Read/4 fell from 222 ns to 27 ns (−88%). Follow-ups: states aligned to cache lines (−43% on 128-core NUMA, 86781963cc), acquirem only when allocating (8c269479ed), non-escaping linkname (ba10a38ed0), PC-in-vDSO-page computation fixed (ba42120723), per-M state cleaned before dropping P (0b31e6d4cc, stress test 8969771cc3), then riscv64 (e58ce6c835, Read/4 −87%) and riscv_hwprobe via vDSO (21167589a8). The earlier vDSO clock work: clock_gettime on riscv64 (−32%, 1bd35fab05), s390x (636c5f0208), loong64 (8542bd8938, fa... 3fa5ab0d83 symbol version fix, 42023d6f52 g saved around VDSO), FreeBSD fast clock_gettime with HPET/ARM timer (e51b3ae0ee, 535fe2b226), freebsd/riscv64 vDSO (0e56c31b85), async fatal signals in VDSO (86f6bf18b0), and VDSO symbol hash tests (a2baae6851, dde20209b8 correct arm64 hashes).

Timeline.

Signals and async-preemption machinery

Span: Go 1.18 → Go 1.28 · Status at HEAD: signal handling uniform across Unix with libc sigaction under cgo on more arches; preemption saves AVX-512/SVE state

Earlier history: Goroutine preemption (stackguard0, 10 ms and GC preemption) (Go 1.2 → Go 1.2) · Signals: handling, os/signal, foreign threads (Go 1.0 → Go 1.4) · Signals: masks, forwarding, sigaltstack and the unified Unix signal layer (Go 1.5 → Go 1.17) · Asynchronous preemption: signal plumbing, injected calls and per-arch support (Go 1.14 → Go 1.16) · Debugger function calls and conservative preemption frames (Go 1.11 → Go 1.15) · Thread lifecycle, physical page size and scheduler-facing OS hooks (Go 1.5 → Go 1.16)

Design. Signal handlers on sigtramp are marked TOPFRAME so unwinders stop (4bb45f7549). SIGPROF handling filters between setitimer and per-thread timer_create signals (8cfd8c3db8, 8cfcee1fff stray signals). Async preemption (SIGURG) register saving evolves: asyncPreempt assembly generated by mkpreempt.go (state removed cef381ba60), AVX2/AVX-512 state saved (55d961b202, 9eeb1e7a9a on dev.simd; 674806dda3 save AVX even when !avx2), VZEROUPPER removed on darwin/amd64 (fe87b586c0), loong64 fcc/fcsr (48d61a46e2, bd885401d5, e76c910108), SVE for arm64 (5e6be730dc, SVE2 detection e077557cfb). Windows preemption: arm/arm64 support (c58243aa8a, f7e34e705c), skip async preemption while handling an exception (0042b271db), disable mutex profile (3d727ce069). Distinguishing external signals: SI_TKILL as SI_USER (d11c58eedb), be003bd00b (don’t re-raise ignored signal), c2fabf1a26 os/signal ignores bogus signals, 974764364a dieFromSignal sane for pid 1, af83819064 darwin avoids SA_SIGINFO with SIG_DFL/SIG_IGN. cgo: libc sigaction on 386 (c5737dc21b), loong64 (180795ceac) and riscv64 (61b0b6dd84) because the C ABI hides sa_restorer; sigtramp ABI fix for riscv64/mips/s390x (d0eb91559a). Debugging: debugger function calls on arm64 (9717e8f80f), ppc64le (b0ae440bf0), loong64 (3a55e92ccf→3a55b92ccf); fatal signal prints fault address (c4af8abae1) and debuglog (9ceba95008); debugger hook (d8c9eece28); debugPinnerV1 (7f9edb4225).

Timeline.

Syscall layer: AllThreadsSyscall, internal/runtime/syscall, libc and Windows SyscallN

Span: Go 1.18 → Go 1.27 · Status at HEAD: Linux raw syscalls live in internal/runtime/syscall/linux; Windows/darwin generic syscalln in the syscall package

Earlier history: Threads, locks and the OS scheduler interface (Go 1.0 → Go 1.4) · Integrated network poller: OS back-ends (Go 1.1 → Go 1.4) · Darwin and OpenBSD move to libc; Solaris and AIX libcall; syscall wrapper layer (Go 1.5 → Go 1.17)

Design docs & discussion

Design. AllThreadsSyscall was reimplemented purely with signals after a thread blocked in a syscall could hang it (issue 50113): doAllThreadsSyscall(num, args...) sends SIGSETXID (SIGRTMIN+1, as glibc) to every thread in allm, each performing the syscall in the handler; synchronisation with thread creation uses allocmLock; only for non-cgo binaries; state moved into mOS (0a5fae2a0e, 7a132d6f4e). A new package runtime/internal/syscall hosts Syscall6 on Linux (0b321c9a7c), with syscall.RawSyscall6 pointing at it (15602e8865, dff3a00cea after migration 05c0579621), later gaining basic file-system calls (2a29cddec3) and being renamed .../linux (5c45fe1385). ABIInternal variants for riscv64, ppc64, loong64 (939f9fd64a, f9bd1d0f11, 2e77b51df5, 733df2bc0a, 039b3ebeba). Windows: syscall.SyscallN (0e598e7da4, limit 42; limit lifted in 7c42da1bc5), implemented once in syscall_syscalln with Syscall{3,6,...} moved out of the runtime (98f301cf68), loadlibrary/getprocaddress moved to syscall (ef40549786), stdcall deduplicated (e666972a67), types in internal/runtime/syscall/windows (c7ed3a1c5a, 889e71c2ac), errors returned as values (4216fa3d04, 9b4244e9ea). Darwin: syscalln generic trampoline (4a0115c886, 8320fe8f0e, 9-arg fixes 9d7de04838, 786e62bcd3, 7acb0d0446). OpenBSD converted to direct libc calls except mips64 (0d6a7f9d2e, 1.20); _exit instead of exit on AIX/Solaris (6bbbc5dc70). Linux 32-bit: 64-bit futex/timer_settime (3cf1aaf8b9, 4620db72d2, ENOSYS fallback; Android skip a5f474fc06; ba402cd756, 4e06ed21ac, 04dc12c1a1, 6e808ba41a uname checks; all linux 32/64-bit files merged ac3369242d).

Timeline.

Windows runtime port

Span: Go 1.18 → Go 1.28 · Status at HEAD: Windows 10 minimum, windows/arm removed, loader-provided TLS, high-resolution timers, SEH-aware crashing

Earlier history: Windows runtime port (Go 1.0 → Go 1.4) · Windows runtime (Go 1.5 → Go 1.17)

Design. TLS: amd64/386 moved to TEB TLS slots (da564d0006, 28f8dbd7b9), fell back to ArbitraryUserPointer when slots were full (715d53c090), and in 1.28 the PE TLS directory is emitted by internal/external linkers so the loader allocates g’s slot (TEB.ThreadLocalStoragePointer[_tls_index] + runtime.tls_g) (35b807d2fc). Timers: IOCP wake via WaitCompletionPacket + high-res timer (cf52e70997, BenchmarkSleep −80%), high-res timers on arm/arm64 (b6f29d2365), CPU profiler via high-res timers (a09ea59198, 0d37007fda fallback for Windows 10 1803/1809 where periodic high-res timers fail), no winmm.dll on newer Windows (558919b454), precise monotonic clock (16d46baf6f), QueryPerformanceCounter import (ab2e3ce77c). Process/crash: GOTRACEBACK=wer (65ea4c5021), CFG support (5c0d314adc), LoadLibraryEx only (a17d959deb), procid set (140000dd55), SEH handling (bc15070085, e03ee85ef4, d58290fefd). Threads: retry _beginthread (c7ccabf3fe), CreateThread (00e793cfe3, retry e93f439ac4), threads created on system stack to avoid overrunning goroutine stack (9a5f58afaa), preemption synchronised by claiming _Gscanrunning (12c76d447f). Removed: windows/arm (dc54d7b607, 9b0a507735), pre-Windows 10 (341b5e2c02), 2012 test skips (0ac819d3ae).

Timeline.

New, ported and removed platforms

Span: Go 1.18 → Go 1.28 · Status at HEAD: added linux/loong64, freebsd/riscv64, openbsd/riscv64, openbsd/ppc64, wasip1/wasm; removed windows/arm, openbsd/mips64; linux/ppc64 moved to ELFv2

Earlier history: Platform ports: OS and architecture matrix (Go 1.0 → Go 1.4) · New and ported OS/arch targets, 1.5–1.17 (Go 1.5 → Go 1.17) · Plan 9 and NaCl ports (Go 1.5 → Go 1.15)

Design. linux/loong64 was brought up in 1.19 in ~15 commits: entry, syscalls, signals, vDSO, atomics, cgo (2a5ceaaa78, c8de7628cb, 0084706528, 8542bd8938, a7cc865edb, 34507e879d, 7607888df0, 740b0ebb76); then plugin buildmode (d7fcb5cf80), msan (fbbed139a1), regabi + reflect (f6f141ab80, 4e6bbbe61f), debug calls, SIMD memclr/Count, procyield via NOPs (ca0b8b21fe, CockroachDB −14%), secret (a133902362). riscv64: X3/GP no longer used (5b213178e7), RDCYCLE cputicks (946e2543f8), regabi in reflect attempted then reverted (56400fc706, ad646b33c9), compressed instructions (b9ef0633f6), RVA23 (664aebab7d), fine-grained FENCE (ab7c8279a0), vector/zbc detection (ed1646a8ae, 122eb7d035). FreeBSD/riscv64 (9aa7107cb5, d268504fd9, 0e56c31b85), OpenBSD/riscv64 (57c1dfd2ab), OpenBSD/ppc64 (c140105e1c), linux/ppc64 ELFv2 and external linking (35c5deb1d4, 364de84f36). wasip1: GOOS definitions (8890c3a05f), remaining runtime files (be50055eef), GOWASIRUNTIME default (3687f77069), go:wasmimport type limits (53c0158905), net FileListener/FileConn (a17de43ef1), Fcntl (7756f82590), wazero in tests (da5d8fdd0c), library mode (0b1494f5d2). Register ABI on other arches: ARM64 regabi (reflectcall, MakeFunc, assembly: 2e4b79949f, 0c123cdf8b, aa9cfdf775, 28bd325e41, 3de4986852), non-regabi arm64 fallback removed (602e6aa979), ppc64x (6e5dd0b59b, 91c2318e67), loong64 (f6f141ab80), s390x (41af98eb83, 58a48a3e3b). Removal: windows/arm (dc54d7b607), openbsd/mips64 (d5dc36af45, last syscall(2) user). Wasm runtime: memory reuse (dac514ea37, 4510586f93), 32-bit function index (9782dcfd16), js notes without maps (2caf638e2f), crash abort (2f3fee058f), And/Or atomics (6ecd5f7504).

Timeline.

CPU feature detection, GOAMD64 checks and cpu GODEBUG options

Span: Go 1.18 → Go 1.28 · Status at HEAD: internal/cpu detects features for amd64 (AVX-512 family, SHA-NI), arm64 (SHA3/SHA512, LSE, DIT, SVE/SVE2, Neoverse IDs), riscv64 (extensions, VLENB, vector crypto, zbc), loong64 (HWCAP, LAMCAS); auxv exposed

Earlier history: CPU feature detection (internal/cpu), auxv parsing and GODEBUG=cpu.* (Go 1.9 → Go 1.17)

Design docs & discussion

Design. internal/cpu reads CPUID/auxv/hwcap/system registers at start-up and honours GODEBUG=cpu.<feature>=off options. Rules tightened: options for features required by the GOAMD64 level cannot be disabled (5ccd8e5133, 195945a40d removed the “required” marker mechanism), tests respect GOAMD64>1 (9b112cec83), and in 1.28 RISC-V GODEBUG options are restricted to optional extensions (aafcf7a2b8). The runtime itself checks at start-up that the CPU provides what the build assumed: amd64 microarchitecture level (8c8baad927, ~135 lines of asm; TLS first 01b6cf09fc; Darwin v4 6a40bdcbaf), LSE on ARM64 (81c66e71d4, first attempt reverted 475e08349d). The runtime exposes auxv to x/sys/cpu (1a9893a969) and loong64 HWCAP (0f003f9d15).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.18 · 1.19 · 1.20 · 1.21 · 1.22 · 1.23 · 1.24 · 1.25 · 1.26 · 1.27 · 1.28