runtype
June 30, 2026 ยท View on GitHub
Synthesizes Go rtypes that carry interpreted-method metadata, so native reflect/itab dispatch can call interpreter methods directly.
Overview
runtype (Go runtime types) is the low-level half of mvm's native method-dispatch
mechanism (see ADR-021).
Go cannot attach methods to a reflect.StructOf-built type, so runtype builds a
fresh rtype by hand -- mirroring internal/abi layouts byte-for-byte -- and
overlays an UncommonType + method array whose dispatch entries point at
pre-built stub PCs.
It sits beside the pipeline rather than in it: the interpreter calls into runtype
(via stdlib/stubs) after a type's methods are known.
runtype deliberately carries no knowledge of method shapes or handlers.
Fill takes a MethodSpec with an already-resolved stub PC; the shape
catalog and stub pools live in stubs, which imports runtype
one-directionally.
Key types and functions
A type's identity is reserved once at materialize (an empty method table allocated over its layout) and the methods are filled in place at attach, so a composite that captured the reserved rtype sees the methods afterward with no swap. This materialize-once design replaced an earlier swap+cascade (retired).
MethodSpec--{Name, Exported, Sig, StubPC}. One method to install;StubPCis the dispatch-stub entry PC the caller resolved from the shape.Reservation+ReserveMethods(layout, name, pkg)-- reserve a named method-bearing identity overlayout(struct / primitive / slice / array / map / func) withMcount 0;Reservation.Type()is usable immediately.ReservePtrMethods(elem, ...)-- reserve*Tand wireelem.PtrToThissoreflect.PointerTo(elem)returns this method-bearing*T, all before fill.(*Reservation).Fill([]MethodSpec)-- install the methods into the reserved table in place, publishing the counts last (preserves rtype identity).FillStructLayout(reserved, realLayout)-- patch a struct reserved with a provisional layout to its real one in place, so a field cycle (*T) resolves to the reserved identity.InterfaceOf(name, pkg, []Imethod)-- a method-bearing interface rtype used as a satisfaction target (e.g. forerrors.Asagainst an anonymous interface); methods live inline, no stub pool.- Derived constructors
PointerTo/SliceOf/ArrayOf/ChanOf/MapOf-- analogs ofreflect.PointerToetc. that work on synthesized-rtype elems via a layout-shadow registry (reflect.*Ofcrashes on them). IsSynth/HasPtrToThis/StampName-- predicates and the in-place name stamper used by the vm derive layer.FuncPC(fn)-- entry PC of a func value; exported for thestubspools.
Internal design
A synthesized rtype is a typed Go struct (so the GC sees its pointer fields) holding a
copy of the source abiType prefix, an abiUncommon, and a [16]abiMethod
array (maxMethods). stampHeader gives it a fresh hash, a restamped Str
(via addReflectOff), and clears tflagExtraStar; installMethods sorts
methods by name (reflect's method lookup needs sorted order) and writes each
method's Ifn/Tfn to its stub PC.
flowchart LR
layout["layout rtype<br/>(reflect.StructOf, ...)"] --> reserve["runtype.Reserve*"]
reserve --> clone["copy abiType prefix<br/>+ stampHeader"]
clone --> overlay["overlay abiUncommon<br/>+ empty [16]abiMethod"]
overlay --> reg["registerLayout<br/>(synthesized -> native shadow)"]
overlay --> rt["reserved reflect.Type<br/>(Mcount 0)"]
specs["[]MethodSpec<br/>(Name, Sig, StubPC)"] --> fill["(*Reservation).Fill"]
rt --> fill
fill --> done["methods installed in place<br/>(identity preserved)"]
Key files: abi.go (the internal/abi mirror, validated by abi_test.go),
linkname.go (addReflectOff, rtypePtr, asReflectType), name.go
(encodeName, hand-rolled to avoid linknaming reflect.newName), hash.go,
funcpc.go, reserve.go (Reserve*/Fill), derive.go (constructors + the
synthesized -> native layout registry that lets derivations nest),
synth.go/synth_kinds.go/synth_ptr.go (the synth rtype containers +
stampHeader/makeUncommon/installMethods helpers reserve uses),
interface.go.
Uncommon-type offset is per-kind (pointer/slice/chan use a 56-byte
PtrType+UncommonType layout; primitives use 48) and must match the runtime's
Uncommon() dispatch, or the method walk reads garbage.
Dependencies
- Standard library only:
reflect,unsafe,sync,sync/atomic,errors/fmt/sort. No internal mvm imports. - Consumed by
derive/(the derive/predicate helpers) andvm/(vm/type.gore-exports,vm/synth_bridge.goforClone/InterfaceOf) and bystdlib/stubs(FuncPC+ theAttach*entry points).
Open questions / TODOs
abi.gomirrors unexported runtime layouts; bump and re-runabi_test.goon each Go release.Type.Method(i).Func.Calldoes not work for non-direct kinds (natural-ABI receiver); the common interface-dispatch path does.- Synthesized rtypes are never freed, so they pollute the process
itabcache (REPL redefinition leaks).