You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Incrementally replace LLGo-maintained standard-library runtime substitutes with the pinned Go toolchain's GOROOT/src/runtime implementation, using minimal source patches first and Plan 9 assembly only when LLGo's ABI and stack semantics are compatible.
This is a staged compiler/runtime migration. It must not be treated as a file move or folded into the pthread G/M/P cleanup: directly enabling the upstream runtime would also require compatible compiler intrinsics, function ABI, getg, stack switching, GC metadata, write barriers, generated offsets, OS startup, and runtime assembly.
Current architecture
LLGo currently has two runtime layers with different responsibilities:
runtime/internal/runtime is the compiler-facing low-level ABI. LLGo-generated code calls it for allocation, maps, channels, panic/defer/recover, goroutine startup, locality, and other language operations.
runtime/internal/lib/runtime is the replacement implementation compiled as the standard-library package runtime. It provides public and linknamed runtime APIs and already depends on the low-level layer.
The standard runtime package is currently an altPkgReplace package. That replacement also prevents the normal GOROOT runtime assembly from being selected. The two directories therefore overlap in concepts, but they are not interchangeable copies today.
Motivation
Maintaining large copied or simplified runtime implementations has several costs:
behavior drifts from the pinned Go version;
new standard-library releases add hooks manually;
copied assembly and platform code are difficult to keep correct;
package replacement gives a poorer patch and upgrade experience than compiling the original source with small, reviewable source patches;
scheduler, syscall, timer, GC, traceback, and profiling behavior can be implemented twice.
Where semantics and ABI match, the Go implementation should be the source of truth. LLGo-specific code should describe only the incompatibility.
Principles
Pin the supported Go version and upgrade it explicitly. Runtime source reuse is version-sensitive.
Prefer source patches over package replacement for reusable Go source. Keep every patch as small and local as possible.
Reuse GOROOT .s files through Plan 9 assembly only after their ABI, TLS, stack, GC, and generated-offset assumptions are satisfied.
Keep a stable compiler-facing runtime ABI so moving an implementation does not repeatedly change SSA lowering.
Migrate one coherent subsystem at a time and remove the replaced LLGo implementation in the same change.
Preserve native, C library, WebAssembly, and bare-metal build selection. Unsupported targets must select an explicit fallback, not accidentally compile a host implementation.
Do not claim compatibility from successful compilation alone; exercise the reused behavior at runtime.
Goals
Define which runtime symbols are owned by the compiler-facing layer, the standard runtime package, or a platform backend.
Make runtime eligible for incremental source patching rather than requiring an all-at-once replacement switch.
Reuse upstream leaf Go implementations and compatible assembly with minimal deltas.
Shrink runtime/internal/lib/runtime as ownership moves back to GOROOT sources.
Prevent duplicate definitions and detect missing runtime hooks during Go upgrades.
Establish gates for the later scheduler, stack, and GC migration.
Non-goals
Do not enable the complete upstream runtime/proc.go in the first change.
Do not reuse mstart, mcall, systemstack, morestack, or related assembly before LLGo implements their required g0 stack, register ABI, TLS, stack-map, and preemption semantics.
Do not change LLGo to Go's full moving/growable stack model as part of source-patch plumbing.
Do not introduce a large new source-patch framework if the existing overlay mechanism can express the migration.
Proposed design
1. Runtime ownership manifest
Add a checked manifest or generated report for runtime symbols that records:
compiler intrinsic / low-level ABI ownership;
upstream source ownership;
LLGo source-patch replacement;
platform backend ownership;
required linkname aliases and assembly definitions.
The check must reject duplicate definitions and report upstream symbols that become newly required after a pinned Go upgrade.
2. Stable compiler ABI boundary
Keep compiler lowering pointed at a small, target-independent low-level ABI. For example, a go statement should submit an entry function and startup record to one runtime newproc-like boundary; it should not emit pthread operations.
Initially that boundary may still live in runtime/internal/runtime. The standard runtime package can call or link to it. Ownership should move only when the upstream ABI is actually implementable; source directory layout alone must not dictate the compiler ABI.
3. Incremental source patching of runtime
Extend the existing source-patch/package-patch merge so runtime can be migrated declaration by declaration:
compile the pinned GOROOT source;
skip only incompatible declarations or files;
inject a small LLGo replacement for those declarations;
remove the corresponding implementation from runtime/internal/lib/runtime;
retain the replacement package only for the remaining unsupported surface.
Source patches should explain the semantic or ABI incompatibility and should be guarded by the relevant Go version and target tags.
4. Assembly reuse
Build on #2128 for selective GOROOT assembly reuse. Each reused file requires validation of:
Go ABI variant and argument/result layout;
TLS and getg representation;
stack-growth and g0 assumptions;
GC visibility and write-barrier requirements;
generated go_asm.h offsets;
unwind, DWARF, and C transition behavior;
architecture and OS build constraints.
Leaf assembly such as compatible syscall/time helpers can migrate before scheduler context-switch assembly. Runtime trampolines depend on #2129.
5. Scheduler, stack, and GC gate
Upstream runtime.newproc is coupled to systemstack, per-P run queues, growable Go stacks, stack maps, write barriers, and runtime assembly. Reusing it is a later gated phase and requires at least:
a defined LLGo G/M/P model and replaceable execution backend;
compatible function-value and compiler runtime-call ABI;
a getg representation usable from Go and assembly;
safe-point, root, and stack metadata contracts;
g0/system-stack or an explicitly adapted equivalent;
target-specific startup and cgo/C-library transition rules.
Until those gates pass, LLGo should reuse upstream naming, state meanings, and isolated algorithms where useful without pretending that the full upstream scheduler is active.
Phased work
Phase 0: audit and compile harness
Produce the runtime ownership/dependency report.
Add a diagnostic build mode that attempts selected GOROOT runtime files and reports unsupported directives, intrinsics, symbols, and assembly assumptions.
Record code-size and build-time baselines.
Phase 1: leaf Go source
Migrate self-contained helpers with matching LLGo semantics.
Convert broad package replacements into narrow source patches where possible.
Add upgrade checks against the pinned Go version.
Phase 2: platform and assembly helpers
Reuse compatible syscall, clock, and platform helpers.
LLGo already reuses part of the pinned GOROOT internal/abi package: its alternate package is merged without skipall, so unoverridden declarations continue to come from GOROOT. The LLGo file currently overrides only the compiler/runtime boundary such as Type, InterfaceType, FuncPCABI0, FuncPCABIInternal, NoEscape, TypeOf, and escape helpers. This is the preferred incremental shape and should be preserved.
Full package reuse has two independent compatibility gates:
Runtime metadata layout. LLGo runtime/abi.Type, map metadata, interface metadata, and LLGo-specific type flags are not layout-compatible with Go 1.26 internal/abi. This affects reflection, maps, interfaces, linker metadata, and GC visibility; closure ABI changes alone do not solve it.
Calling convention. Go RegArgs, reflect-call helpers, function values, the closure-context register, stack maps, and runtime assembly form one compiler/runtime ABI. LLGo currently represents a function value as code plus context and passes the context as an explicit hidden argument. That is intentionally friendly to the C ABI and the existing libffi bridge, but it is not the Go internal ABI.
Declarations should therefore be classified and checked as:
directly reusable constants and helpers that are independent of metadata layout and calling convention;
reusable GOROOT algorithms compiled through small source patches against LLGo-owned boundary types;
blocked declarations tied to type metadata, RegArgs, reflect-call, stack maps, or the closure-context ABI.
The closure-context ABI must be treated as a feasibility gate rather than silently changed:
Keeping the explicit hidden argument is the compatibility baseline and permits incremental source reuse, but requires adapters for Go-ABI assembly that expects a context register.
A Go-like dedicated context register could enable more direct assembly and ABI reuse, but requires LLVM register reservation or a defined calling convention on every target, C-call and callback wrappers, GC visibility rules, and architecture-specific libffi call/closure support. Rebuilding libffi with a fixed-register option alone does not marshal the context register.
A dual-ABI design can retain the current C-friendly function-value ABI and add narrow adapters for selected upstream Go-ABI entry points. This lowers migration risk at the cost of maintaining explicit transition points.
Phase 0 should prototype the latter two options on amd64 and arm64 and validate direct closures, C round trips, callbacks, libffi calls and closures, register-clobber stress, LTO, GC roots, panic/recover, and code-size/build-time impact. A later proposal or decision record should select the closure ABI only from those results; metadata-layout migration remains a separate prerequisite for full internal/abi reuse.
Summary
Incrementally replace LLGo-maintained standard-library
runtimesubstitutes with the pinned Go toolchain'sGOROOT/src/runtimeimplementation, using minimal source patches first and Plan 9 assembly only when LLGo's ABI and stack semantics are compatible.This is a staged compiler/runtime migration. It must not be treated as a file move or folded into the pthread G/M/P cleanup: directly enabling the upstream runtime would also require compatible compiler intrinsics, function ABI,
getg, stack switching, GC metadata, write barriers, generated offsets, OS startup, and runtime assembly.Current architecture
LLGo currently has two runtime layers with different responsibilities:
runtime/internal/runtimeis the compiler-facing low-level ABI. LLGo-generated code calls it for allocation, maps, channels, panic/defer/recover, goroutine startup, locality, and other language operations.runtime/internal/lib/runtimeis the replacement implementation compiled as the standard-library packageruntime. It provides public and linknamed runtime APIs and already depends on the low-level layer.The standard
runtimepackage is currently analtPkgReplacepackage. That replacement also prevents the normal GOROOT runtime assembly from being selected. The two directories therefore overlap in concepts, but they are not interchangeable copies today.Motivation
Maintaining large copied or simplified runtime implementations has several costs:
Where semantics and ABI match, the Go implementation should be the source of truth. LLGo-specific code should describe only the incompatibility.
Principles
.sfiles through Plan 9 assembly only after their ABI, TLS, stack, GC, and generated-offset assumptions are satisfied.Goals
runtimeeligible for incremental source patching rather than requiring an all-at-once replacement switch.runtime/internal/lib/runtimeas ownership moves back to GOROOT sources.Non-goals
runtime/proc.goin the first change.mstart,mcall,systemstack,morestack, or related assembly before LLGo implements their required g0 stack, register ABI, TLS, stack-map, and preemption semantics.Proposed design
1. Runtime ownership manifest
Add a checked manifest or generated report for runtime symbols that records:
The check must reject duplicate definitions and report upstream symbols that become newly required after a pinned Go upgrade.
2. Stable compiler ABI boundary
Keep compiler lowering pointed at a small, target-independent low-level ABI. For example, a
gostatement should submit an entry function and startup record to one runtimenewproc-like boundary; it should not emit pthread operations.Initially that boundary may still live in
runtime/internal/runtime. The standardruntimepackage can call or link to it. Ownership should move only when the upstream ABI is actually implementable; source directory layout alone must not dictate the compiler ABI.3. Incremental source patching of
runtimeExtend the existing source-patch/package-patch merge so
runtimecan be migrated declaration by declaration:runtime/internal/lib/runtime;Source patches should explain the semantic or ABI incompatibility and should be guarded by the relevant Go version and target tags.
4. Assembly reuse
Build on #2128 for selective GOROOT assembly reuse. Each reused file requires validation of:
getgrepresentation;go_asm.hoffsets;Leaf assembly such as compatible syscall/time helpers can migrate before scheduler context-switch assembly. Runtime trampolines depend on #2129.
5. Scheduler, stack, and GC gate
Upstream
runtime.newprocis coupled tosystemstack, per-P run queues, growable Go stacks, stack maps, write barriers, and runtime assembly. Reusing it is a later gated phase and requires at least:getgrepresentation usable from Go and assembly;Until those gates pass, LLGo should reuse upstream naming, state meanings, and isolated algorithms where useful without pretending that the full upstream scheduler is active.
Phased work
Phase 0: audit and compile harness
Phase 1: leaf Go source
Phase 2: platform and assembly helpers
Phase 3: standard runtime integration
runtime/internal/lib/runtimeand keep the compiler-facing layer focused on language ABI and backend primitives.Phase 4: scheduler and stack feasibility
newproc/getgABI against LLGo G/M/P.Validation
Every migration PR should include the relevant subset of:
c-archive, andc-sharedcoverage where the subsystem is reachable there;Acceptance criteria
Related work
internal/abireuse gateLLGo already reuses part of the pinned GOROOT
internal/abipackage: its alternate package is merged withoutskipall, so unoverridden declarations continue to come from GOROOT. The LLGo file currently overrides only the compiler/runtime boundary such asType,InterfaceType,FuncPCABI0,FuncPCABIInternal,NoEscape,TypeOf, and escape helpers. This is the preferred incremental shape and should be preserved.Full package reuse has two independent compatibility gates:
runtime/abi.Type, map metadata, interface metadata, and LLGo-specific type flags are not layout-compatible with Go 1.26internal/abi. This affects reflection, maps, interfaces, linker metadata, and GC visibility; closure ABI changes alone do not solve it.RegArgs, reflect-call helpers, function values, the closure-context register, stack maps, and runtime assembly form one compiler/runtime ABI. LLGo currently represents a function value as code plus context and passes the context as an explicit hidden argument. That is intentionally friendly to the C ABI and the existing libffi bridge, but it is not the Go internal ABI.Declarations should therefore be classified and checked as:
RegArgs, reflect-call, stack maps, or the closure-context ABI.The closure-context ABI must be treated as a feasibility gate rather than silently changed:
Phase 0 should prototype the latter two options on amd64 and arm64 and validate direct closures, C round trips, callbacks, libffi calls and closures, register-clobber stress, LTO, GC roots, panic/recover, and code-size/build-time impact. A later proposal or decision record should select the closure ABI only from those results; metadata-layout migration remains a separate prerequisite for full
internal/abireuse.