Skip to main content

Proto

Struct Proto 

Source
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: u8

Fixed parameter count.

§is_vararg: bool

Whether the function accepts ....

§has_vararg_table_pseudo: bool

PUC 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: bool

PUC 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: u8

registers 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: u32

Source line where the function was defined.

§last_line_defined: u32

line 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: u8

Index 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

Source

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 in Value (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 a debug_assert! catches the contract if a future refactor changes that.
  • upvals: per descriptor, in_stack byte + index byte + read_only byte + 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 own stable_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 .lua source 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.

Auto Trait Implementations§

§

impl !Freeze for Proto

§

impl !RefUnwindSafe for Proto

§

impl !Send for Proto

§

impl !Sync for Proto

§

impl !UnwindSafe for Proto

§

impl Unpin for Proto

§

impl UnsafeUnpin for Proto

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.