pub struct Vm {
pub heap: Heap,
pub capi_stack: Vec<Value>,
pub capi_cstr_pin: Option<CString>,
/* private fields */
}Expand description
A Lua virtual machine: one OS thread’s worth of Lua state.
§Threading model
Vm is !Send + !Sync. The GC uses Gc<T> = NonNull<T> over
an intrusive mark-sweep heap (not Rc<RefCell<T>>), and the trace
JIT side-table uses Rc<CompiledTrace> — both single-threaded by
design. Embedders that want concurrency spawn one Vm per OS
thread (or per single-thread Tokio worker) and exchange data via
channels. See docs/threading.md for
canonical embedding patterns including Tokio current_thread,
LocalSet on multi-thread, and Vm-per-OS-thread + channels.
The constraint is enforced at compile time:
fn must_be_send<T: Send>() {}
must_be_send::<luna_core::Vm>(); // error[E0277]: `Vm` cannot be sent between threads safelyA future feature = "send" (post-v1.1 sprint) will gate an
opt-in Arc<RwLock<T>> mode with a hard ≤8% perf regression
budget. See .dev/rfcs/v1.1-rfc-vm-send-sync.md for the design.
Fields§
§heap: HeapThe GC heap owned by this VM. Embedders normally interact via the
Vm methods (load / call_value / set_global / …) rather than
the heap directly.
capi_stack: Vec<Value>C ABI scratch (capi module): the host-visible value stack that C
callers operate on via lua_pushinteger / lua_tostring / etc.
Kept here (instead of in a separate LuaState wrapper) so the
trampoline that bridges to a LuaCFunction can safely cast the
Vm pointer it already holds to the public *mut LuaState type
without any aliasing of &mut Vm against &mut LuaState.vm.
capi_cstr_pin: Option<CString>Pinned CString backing the pointer last returned by lua_tostring;
valid until the next lua_tostring on the same Vm.
Implementations§
Source§impl Vm
impl Vm
Sourcepub const DEFAULT_LOADER_INPUT_BUDGET: usize
pub const DEFAULT_LOADER_INPUT_BUDGET: usize
Default loader input budget — 256 MiB.
Vm::load and the Lua-level load(reader, ...) both refuse
sources whose byte length crosses this cap, returning the
PUC-shaped not enough memory error rather than letting the
host allocator try (and crash) to hold the next chunk.
Sourcepub fn new(version: LuaVersion) -> Vm
pub fn new(version: LuaVersion) -> Vm
Build a fully-loaded Vm — the default for embedders that want PUC’s
standard library surface. Equivalent to Vm::new_minimal(version)
followed by vm.open_all_libs().
Sourcepub fn new_minimal(version: LuaVersion) -> Vm
pub fn new_minimal(version: LuaVersion) -> Vm
P09 embedding: build a Vm with no standard libraries loaded. Embedders
that want a sandbox (Redis-style scripts, in-game scripting with
a curated API) call this and then open_base / open_math / etc.
selectively. The Vm is otherwise fully initialized (main coroutine,
RNG seed, GC) so eval and call_value are immediately usable.
Sourcepub fn install_jit_backend<C, T>(&mut self, chunk: C, trace: T)where
C: IntChunkCompiler + 'static,
T: TraceCompiler + 'static,
pub fn install_jit_backend<C, T>(&mut self, chunk: C, trace: T)where
C: IntChunkCompiler + 'static,
T: TraceCompiler + 'static,
v1.1 A1 Session C — install a caller-supplied JIT backend. The
luna crate uses this to swap in its CraneliftBackend; tests
or third-party backends pass their own crate::jit::IntChunkCompiler /
crate::jit::TraceCompiler implementations. Re-installing on a Vm whose
closures already populated Proto.jit: JitProtoState::Compiled
does NOT evict those cached entries — call right after
construction for a clean swap.
Naming: install_jit_backend (not install_default_jit)
because the “default” in luna-core is NullJitBackend; the
“default JIT” lives in the luna crate.
Sourcepub fn install_jit_storage<S>(&mut self, storage: S)where
S: JitStorage + 'static,
pub fn install_jit_storage<S>(&mut self, storage: S)where
S: JitStorage + 'static,
v2.0 Track J sub-step J-B — install a caller-supplied JIT
storage holder. Default is crate::jit::NullJitStorage;
the luna_jit crate’s install_default_jit pairs this with
install_jit_backend(CraneliftBackend, CraneliftBackend) to
also install a fresh CraneliftJitStorage. Storage holds
the per-Vm JIT cache + handle collections that used to be
thread_local!s in luna_jit::jit_backend.
Idempotency: re-installing storage on a Vm that already
holds compiled-trace pointers WILL evict their owners (the
old CraneliftJitStorage’s JITModules drop their mmap
pages). Call right after construction for a clean swap.
Sourcepub fn install_null_jit(&mut self)
pub fn install_null_jit(&mut self)
v1.1 A1 Session A — install the no-op JIT backend. try_compile
reports “skipped” so every closure stays on the interpreter
path, and the trace recorder’s compile attempt always returns
None. Intended for tests that want to verify the trait
boundary works in a JIT-free configuration, and for the future
luna-core build path that ships without Cranelift.
Calling this on a Vm whose closures already populated
Proto.jit: JitProtoState::Compiled does NOT evict those
cached entries — the dispatcher will still call into them. For
a truly JIT-free run, call this immediately after construction.
Sourcepub fn open_all_libs(&mut self)
pub fn open_all_libs(&mut self)
Open the entire 5.5 standard library on a new_minimal-built Vm.
Vm::new calls this; sandboxed embedders open libraries one at a
time instead (open_base, open_math, open_table, …).
Sourcepub fn open_base(&mut self)
pub fn open_base(&mut self)
Install the base library (print, type, pairs, tostring,
pcall, error, assert, select, setmetatable, getmetatable,
rawequal, rawget, rawset, rawlen, next, tonumber,
collectgarbage, warn on 5.4+, _VERSION, _G, plus 5.1’s
retired globals unpack, loadstring, setfenv, getfenv,
newproxy, gcinfo when version == 5.1). Safe to call at most
once per Vm.
Sourcepub fn open_table(&mut self)
pub fn open_table(&mut self)
Install the table standard library.
Sourcepub fn open_string(&mut self)
pub fn open_string(&mut self)
Install the string standard library (and the shared string metatable).
Sourcepub fn open_os_io(&mut self)
pub fn open_os_io(&mut self)
os and io are merged because file userdata shares state with both
(io.tmpname and os.tmpname are the same function, io.popen
wraps os.execute’s shell).
Sourcepub fn open_debug(&mut self)
pub fn open_debug(&mut self)
Install the debug standard library (introspection / hooks). Off by
default for sandbox embedders.
Sourcepub fn open_coroutine(&mut self)
pub fn open_coroutine(&mut self)
Install the coroutine standard library.
Sourcepub fn open_package(&mut self)
pub fn open_package(&mut self)
package plus the 5.1-only module and package.seeall aliases.
Sourcepub fn open_bit32(&mut self)
pub fn open_bit32(&mut self)
5.2-only bit32 library (5.3+ retired in favour of native bitwise
ops on 64-bit integers).
Sourcepub fn native(&mut self, f: NativeFn) -> Value
pub fn native(&mut self, f: NativeFn) -> Value
Allocate a native function object (no upvalues): builtin registration.
Sourcepub fn native_with(&mut self, f: NativeFn, upvals: Box<[Value]>) -> Value
pub fn native_with(&mut self, f: NativeFn, upvals: Box<[Value]>) -> Value
Allocate a native function object with captured upvalues.
Sourcepub fn set_string_metatable(&mut self, mt: Option<Gc<Table>>)
pub fn set_string_metatable(&mut self, mt: Option<Gc<Table>>)
Install the shared string metatable (string library, P04).
Sourcepub fn globals(&self) -> Gc<Table>
pub fn globals(&self) -> Gc<Table>
The current globals table (_G / _ENV source for new chunks).
Sourcepub fn version(&self) -> LuaVersion
pub fn version(&self) -> LuaVersion
The Lua dialect this VM was constructed for (5.1 / 5.2 / 5.3 / 5.4 / 5.5). Determines numeric semantics, available standard libraries, and metamethod behavior.
Sourcepub fn set_global<V: IntoValue>(
&mut self,
name: &str,
v: V,
) -> Result<(), LuaError>
pub fn set_global<V: IntoValue>( &mut self, name: &str, v: V, ) -> Result<(), LuaError>
Set a global by name. v may be any IntoValue: a primitive
(i64, f64, bool, &str, String, Vec<u8>), a Value
directly, an Option<T>, or a Gc<Table> / Gc<LuaClosure> /
Gc<NativeClosure> handle.
Returns Err(LuaError) only if the globals table overflows
(extremely unlikely in practice — MAX_ASIZE = 1 << 27).
String interning + key construction cannot fail.
let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().build();
vm.set_global("answer", 42).unwrap();
vm.set_global("ratio", 0.5_f64).unwrap();
vm.set_global("hello", "world").unwrap();
let r = vm.eval("return answer, ratio, hello").unwrap();
assert_eq!(r.len(), 3);Sourcepub fn define_macro(&mut self, name: &str, m: Box<dyn Macro>)
pub fn define_macro(&mut self, name: &str, m: Box<dyn Macro>)
v1.3 Phase ML — register a MacroLua macro under name. Inert
under non-MacroLua dialects (the macro is stored but the load
path only consults the registry when
self.version == LuaVersion::MacroLua).
name is stored without the leading @ — source code writes
@double(x) to invoke a macro registered as "double".
Sourcepub fn clear_macros(&mut self)
pub fn clear_macros(&mut self)
v1.3 Phase ML — drop all MacroLua macros (built-in + custom). Mostly useful for tests / dogfood resets.
Sourcepub fn load(
&mut self,
src: &[u8],
chunkname: &[u8],
) -> Result<Gc<LuaClosure>, SyntaxError>
pub fn load( &mut self, src: &[u8], chunkname: &[u8], ) -> Result<Gc<LuaClosure>, SyntaxError>
Parse + compile a chunk and close it over the globals table.
Sourcepub fn eval(&mut self, src: &str) -> Result<Vec<Value>, LuaError>
pub fn eval(&mut self, src: &str) -> Result<Vec<Value>, LuaError>
Compile and run src as an anonymous chunk; return its results.
Source name in the traceback is "=eval". Syntax errors are
surfaced as LuaError carrying the formatted PUC-style message
(interned through the heap so the error value composes with
pcall / error_text like any runtime error).
Sourcepub fn error_text(&self, e: &LuaError) -> String
pub fn error_text(&self, e: &LuaError) -> String
Render an error value for messages/tests. Non-string errors —
error({code=…}), error(42), etc. — collapse to a type tag
("(error object is a table value)"); embedders that need
structured payloads should inspect e.0 directly. Errors whose
text starts with "native panic:" indicate a Rust panic
crossed catch_unwind — the Vm may be inconsistent and should
be dropped (do not reuse).
Sourcepub fn call_value(
&mut self,
f: Value,
args: &[Value],
) -> Result<Vec<Value>, LuaError>
pub fn call_value( &mut self, f: Value, args: &[Value], ) -> Result<Vec<Value>, LuaError>
Call any callable value from the host (or from natives like pcall).
Sourcepub fn collect_garbage(&mut self) -> usize
pub fn collect_garbage(&mut self) -> usize
Run a full collection with the VM’s roots, then run any __gc
finalizers the collection scheduled. A no-op (returns 0) when already
inside a finalizer — the collector is not reentrant (PUC).
Sourcepub fn warn_log_take(&mut self) -> Vec<Vec<u8>>
pub fn warn_log_take(&mut self) -> Vec<Vec<u8>>
Drain the in-process warning log (one entry per emitted message, sans
"Lua warning: " prefix and newline). For test harnesses that want to
assert on warn output without scraping stderr.
Sourcepub fn set_instr_budget(&mut self, budget: Option<i64>)
pub fn set_instr_budget(&mut self, budget: Option<i64>)
Arm the cooperative instruction budget (P09 embedding). The run loop
decrements this once per dispatch turn; on zero it raises a catchable
"instruction budget exceeded" error and disarms itself so the host
can resume with a fresh budget on the next call. None removes the
cap. Pass Some(n) before eval/call_value for the embedder’s
short-script semantics.
Sourcepub fn instr_budget_remaining(&self) -> Option<i64>
pub fn instr_budget_remaining(&self) -> Option<i64>
Remaining instruction budget (None when unbounded).
Sourcepub fn set_jit_enabled(&mut self, enabled: bool)
pub fn set_jit_enabled(&mut self, enabled: bool)
Toggle the cranelift JIT (P11). Default true. Sandbox embedders
must disable JIT when relying on instr_budget — see the
jit_enabled field doc for the rationale.
Sourcepub fn jit_enabled(&self) -> bool
pub fn jit_enabled(&self) -> bool
Current JIT enable state.
Sourcepub fn set_trace_jit_enabled(&mut self, enabled: bool)
pub fn set_trace_jit_enabled(&mut self, enabled: bool)
Toggle the trace JIT (P12). Off by default while the sprint
develops. When enabled, hot back-edges are counted on
Proto.trace_hot_count; once the counter passes
TRACE_HOT_THRESHOLD, the dispatch loop enters recording
mode at the back-edge target. Stays a no-op until S2’s
trace lowerer and S3’s dispatcher land.
Sourcepub fn set_p16_self_link_enabled(&mut self, enabled: bool)
pub fn set_p16_self_link_enabled(&mut self, enabled: bool)
P16-A — opt-in flag for the self-link cycle catch. See field
docs for the correctness blocker. Default false.
Sourcepub fn p16_self_link_enabled(&self) -> bool
pub fn p16_self_link_enabled(&self) -> bool
Current state of the P16-A self-link cycle catch.
Sourcepub fn trace_jit_enabled(&self) -> bool
pub fn trace_jit_enabled(&self) -> bool
Current trace-JIT enable state.
Sourcepub fn trace_closed_count(&self) -> u64
pub fn trace_closed_count(&self) -> u64
Number of traces that have closed cleanly (looped back to the head PC) since this Vm was constructed. Cumulative; used by tests + tuning. Will become the dominant signal once S2’s compile + cache lands.
Sourcepub fn trace_aborted_count(&self) -> u64
pub fn trace_aborted_count(&self) -> u64
Number of traces that have aborted (exceeded MAX_TRACE_LEN or hit an un-recordable op — the latter lands at S2).
Sourcepub fn trace_inline_abort_count(&self) -> u64
pub fn trace_inline_abort_count(&self) -> u64
P13-S13-G v2 — number of compiled traces whose close shape
is TraceEnd::InlineAbort (depth>0 boundary). Such traces
pin dispatchable=false because the dispatcher can’t
resume at a depth>0 PC without the matching CallFrames.
S4-step4b’s frame-mat helper could synthesise those, but
the InlineAbort emit path isn’t wired up yet — fresh
pickup work for S13-G v2-full.
Sourcepub fn trace_dispatch_off_reasons(&self) -> &[&'static str]
pub fn trace_dispatch_off_reasons(&self) -> &[&'static str]
P13-S13-G v2.5 — see JitCounters::dispatch_off_reasons.
Sourcepub fn trace_compile_failed_reasons(&self) -> &[&'static str]
pub fn trace_compile_failed_reasons(&self) -> &[&'static str]
P13-S13-G v2.6 — see JitCounters::compile_failed_reasons.
Sourcepub fn trace_closed_lens(&self) -> &[(bool, usize)]
pub fn trace_closed_lens(&self) -> &[(bool, usize)]
P13-S13-H — see JitCounters::closed_lens. Returns
(is_call_triggered, ops_len) for every trace that closed.
Sourcepub fn trace_close_cause_counts(&self) -> &HashMap<&'static str, u64>
pub fn trace_close_cause_counts(&self) -> &HashMap<&'static str, u64>
v2.0 Track-R R2 — see [crate::vm::jit_state::JitCounters::close_cause_counts].
Per-reason close-cause counts (recorder-side abort/discard +
lowerer-side dispatch_off labels) keyed by &'static str.
Sourcepub fn trace_downrec_link_compiled_count(&self) -> u64
pub fn trace_downrec_link_compiled_count(&self) -> u64
v2.0 Track-R R3b — number of compiled traces whose
CompiledTrace.downrec_link is Some(_) (lowerer’s
downrec_idx_opt arm emitted the stitch sentinel + caller-pc
guard scaffold). R3b regression pin checks >= 1 on a fib(3)
hot loop with p16-on. R3b keeps dispatchable = false even
when this count bumps; R3d will lift it.
Sourcepub fn trace_downrec_dispatched_count(&self) -> u64
pub fn trace_downrec_dispatched_count(&self) -> u64
v2.0 Track-R R3c — see
[crate::vm::jit_state::JitCounters::downrec_dispatched]. Number
of times the dispatcher’s is_downrec_sentinel arm fired and
classified the return as a caller-pc-guard HIT.
Sourcepub fn trace_downrec_deopt_count(&self) -> u64
pub fn trace_downrec_deopt_count(&self) -> u64
v2.0 Track-R R3c — see
[crate::vm::jit_state::JitCounters::downrec_deopt]. Number of
times the dispatcher entered a downrec_link-bearing trace and
the trace returned via the lowerer’s deopt block (caller-pc
guard MISS), or the dispatcher itself force-deopted via the
stitch-cycle checkpoint.
Sourcepub fn trace_multi_way_guard_emitted_count(&self) -> u64
pub fn trace_multi_way_guard_emitted_count(&self) -> u64
v2.0 Track-R R3d — see
[crate::vm::jit_state::JitCounters::multi_way_guard_emitted].
Number of compiled traces whose lowerer emitted a multi-way
caller-pc guard chain (>= 2 distinct caller_pc candidates)
at the TraceEnd::DownRec close + lifted dispatchable = true.
Sourcepub fn trace_compiled_count(&self) -> u64
pub fn trace_compiled_count(&self) -> u64
P12-S2.C — number of closed traces the lowerer compiled and
parked on Proto.traces. Re-records of the same head_pc are
deduped (the second close finds the head_pc already cached
and skips compile), so this never exceeds trace_closed_count.
Sourcepub fn trace_field_ic_snapshot_count(&self) -> u64
pub fn trace_field_ic_snapshot_count(&self) -> u64
v2.1 Phase 1I.B — number of times the recorder captured a
crate::jit::trace_types::FieldIcSnapshot under
LUNA_JIT_FIELD_IC=1. Stays 0 on the env-default path. Used
by the Phase 1I.B opt-in fire test to verify the env gate
wiring round-trips end-to-end (env -> recorder -> snapshot
-> counter -> getter -> assertion).
Sourcepub fn trace_compile_failed_count(&self) -> u64
pub fn trace_compile_failed_count(&self) -> u64
P12-S2.C — number of closed traces the lowerer rejected
(any of the bail conditions in
crate::jit::trace::try_compile_trace).
Sourcepub fn trace_dispatched_count(&self) -> u64
pub fn trace_dispatched_count(&self) -> u64
P12-S3 — number of times the dispatcher jumped into a
compiled trace. Bumps on every entry; trace_deopt_count
counts the subset where the trace returned with a parked
jit_pending_err.
Sourcepub fn trace_deopt_count(&self) -> u64
pub fn trace_deopt_count(&self) -> u64
P12-S3 — number of trace entries that came back with
jit_pending_err set (typically a metatable shadowed an
index inside a helper, forcing the dispatcher to fall back
to the interpreter without committing the trace’s result).
Sourcepub fn trace_side_trace_started_count(&self) -> u64
pub fn trace_side_trace_started_count(&self) -> u64
P15-A v1 — number of times the dispatcher started a side
trace recording (an exit_hit_counts slot crossed
crate::jit::trace::HOTEXIT_THRESHOLD while active_trace
was None and trace JIT was enabled). Each unit is exactly one
start_side_trace call; the actual compile success counts
under Self::trace_compiled_count like any other trace.
Probe use: distinguishes the “side-trace pipeline fired”
signal from the “primary back-edge / call-trigger fired”
signal so v0-v3 architectural progress is visible without
reading per-counter histograms.
Sourcepub fn trace_side_trace_compiled_count(&self) -> u64
pub fn trace_side_trace_compiled_count(&self) -> u64
P15-A v2-A — number of side-trace recordings that closed,
compiled successfully, AND patched their parent’s
exit_side_trace_ptrs[exit_idx]. The parent’s IR doesn’t
dispatch through these ptrs yet (v2-B/C job), but the
counter + ptr write proves the compile + link pipeline is
complete end-to-end.
Sourcepub fn trace_side_trace_shape_mismatch_count(&self) -> u64
pub fn trace_side_trace_shape_mismatch_count(&self) -> u64
P15-A v2-C-A5-C — number of side traces that compiled
successfully but were SHEDDED by the close-handler shape-
match gate (exit_tags_match_entry_tags). High ratios
vs. trace_side_trace_compiled_count indicate the
architecture is shedding lots of would-be side traces;
useful as a tuning probe for future relaxation of the
gate or for child-IR re-specialisation against parent’s
exit shape.
Sourcepub fn trace_sinkable_seen_count(&self) -> u64
pub fn trace_sinkable_seen_count(&self) -> u64
P12-S5-A — sum of NewTable sites the pre-emit escape sweep
classified as crate::jit::trace::EscapeState::Sinkable
across every successfully compiled trace on this Vm. The
count is post-demotion: sites pre-emit drops back to Escaped
for not meeting v1 sunk-emit criteria are NOT counted.
trace_sunk_alloc_count matches one-for-one today (every
surviving Sinkable site goes through sunk emit).
Sourcepub fn trace_accum_bufferable_seen_count(&self) -> u64
pub fn trace_accum_bufferable_seen_count(&self) -> u64
P14-S14-B v1 — see JitCounters::accum_bufferable_seen.
Sourcepub fn trace_exit_hit_summary(&self, cl: Gc<LuaClosure>) -> Vec<(u32, Vec<u32>)>
pub fn trace_exit_hit_summary(&self, cl: Gc<LuaClosure>) -> Vec<(u32, Vec<u32>)>
P15-prep — total dispatch hits across all known traces, broken into hot-exit telemetry (max single-exit count, total dispatches, exit count). Used by probes to identify hot side-exits as side-trace candidates.
Walks cl.proto AND all nested protos in cl.proto.protos
recursively, so inner functions’ traces are reported.
Sourcepub fn hot_exit_iter(&self, cl: Gc<LuaClosure>) -> Vec<HotExitInfo>
pub fn hot_exit_iter(&self, cl: Gc<LuaClosure>) -> Vec<HotExitInfo>
P15-A v0 — surface every side-exit slot whose hit count is
>= HOTEXIT_THRESHOLD across every trace reachable from
cl.proto (recursively walking proto.protos). Returned
entries are side-trace candidates: each carries the parent
trace’s (head_proto, head_pc), the exit’s index in the
parent’s exit_hit_counts, and the side trace’s natural
entry shape (cont_pc + exit_tags).
Layout of exit_hit_counts (mirrored by the iter):
[0..per_exit_inline.len())→InlineSideExit(cont_pc + window-sized exit_tags).[per_exit_inline.len()..inline.len() + per_exit_tags.len())→per_exit_tags[i](per-cont_pc caller-window tags).- Last slot → global clean-tail (cont_pc =
head_pc, exit_tags =ct.exit_tags).
Sourcepub fn trace_sunk_alloc_count(&self) -> u64
pub fn trace_sunk_alloc_count(&self) -> u64
P12-S5-B — sum of NewTable sites that actually took the
sunk-emit path across every successfully compiled trace on
this Vm. Each counted site skips its heap Gc<Table>
allocation per dispatch; the array part lives as Cranelift
Variables for the duration of the trace.
Sourcepub fn trace_materialize_emit_count(&self) -> u64
pub fn trace_materialize_emit_count(&self) -> u64
P12-S5-C — sum of materialise-helper emit sites across every
successfully compiled trace on this Vm. Each unit is a
(site × cmp side-exit) pair whose IR reconstructs a heap
Gc<Table> from the virt slots on deopt — proves S5-C
emit is wiring materialise into the right side-exits.
Sourcepub fn trace_closure_emit_count(&self) -> u64
pub fn trace_closure_emit_count(&self) -> u64
P12-S7-A diagnostic — total Op::Closure ops the trace JIT
lowered to the luna_jit_op_closure helper. Each emitted op
replaces a Heap::new_closure_inline call on the dispatch
path; the count is static (one per matching op per compiled
trace), summed at compile success.
Sourcepub fn trace_per_exit_inline_compiled_count(&self) -> u64
pub fn trace_per_exit_inline_compiled_count(&self) -> u64
v2.0 Stage 7 polish 6 fire experiment — see
[crate::vm::jit_state::JitCounters::per_exit_inline_compiled].
Number of compiled traces whose per_exit_inline.len() > 0
(depth>0 inlined cmp side-exits emitted).
Sourcepub fn trace_per_exit_inline_dispatchable_count(&self) -> u64
pub fn trace_per_exit_inline_dispatchable_count(&self) -> u64
v2.0 Stage 7 polish 6 fire experiment — see
[crate::vm::jit_state::JitCounters::per_exit_inline_dispatchable].
Number of compiled traces with per_exit_inline.len() > 0 AND
dispatchable == true — i.e. the count of compiled traces
that would actually exercise the AOT polish 6 chain-reloc +
deploy-resolver path.
Sourcepub fn trace_max_depth_seen(&self) -> u8
pub fn trace_max_depth_seen(&self) -> u8
P12-S4-step1 diagnostic — max inline_depth ever seen on any
RecordedOp pushed by the recorder. Tells tests + tuning
whether a self-recursive function actually walked the depth
tracker past 0. Saturates at MAX_INLINE_DEPTH. Persists
across traces and Vm activations; reset only on Vm::new.
Sourcepub fn set_bytecode_loading(&mut self, enabled: bool)
pub fn set_bytecode_loading(&mut self, enabled: bool)
Toggle precompiled-chunk loading. Default true. Sandbox embedders
should set to false so load/loadstring reject bytecode input
(which bypasses parser limits and could exploit verifier gaps).
Sourcepub fn bytecode_loading(&self) -> bool
pub fn bytecode_loading(&self) -> bool
Current bytecode-loading gate state.
Sourcepub fn set_puc_bytecode_loading(&mut self, enabled: bool)
pub fn set_puc_bytecode_loading(&mut self, enabled: bool)
Toggle PUC .luac bytecode loading. Default false — PUC
bytecode is a strictly larger trust surface than luna’s own dump
format (third-party toolchain bugs, malformed chunks, unknown
opcode shapes). Enable only for trusted PUC chunks. Per-dialect
translators (Phase LB Wave 2) live in crate::vm::dump::puc.
Sourcepub fn puc_bytecode_loading(&self) -> bool
pub fn puc_bytecode_loading(&self) -> bool
Current PUC bytecode-loading gate state.
Sourcepub fn set_loader_input_budget(&mut self, bytes: usize)
pub fn set_loader_input_budget(&mut self, bytes: usize)
Set the loader input byte budget (see
Vm::DEFAULT_LOADER_INPUT_BUDGET). Pass usize::MAX to
effectively disable. Smaller caps are honored verbatim — a 0
cap rejects every non-empty source.
Sourcepub fn loader_input_budget(&self) -> usize
pub fn loader_input_budget(&self) -> usize
Current loader input byte budget.
Sourcepub fn take_error_traceback(&mut self) -> Option<String>
pub fn take_error_traceback(&mut self) -> Option<String>
Take the error traceback captured at the latest error point and
reset it. Embedders should call this immediately after a failed
call_value/eval/call/etc. — the next public call_value
entry clears it. Returns None if no error was in flight.
Sourcepub fn set_memory_cap(&mut self, cap: Option<usize>)
pub fn set_memory_cap(&mut self, cap: Option<usize>)
Arm the soft memory cap (P09 embedding). The run loop checks the
heap’s tracked byte usage between dispatch turns; on overshoot it
first runs a full collect, and if bytes still exceeds the cap it
raises a catchable "memory cap exceeded" Lua error and disarms
itself (fire-once: re-arm before the next call_value if reusing
the Vm across requests). None removes the cap. The accounting is
approximate — internal Vec/Box capacity overhead is not tracked,
so embedders should size the cap with ~2× margin over the desired
hard limit and additionally bound the Vm’s lifetime (drop after
each request).
Sourcepub fn memory_used(&self) -> usize
pub fn memory_used(&self) -> usize
Approximate bytes the heap is currently holding. Object shells plus
every table’s internal array/hash boxes (tracked via
Heap::apply_bytes_delta in set/rehash/ensure_*). Proto
bytecode and closure upvalue slices still go uncounted — this is a
lower bound, not a precise malloc_stats-style total.
Sourcepub fn running_native_upvalue(&self, i: usize) -> Value
pub fn running_native_upvalue(&self, i: usize) -> Value
Read upvalue slot i of the native function currently on top of the
dispatch chain (the one whose body is executing). Returns Value::Nil
when no native is running. Public so the C ABI trampoline can fetch
the host C function pointer it stashed there at registration time.
Sourcepub fn nat_upval(&self, func_slot: u32, i: usize) -> Value
pub fn nat_upval(&self, func_slot: u32, i: usize) -> Value
A native function’s own captured upvalue (self lives at func_slot).
Public so native_typed trampolines and embedders authoring
stateful natives via native_with(...) can read their upvals.
Sourcepub fn nat_arg(&self, func_slot: u32, nargs: u32, i: u32) -> Value
pub fn nat_arg(&self, func_slot: u32, nargs: u32, i: u32) -> Value
Read the i-th positional argument inside a NativeFn body
(analogous to lua_tovalue(L, i + 1)). i >= nargs yields Nil,
matching PUC’s “missing arg is nil” contract. Public so embedders
can author their own natives.
Sourcepub fn nat_return(&mut self, func_slot: u32, vals: &[Value]) -> u32
pub fn nat_return(&mut self, func_slot: u32, vals: &[Value]) -> u32
Push the return values of a NativeFn and return their count
(analogous to pushing N values then return N from a C function).
Public so embedders can author their own natives.
Source§impl Vm
impl Vm
Sourcepub fn install_aot_trace(&mut self, proto: Gc<Proto>, trace: CompiledTrace)
pub fn install_aot_trace(&mut self, proto: Gc<Proto>, trace: CompiledTrace)
v1.3 Phase AOT Stage 7 sub-piece 4 — install a precompiled
CompiledTrace onto proto.traces so the interp dispatcher
fires it at the trace’s head_pc. This is the runtime install
API the deploy-side luna-runtime-helpers resolver calls once
per AOT-emitted trace meta entry, after looking up proto by
stable hash (see crate::runtime::function::Proto::stable_hash).
§What this does
Pushes trace onto proto.traces via the existing RefCell.
The trace’s entry fn ptr must already point at runnable
machine code (the AOT linker resolved the symbol at link time;
the deploy resolver passes the address verbatim).
§What this does NOT do
- No deduplication. Calling twice with the same
head_pcpushes two entries; the dispatcher’sfindwill pick the first match. The deploy resolver is responsible for not double-installing. - No invalidation of the runtime JIT cache. If the runtime
JIT later records + compiles a trace for the same
(proto, head_pc), both coexist onproto.tracesand the dispatcher’sfindpicks whichever appears first. AOT traces install before any runtime recording is possible (resolver runs beforevm.loadreturns its first closure), so AOT traces win the race for the same site. - No coverage gating. AOT traces are trusted by
construction — they were validated at compile time. Setting
dispatchable: falseon the input would silently disable dispatch; the caller controls that flag.
§Safety / soundness
trace.entry is an unsafe extern "C" fn (mmap’d or linked
machine code). Soundness contract:
- The fn pointer must remain valid for the
Vm’s lifetime. In the AOT-binary deploy shape this is trivially satisfied — the fn lives in the binary’s.text. trace.entry_tags/exit_tags/window_sizemust match what the trace’s IR actually compiled against; the dispatcher uses them to marshalreg_statein and out without further validation. A mismatch corrupts vm.stack.
The AOT pipeline (luna-aot) is responsible for ensuring these
invariants hold; this fn is a plain push — no validation that
would slow the dispatcher’s hot path either.
Sourcepub fn collect_proto_hashes(
&self,
root: Gc<Proto>,
) -> Vec<(Gc<Proto>, [u8; 16])>
pub fn collect_proto_hashes( &self, root: Gc<Proto>, ) -> Vec<(Gc<Proto>, [u8; 16])>
v1.3 Phase AOT Stage 7 sub-piece 4 — walk the proto tree
reachable from root and return (proto, stable_hash) pairs
for every Proto found. Used by the deploy-side resolver to
match AOT-emitted proto_hash keys against the freshly
undump’d chunk’s protos.
The walk is BFS over Proto.protos. Same-Proto deduplication
is done via Gc::as_ptr identity — a Proto re-referenced from
multiple nested closures (rare; the cache field would catch
the closure-side dedup, not the Proto side) is reported once.
§Why on &Vm and not a free fn
Keeps the AOT install API discoverable on the Vm surface —
vm.collect_proto_hashes(root) reads naturally next to
vm.install_aot_trace(proto, trace). Doesn’t actually touch
any Vm field, so &self (read-only) is enough.
Source§impl Vm
impl Vm
Sourcepub fn create_async_native(&mut self, f: AsyncNativeFn) -> Value
pub fn create_async_native(&mut self, f: AsyncNativeFn) -> Value
v1.1 B10 Stage 2 — allocate a Value::Native whose closure is
tagged as async (NativeClosure.is_async = true). The
underlying NativeFn pointer slot stores f transmuted from
AsyncNativeFn — same pointer width, no provenance loss —
and the marker bit is what tells the dispatcher to route it
through the cooperative-yield path.
The returned Value can be installed under a Lua global via
Vm::set_global, passed as a callback, stored in a table —
whatever a sync vm.native(f) value supports. Calling it from
a sync Vm::eval context raises LuaError (“async native
called in sync context”); only Vm::eval_async (or another
driver that sets async_mode = true) can drive it.
Sourcepub fn set_async_native(
&mut self,
name: &str,
f: AsyncNativeFn,
) -> Result<(), LuaError>
pub fn set_async_native( &mut self, name: &str, f: AsyncNativeFn, ) -> Result<(), LuaError>
v1.1 B10 Stage 2 — convenience: install an async native under
name as a Lua global. Equivalent to
vm.set_global(name, vm.create_async_native(f)).
Sourcepub fn eval_async<'vm>(&'vm mut self, src: &str) -> EvalFuture<'vm> ⓘ
pub fn eval_async<'vm>(&'vm mut self, src: &str) -> EvalFuture<'vm> ⓘ
v1.1 B10 Stage 1 — convenience entry: compile + run src as an
anonymous chunk via the cooperative-yield dispatcher. The
returned EvalFuture borrows &mut self for its full lifetime,
which (by Vm: !Send) keeps it pinned to a single OS thread.
Holding two EvalFutures on the same Vm is blocked by the
borrow checker (&mut Vm exclusivity). Holding a sync
eval/call_value call while an EvalFuture is in flight
is likewise blocked.
The chunk source name in tracebacks is "=eval". Use
Vm::eval_async_chunk to supply a custom name.
Sourcepub fn eval_async_chunk<'vm>(
&'vm mut self,
src: &str,
name: &str,
) -> EvalFuture<'vm> ⓘ
pub fn eval_async_chunk<'vm>( &'vm mut self, src: &str, name: &str, ) -> EvalFuture<'vm> ⓘ
v1.1 B10 Stage 1 — like Vm::eval_async but with a
user-supplied chunk name (appears in tracebacks).
Sourcepub fn set_async_slice(&mut self, n: i64)
pub fn set_async_slice(&mut self, n: i64)
v1.1 B10 Stage 1 — set the per-poll opcode quota loaded into
instr_budget at the start of each EvalFuture poll slice.
Default 10_000 opcodes. Smaller = finer-grained cooperative
yield (lower per-task latency, more task-switch overhead);
larger = closer to sync throughput per slice.
Sourcepub fn async_slice(&self) -> i64
pub fn async_slice(&self) -> i64
v1.1 B10 Stage 1 — current per-poll async slice size (default 10_000).
Source§impl Vm
impl Vm
Sourcepub fn eval_chunk(
&mut self,
src: &str,
name: &str,
) -> Result<Vec<Value>, LuaError>
pub fn eval_chunk( &mut self, src: &str, name: &str, ) -> Result<Vec<Value>, LuaError>
Same as Vm::eval but with a user-supplied chunk name
(appears in tracebacks for debugging).
Sourcepub fn intern_str(&mut self, s: &str) -> Gc<LuaStr>
pub fn intern_str(&mut self, s: &str) -> Gc<LuaStr>
Intern a UTF-8 string into the heap’s string table.
Idempotent — interning the same bytes twice returns the same
Gc<LuaStr> handle.
Useful for embedders constructing table keys or comparing Lua
strings without going through Value::Str wrapping each time.
Sourcepub fn error_kind(&self) -> LuaErrorKind
pub fn error_kind(&self) -> LuaErrorKind
Classification of the most recently raised error on this Vm.
Returns crate::vm::error::LuaErrorKind::Runtime before any error fires.
Sourcepub fn error_source(&self) -> Option<(&str, u32)>
pub fn error_source(&self) -> Option<(&str, u32)>
(source_name, line) of the most recently raised error, or
None if the dispatcher could not locate one. Source names
match Lua’s chunk-name convention ("=eval", "=stdin",
user-supplied via Vm::load).
Sourcepub fn set_error_kind(&mut self, kind: LuaErrorKind)
pub fn set_error_kind(&mut self, kind: LuaErrorKind)
Set the classification for the next error to be raised — used
by the dispatcher at well-known sites. Embedders writing
native callbacks may call this before returning Err(LuaError)
to flag a specific kind (e.g. LuaErrorKind::Type for a bad
arg).
Sourcepub fn set_error_source(&mut self, name: String, line: u32)
pub fn set_error_source(&mut self, name: String, line: u32)
Set the (source_name, line) for the next error to be raised.
The dispatcher uses this at the syntax-error / parser
boundary.
Sourcepub fn clear_error_metadata(&mut self)
pub fn clear_error_metadata(&mut self)
Clear error classification — called on a clean call_value
entry so old error metadata doesn’t leak into the next call.
Sourcepub fn create_userdata<T: LuaUserdata>(&mut self, value: T) -> Value
pub fn create_userdata<T: LuaUserdata>(&mut self, value: T) -> Value
Allocate a host userdata wrapping value. Returns the
Value::Userdata you can set_global / pin / pass to scripts.
The metatable produced by crate::vm::LuaUserdata::add_methods
is auto-installed on the userdata (cached per Vm keyed by
TypeId::of::<T>()). For a type that only needs identity +
raw host-side access (no Lua-callable methods), provide an
empty impl:
struct Counter(i64);
impl LuaUserdata for Counter {}use luna_core::vm::{LuaUserdata, Vm};
use luna_core::version::LuaVersion;
use luna_core::runtime::Value;
#[derive(Debug)]
struct Counter(i64);
impl LuaUserdata for Counter {}
let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().build();
let ud = vm.create_userdata(Counter(42));
vm.set_global("counter", ud).unwrap();
match ud {
Value::Userdata(g) => {
// SAFETY: single-threaded heap; pointer is live.
let r = unsafe { &*g.as_ptr() };
assert_eq!(r.downcast::<Counter>().unwrap().0, 42);
}
_ => unreachable!(),
}Sourcepub fn set_userdata<T: LuaUserdata>(
&mut self,
name: &str,
value: T,
) -> Result<(), LuaError>
pub fn set_userdata<T: LuaUserdata>( &mut self, name: &str, value: T, ) -> Result<(), LuaError>
Convenience: Self::create_userdata + Self::set_global.
Sourcepub fn userdata_borrow<T: Any + 'static>(&mut self, name: &str) -> Option<&T>
pub fn userdata_borrow<T: Any + 'static>(&mut self, name: &str) -> Option<&T>
Borrow the host payload of a global userdata as &T. Returns
None if the global doesn’t exist, isn’t a userdata, isn’t a
host userdata, or holds a different type than T.
Takes &mut self because the lookup interns the key string;
returning a borrow tied to &mut Vm mirrors vm.set_global
ergonomics.
Sourcepub fn create_coroutine(&mut self, body: Value) -> Value
pub fn create_coroutine(&mut self, body: Value) -> Value
Create a new coroutine carrying body (a Lua function or
any callable Value). Returns the Value::Coro handle ready
to be passed to Self::resume_coroutine.
Equivalent to coroutine.create(body) from a Rust embedder.
Sourcepub fn resume_coroutine(
&mut self,
co: Value,
args: Vec<Value>,
) -> Result<Vec<Value>, LuaError>
pub fn resume_coroutine( &mut self, co: Value, args: Vec<Value>, ) -> Result<Vec<Value>, LuaError>
Resume a coroutine with the given arguments. Returns the
yielded values on yield, the return values on the body’s
terminal return, or an error if the body raised.
Equivalent to coroutine.resume(co, args...). Returns
Err(LuaError) if co is not a Value::Coro.
Sourcepub fn set_rust_debug_hook(
&mut self,
hook: Option<RustDebugHook>,
mask: u32,
count: i64,
)
pub fn set_rust_debug_hook( &mut self, hook: Option<RustDebugHook>, mask: u32, count: i64, )
Install a Rust-side debug hook (see crate::vm::exec::RustDebugHook). The
mask is a bitwise OR of HOOK_MASK_CALL / HOOK_MASK_RETURN
/ HOOK_MASK_LINE / HOOK_MASK_COUNT exported from
crate::vm::exec. The count arg sets the instruction
granularity for Count events (ignored unless HOOK_MASK_COUNT
is set).
Passing hook = None clears the Rust hook; the Lua-side hook
installed via debug.sethook is unaffected.
Sourcepub fn clear_rust_debug_hook(&mut self)
pub fn clear_rust_debug_hook(&mut self)
Clear the Rust-side debug hook (sugar over
set_rust_debug_hook(None, 0, 0)).
Sourcepub fn current_op(&self) -> Option<Op>
pub fn current_op(&self) -> Option<Op>
Read the most recently dispatched Lua opcode, if the Vm is currently
executing inside a Lua frame. Intended for use from a Count hook
(installed via Self::set_rust_debug_hook with HOOK_MASK_COUNT)
to tally per-opcode distribution against a workload — the v1.2
methodology gate (perf-decomposition-vs-polish.md §2 Phase A,
in ~/.claude-shared/global/methodology/) requires runtime-counter
validation of per-iter op mix before any stage decomposition is
acted on.
Returns None outside a Lua frame (top-level setup, while a
native callback or Cont guard is on top of the call stack, etc.).
Reads self.frames.last() → CallFrame::Lua(f) → f.closure.proto.code[f.pc - 1]
— the just-dispatched opcode (PC has already advanced past it).
Sourcepub fn userdata_borrow_mut<T: Any + 'static>(
&mut self,
name: &str,
) -> Option<&mut T>
pub fn userdata_borrow_mut<T: Any + 'static>( &mut self, name: &str, ) -> Option<&mut T>
Mutable variant of Self::userdata_borrow.
Source§impl Vm
impl Vm
Sourcepub fn pin_host(&mut self, v: Value) -> HostRootTicket
pub fn pin_host(&mut self, v: Value) -> HostRootTicket
Pin v as a host root. Reuses a recycled slot if the free
list is non-empty, else extends the pool. Bumps the slot’s
generation; previously-issued tickets for that slot become
stale (read_host returns None, write_host / unpin
return Err(HostRootStale)).
Returns a HostRootTicket (Copy, 8 bytes). The value
becomes an extra GC root until the ticket is released via
Self::unpin or the whole pool via Self::unpin_all.
Sourcepub fn read_host(&self, t: HostRootTicket) -> Option<Value>
pub fn read_host(&self, t: HostRootTicket) -> Option<Value>
Read a previously pinned host root. Returns None if the
ticket is stale (slot was unpinned and possibly re-pinned to a
different value) or if the ticket index is out of bounds.
Sourcepub fn write_host(
&mut self,
t: HostRootTicket,
v: Value,
) -> Result<(), HostRootStale>
pub fn write_host( &mut self, t: HostRootTicket, v: Value, ) -> Result<(), HostRootStale>
Mutate a previously pinned host root in place. Returns
Err(HostRootStale) on stale ticket; otherwise updates the
slot’s value WITHOUT bumping generation (mutation does not
invalidate other live aliases of the same ticket).
Sourcepub fn unpin(&mut self, t: HostRootTicket) -> Result<(), HostRootStale>
pub fn unpin(&mut self, t: HostRootTicket) -> Result<(), HostRootStale>
Drop a single pinned root. Clears the slot’s value to Nil,
bumps the slot’s generation, and pushes the index onto the
free list for reuse. Returns Err(HostRootStale) if the
ticket is stale (already-unpinned / re-pinned slot); the
pool is unchanged in that case.
Generation overflow at u32::MAX retires the slot
permanently — the index is NOT pushed to the free list, and
future pin_host calls will allocate a fresh slot rather
than reuse this one.
Sourcepub fn host_root_count(&self) -> usize
pub fn host_root_count(&self) -> usize
Number of currently-pinned (live) host roots. Diagnostic only.
Computed as host_roots.len() - host_roots_free.len() — this
over-counts retired-by-overflow slots as still-allocated. For
a short-lived process the difference is sub-MB; long-running
servers should treat this as an upper bound on live pins.
Sourcepub fn unpin_all(&mut self)
pub fn unpin_all(&mut self)
Drop every pinned host root. Embedders driving the Lua
facade in a request-per-script loop call this to release a
batch of pins in one shot. Bumps every slot’s generation;
every previously-issued ticket becomes stale uniformly.
Keeps the underlying Vec capacity to amortize future
pin_host allocations. Slots that already reached
generation == u32::MAX stay retired (not added back to the
free list).
Source§impl Vm
impl Vm
Sourcepub fn sandbox(version: LuaVersion) -> SandboxBuilder
pub fn sandbox(version: LuaVersion) -> SandboxBuilder
Start a SandboxBuilder for a fresh, sandboxed Vm. See
SandboxBuilder for defaults.
Source§impl Vm
impl Vm
Sourcepub fn new_table(&mut self) -> TableBuilder<'_>
pub fn new_table(&mut self) -> TableBuilder<'_>
Allocate a fresh table and return a builder for in-place population.
Sourcepub fn table_of<K, V, const N: usize>(
&mut self,
entries: [(K, V); N],
) -> Gc<Table>
pub fn table_of<K, V, const N: usize>( &mut self, entries: [(K, V); N], ) -> Gc<Table>
Allocate a fresh table populated from a fixed-size slice of
(key, value) pairs. Equivalent to chained new_table().with(...)
calls, but more concise for static tables (stdlib registration,
embedder-side constants).
Panics on table overflow (unreachable for N small).
Source§impl Vm
impl Vm
Sourcepub fn native_typed<F, Marker>(&mut self, f: F) -> Valuewhere
F: NativeTypedSig<Marker>,
pub fn native_typed<F, Marker>(&mut self, f: F) -> Valuewhere
F: NativeTypedSig<Marker>,
Register a typed Rust function as a Lua-callable Value. The
callable must be a fn-pointer or a non-capturing closure
(Copy + 'static + ZST or fn-pointer-sized). For capturing
closures use vm.native_with(...) with explicit upvals.
let add = vm.native_typed(|a: i64, b: i64| -> i64 { a + b });
vm.set_global("add", add).unwrap();
let r = vm.eval("return add(40, 2)").unwrap();
assert!(matches!(r[0], Value::Int(42)));Source§impl Vm
impl Vm
Sourcepub fn register_userdata<T: LuaUserdata>(
&mut self,
) -> Result<Gc<Table>, LuaError>
pub fn register_userdata<T: LuaUserdata>( &mut self, ) -> Result<Gc<Table>, LuaError>
Build (or fetch from cache) the metatable for T. Called
lazily by Vm::create_userdata / Vm::set_userdata;
embedders rarely need to invoke it directly. Returns the same
Gc<Table> on every call within a given Vm (keyed by
TypeId::of::<T>()).
The metatable is pinned as a host root so it survives GC even
when no userdata of type T is currently reachable.