Summary
Remove the __llgo_stub call layer while keeping closure environment details out of Go/go-types signatures and retaining the two-word function value:
funcval = {code, env}
env == nil <=> code has no env parameter
env != nil <=> code needs env
Plain Go and C functions use {realCode, nil}. Closures and bound method wrappers use {realCode, nonNilEnv}.
Design
Each function keeps one semantic Go signature plus backend-only entry metadata: whether it needs env, the env type/value, and its physical LLVM entry type. Static calls use this metadata directly.
Validated native targets add a physical env parameter marked nest or swiftself. Native dynamic funcval calls always use one hidden-env edge, including nil env, so there is no runtime branch. LLVM still considers the env and no-env IR prototypes different; an instruction-free identity barrier prevents devirtualization of a no-env entry into the hidden-env edge.
Wasm, Windows, and unvalidated targets use an ordinary leading env parameter only on env-bearing entries. Dynamic calls branch on env != nil and emit two exact typed edges:
no-env: call code(args...)
env: call code(env, args...)
No unused env parameter is added to every Go function, so C functions, callbacks, variadics, and //export keep their exact ABI. Direct interface invocation passes its receiver as an ordinary argument; first-class method values use env-bearing bound wrappers.
Required environments are always non-nil. Nil pointer receiver method values are boxed, and interface method values capture the complete interface state. As an optimization, a source closure whose captures are all zero-sized may be reclassified as no-env and reconstruct their permitted shared address from LLGo's non-nil zero-size sentinel. Synthetic wrappers and captured pointer values are excluded.
Reflection and libffi
Use stock libffi without patching or rebuilding it:
- native hidden env: use
ffi_call_go when its static-chain ABI matches; otherwise CallWithEnv lets ffi_call marshal semantic arguments into a small final-hop trampoline that installs nest or swiftself and enters the real code with the original stack pointer;
- explicit fallback: add env to the FFI signature and value list only when env is non-nil.
reflect.MakeFunc remains a normal C-ABI libffi closure. Its callback userdata is separate, so its funcval remains {closureCode, nil}.
Benefits
- Go types and ordinary compiler code no longer add and later remove a fake context parameter.
- Function values point directly at real Go or C entries, reducing indirection, generated code, symbols, and metadata.
- Native dynamic calls have no env branch; Wasm remains type-correct without changing every Go function ABI.
- C functions and exported callbacks retain exact C ABI compatibility.
- Context transport is isolated in backend metadata and the final FFI boundary, maximizing reuse of LLGo's SSA and ABI lowering pipelines.
- Zero-sized lexical captures can avoid both environment allocation and transport when the optimization is provably safe.
Scope and acceptance
This covers entry metadata, closure and method-value construction/calls, stub removal, ABI rewriting, reflection, and stock-libffi integration. It does not change funcval layout, implement Wasm TLS/g.ctxt, or choose goroutine, stack, or GC policy.
Tests cover nest, swiftself, and Wasm explicit transport across plain Go functions, C functions, captured and zero-sized closures, nil receiver method values, interface method values, reflection, variadics, aggregates, sret, O0/O2, and LTO.
Summary
Remove the
__llgo_stubcall layer while keeping closure environment details out of Go/go-types signatures and retaining the two-word function value:Plain Go and C functions use
{realCode, nil}. Closures and bound method wrappers use{realCode, nonNilEnv}.Design
Each function keeps one semantic Go signature plus backend-only entry metadata: whether it needs env, the env type/value, and its physical LLVM entry type. Static calls use this metadata directly.
Validated native targets add a physical env parameter marked
nestorswiftself. Native dynamic funcval calls always use one hidden-env edge, including nil env, so there is no runtime branch. LLVM still considers the env and no-env IR prototypes different; an instruction-free identity barrier prevents devirtualization of a no-env entry into the hidden-env edge.Wasm, Windows, and unvalidated targets use an ordinary leading env parameter only on env-bearing entries. Dynamic calls branch on
env != niland emit two exact typed edges:No unused env parameter is added to every Go function, so C functions, callbacks, variadics, and
//exportkeep their exact ABI. Direct interface invocation passes its receiver as an ordinary argument; first-class method values use env-bearing bound wrappers.Required environments are always non-nil. Nil pointer receiver method values are boxed, and interface method values capture the complete interface state. As an optimization, a source closure whose captures are all zero-sized may be reclassified as no-env and reconstruct their permitted shared address from LLGo's non-nil zero-size sentinel. Synthetic wrappers and captured pointer values are excluded.
Reflection and libffi
Use stock libffi without patching or rebuilding it:
ffi_call_gowhen its static-chain ABI matches; otherwiseCallWithEnvletsffi_callmarshal semantic arguments into a small final-hop trampoline that installsnestorswiftselfand enters the real code with the original stack pointer;reflect.MakeFuncremains a normal C-ABI libffi closure. Its callback userdata is separate, so its funcval remains{closureCode, nil}.Benefits
Scope and acceptance
This covers entry metadata, closure and method-value construction/calls, stub removal, ABI rewriting, reflection, and stock-libffi integration. It does not change funcval layout, implement Wasm TLS/
g.ctxt, or choose goroutine, stack, or GC policy.Tests cover
nest,swiftself, and Wasm explicit transport across plain Go functions, C functions, captured and zero-sized closures, nil receiver method values, interface method values, reflection, variadics, aggregates,sret, O0/O2, and LTO.