pub struct Proto {Show 22 fields
pub code: Box<[Inst]>,
pub consts: Box<[Value]>,
pub protos: Box<[Gc<Proto>]>,
pub upvals: Box<[UpvalDesc]>,
pub num_params: u8,
pub is_vararg: bool,
pub has_vararg_table_pseudo: bool,
pub has_compat_vararg_arg: bool,
pub max_stack: u8,
pub lines: Box<[u32]>,
pub source: Gc<LuaStr>,
pub line_defined: u32,
pub last_line_defined: u32,
pub locvars: Box<[LocVar]>,
pub cache: Cell<Option<Gc<LuaClosure>>>,
pub env_upval_idx: u8,
pub jit: Cell<JitProtoState>,
pub trace_hot_count: Cell<u32>,
pub call_hot_count: Cell<u32>,
pub trace_discard_count: Cell<u32>,
pub trace_gave_up: Cell<bool>,
pub traces: TRefLock<Vec<TArc<CompiledTrace>>>,
/* private fields */
}Expand description
A compiled function (PUC Proto). Immutable after compilation.
Fields§
§code: Box<[Inst]>Bytecode instructions, in execution order.
consts: Box<[Value]>Constant table referenced by LoadK / *K opcodes.
protos: Box<[Gc<Proto>]>Nested prototypes referenced by Closure.
upvals: Box<[UpvalDesc]>Upvalue descriptors (one per upvalue this function captures).
num_params: u8Fixed parameter count.
is_vararg: boolWhether the function accepts ....
has_vararg_table_pseudo: boolPUC lparser.c emits a hidden (vararg table) locvar for a function
declared with an explicit anonymous (...) (and NOT for a main chunk’s
implicit vararg, nor for (...t) which becomes a named local). When
true, debug.getlocal exposes the pseudo at num_params + 1.
has_compat_vararg_arg: boolPUC 5.1 LUAI_COMPAT_VARARG: the function declared ... and so gets a
hidden local named arg at num_params populated at entry with the
extra args as {n = count, [1] = e1, [2] = e2, …}. The slot keeps the
shape across resumes; user code can reassign it. 5.1 db.lua :279 reads
arg.n from inside a line hook walking debug.getlocal(2, i).
max_stack: u8registers needed by a frame of this function
lines: Box<[u32]>line of each instruction (same length as code)
source: Gc<LuaStr>chunk name, for error messages
line_defined: u32Source line where the function was defined.
last_line_defined: u32line of the function’s closing end (PUC lastlinedefined); 0 for the
main chunk
locvars: Box<[LocVar]>local-variable debug records (name + live pc range)
cache: Cell<Option<Gc<LuaClosure>>>PUC 5.2+ closure cache (Proto.cache): the last LClosure built from
this Proto. When OP_CLOSURE fires, the VM compares each candidate
upvalue to the cached closure’s same-slot upvalue (getcached); on a
full match the cached closure is reused, so two function() ... end
literals reached from the same source compile but with identical
upvalue bindings compare equal. closure.lua’s for i=1,5 do a[i]=function(x) return x+a+_ENV end end asserts that subsequent
iterations reuse the closure; capturing i instead defeats the cache.
env_upval_idx: u8Index into upvals of the _ENV upvalue (5.1 per-function-env
model needs to clone-on-closure), or u8::MAX for “no _ENV
upval”. Computed once at Proto construction so Op::Closure’s
5.1 path doesn’t string-compare across upvals per closure.
jit: Cell<JitProtoState>P11-S2 — JIT cache slot. Untried on Proto creation; the first
Vm::call_value on a closure whose body fits the S1 whitelist
flips it to Compiled(fn ptr) and the JitHandle that backs
the mmap is parked on the Vm.jit_handles Vec for the Vm’s
lifetime. Failed records the whitelist miss so subsequent
calls skip the compile attempt.
trace_hot_count: Cell<u32>P12-S1 — trace JIT hot-loop detector. Incremented by Vm::run
on each backward-jump dispatched within this Proto. Once the
counter passes TRACE_HOT_THRESHOLD, the next visit to the
backward-jump target promotes that PC to a trace head and
begins recording (S2+). Cell<u32> matches the interp’s
single-threaded dispatch and pays no atomic cost. Cap at
u32::MAX / 2 to leave headroom above the threshold.
call_hot_count: Cell<u32>P12-S4 — trace-on-call counter. Incremented by begin_call on
every Lua-callee push into this Proto. Once it passes
CALL_HOT_THRESHOLD, the next call into this Proto promotes
pc=0 to a trace head and begins recording. Lets the trace
JIT cover self-recursive functions whose body holds no
negative Op::Jmp (fib, recursive make/check in
binary_trees), where the back-edge counter never triggers.
trace_discard_count: Cell<u32>P13-S13-I — count of S13-H “partial-coverage” discards on
this Proto’s call-triggered recordings. Each discard is a
new opportunity for the recorder to record a different
(hopefully longer) trace at a deeper recursion point; the
trigger condition re-uses c >= THRESHOLD && !already_cached (S13-H) so the next call retries. Without
a cap, pathologically-branchy workloads like binary_trees
(make body contains 2 nested self-recursive calls)
produce a 1500+ discard storm — the recorder never
captures a covered trace because every base / shallow-
depth entry caught yields a partial path. The S13-I cap
bounds the storm: after MAX_DISCARDS = 5 discards, the
next close skips the coverage check and compiles + caches
whatever shape it has (length gate will likely refuse
dispatch but at least the trigger stops firing).
trace_gave_up: Cell<bool>P13-S13-K — once the S13-I discard cap forces a compile on
this Proto (the recorder gave up trying to capture a
covered trace and just compiled whatever shape it had), set
this flag to true. Both trigger gates (back-edge in
Op::Jmp and call in begin_call) short-circuit on
gave_up BEFORE doing the proto.traces.borrow() +
linear-scan already_cached check. Each post-cap call into
such a Proto avoids the RefCell borrow + Vec scan
(binary_trees_pattern’s 20k make + 20k check calls per
run = 40k RefCell borrows saved). The gave_up flag never
flips back to false within a Vm — gave-up is permanent
on the Proto, mirroring the JitProtoState::Failed
invariant.
traces: TRefLock<Vec<TArc<CompiledTrace>>>P12-S2 — compiled trace cache for this Proto. A successful
compile_trace(record) (S2.B) parks its CompiledTrace here;
Vm::run’s S3 dispatcher (next phase) iterates this on each
back-edge target visit. RefCell because compile is invoked
from inside Vm::run and may need to push while another op
is mid-dispatch in the same Proto. Empty Vec until S2 lands.
Implementations§
Source§impl Proto
impl Proto
Sourcepub fn stable_hash(&self) -> [u8; 16]
pub fn stable_hash(&self) -> [u8; 16]
v1.3 Phase AOT Stage 7 sub-piece 4 — stable 128-bit hash over a
Proto’s identity-defining bytes. Two Protos whose Lua source +
dialect compile to the same bytecode hash to the same digest;
distinct sources hash distinct. The digest is stable across
dump / undump round-trips and across separate process runs,
so an AOT pipeline (which fingerprints protos at compile time)
and the deploy Vm (which fingerprints the same protos after
undumping the embedded bytecode) agree on which (Proto, pc)
site a precompiled trace targets.
§What’s fed into the hash
code: the raw u32 packed words, in order.consts: per entry, a one-byte discriminant + payload bytes (Int/Float as raw 8-byte LE; Str as[len_u32_le | bytes]; Nil/Bool as discriminant alone). Heap-pointer variants inValue(Table / Closure / Native / Coro / Userdata / LightUserdata) never appear in a Proto’s constant table — constants are restricted to nil / bool / number / string by the Lua compiler — so adebug_assert!catches the contract if a future refactor changes that.upvals: per descriptor,in_stackbyte +indexbyte +read_onlybyte + name bytes (length-prefixed u32 LE).num_params,is_vararg,max_stack: single-byte each.
§What’s NOT fed in
- Nested
protos: each nested Proto has its ownstable_hash; parent identity is determined by its own immediate bytes only. Callers that need a “whole tree” identity should hash the roots they care about. lines,locvars,source,line_defined,last_line_defined: debug metadata. A.luasource edited to add a comment shouldn’t invalidate AOT traces — bytecode is the identity, not the editor cursor.- JIT cache fields (
jit,traces,trace_hot_count, …),cache,has_vararg_table_pseudo,has_compat_vararg_arg,env_upval_idx: runtime-only state derived from the load-bearing fields above.
§Algorithm
Hand-rolled FNV-1a-128 (no third-party deps — luna-core 0-dep
contract is hard). The standard 128-bit constants:
- offset basis =
0x6c62272e07bb014262b821756295c58d - prime =
0x0000000001000000000000000000013b
Collision resistance suffices for AOT proto ID — collisions would manifest as a precompiled trace dispatched against the wrong Proto, but the dispatcher’s existing guards (entry_tags match, head_pc match, register types match) would deopt to interp on a mismatch rather than corrupt state.