Skip to main content

VirtualMachine

Struct VirtualMachine 

Source
pub struct VirtualMachine {
    pub resource_usage: Option<ResourceUsage>,
    pub metrics: Option<VmMetrics>,
    /* private fields */
}
Expand description

The Shape virtual machine

Fields§

§resource_usage: Option<ResourceUsage>

Optional resource usage tracker for sandboxed execution. When set, the dispatch loop calls tick_instruction() each cycle.

§metrics: Option<VmMetrics>

Optional VM metrics collector. None when VMConfig.metrics_enabled is false (the default), giving zero per-instruction overhead.

Implementations§

Source§

impl VirtualMachine

Source

pub fn execute_function_by_name( &mut self, name: &str, args: Vec<KindedSlot>, ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

Execute a named function with arguments, returning its result.

W7-cv-method (Round 3 close). Resolves name to func_id via the program function table and routes to [execute_function_by_id] per W7 playbook §4.

Source

pub fn execute_function_by_id( &mut self, func_id: u16, args: Vec<KindedSlot>, ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

Execute a function by its ID with positional arguments.

W7-cv-method (Round 3 close). Captures saved_depth before frame setup, routes through [call_function_with_nb_args], drives the callee to completion via execute_until_call_depth, and pops the result via the kinded API (W7 playbook §4 + §2.7.10 / Q11 dispatch shape).

Ownership. Each KindedSlot in args holds a strong-count share. cluster-1.5 v2-raw-empirical-isolation-and-fix (2026-05-17): post-fix call_function_with_nb_args is share- neutral (clones each arg before frame-write so the frame’s teardown truncate_stack retire balances the in-helper clone; caller’s carrier shares are preserved by the borrow-only &[KindedSlot] signature). We let args drop normally at scope exit — the per-slot KindedSlot::Drop retires each caller-owned share. The legacy mem::forget pre-fix was a load-bearing leak that compensated for the missing clone in the helper; with the helper now share-neutral, the forget would LEAK one share per heap-bearing arg.

Source

pub fn execute_closure( &mut self, closure_block: &OwnedClosureBlock, args: Vec<KindedSlot>, ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

Execute a closure with its captured upvalues and arguments.

W7-cv-method (Round 3 close). The pre-§2.7.8 _upvalue_bits: Vec<u64> parameter — the deleted-ABI raw-bits shape — is replaced by closure_block: &OwnedClosureBlock. Captures flow from the block’s parallel-kind track via read_capture_kinded inside [call_closure_with_nb_args_keepalive], not from a side-channel payload (W7 playbook §4 + ADR-006 §2.7.8 / Q10).

The keep-alive companion fields carry the closure-self share so op_return / op_return_value release it via drop_with_kind on frame teardown — same B9 lockstep pattern as call_value_immediate_nb’s closure arm.

Source

pub fn execute_function_fast( &mut self, func_id: u16, ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

Fast function execution for hot loops (backtesting).

W7-cv-method (Round 3 close). Pre-computed func_id, no name lookup, no args (callers that need args route through execute_function_by_id). Same saved_depth pattern as the other public entry-points.

Source

pub fn execute_function_with_named_args( &mut self, func_id: u16, named_args: &[(String, KindedSlot)], ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

Execute a function with named arguments.

W7-cv-method (Round 3 close). Maps &[(String, KindedSlot)] to a positional Vec<KindedSlot> via descriptor.param_names lookup, then routes through [execute_function_by_id] per W7 playbook §4. Missing positional slots are sentinel-filled with (NONE_BITS, NativeKind::Bool) per W6.5 §2 Null/Unit row — Drop/Clone-no-op so the pre-population is leak-free.

Ownership. The caller’s named-args carry one share per slot; clone_with_kind is NOT used (we re-home the slot’s bits by reading the slot directly). After mapping, the positional vec owns the same shares; they transfer into the new frame via the execute_function_by_id mem::forget discipline.

Source

pub fn resume( &mut self, _value: KindedSlot, _ctx: Option<&mut ExecutionContext>, ) -> Result<ExecutionResult, VMError>

Resume execution after a suspension.

§2.7.4 Phase-2c — stays todo!(). The suspension shape requires snapshot-tier work: the resume body’s pre-§2.7.7 form pushed value onto the stack and re-entered the suspendable dispatch loop, but the snapshot/restore family (apply_pending_resume / apply_pending_frame_resume in executor/resume.rs) is itself §2.7.4 deferred — its bodies return VMError::NotImplemented(PHASE_2C_SNAPSHOT_SURFACE). Until the snapshot rebuild lands a kind-threaded slot_to_serializable / serializable_to_slot pair plus the §2.7.8 cell-storage parallel-kind tracks for module_bindings and frame-resume payloads, this entry-point cannot be wired — surface-and-stop trigger per W7 playbook §8 (snapshot-tier resume).

Source

pub fn execute_with_async( &mut self, ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

Execute with automatic async task resolution.

Filled by W7-cv-async (Round 3 close). Per W7 playbook §4 W7-cv-async row, sync-resolution only — suspension state crossing a call_value_immediate_* boundary is OUT OF SCOPE per ADR-006 §2.7.11 out-of-scope clause (Phase-2c snapshot tier; same out-of-scope clause as §2.7.10).

Drives the program forward via execute_fast(ctx) (the standard run-to-halt loop that pops the top-of-stack result on completion). Inline task resolution at op_await / op_join_await sites in executor/async_ops/mod.rs is the integration point with [resolve_spawned_task] (below) — once the §2.7.4 task-scheduler kinded-ABI re-light closes those todo!() arms, the await-site handler invokes resolve_spawned_task(task_id) directly inside the dispatch loop, and this driver re-enters execute_fast to continue the program after the suspended op_await opcode returns.

The pre-bulldozer execute_with_async shape — drive a loop over task_scheduler.iter_pending() calling resolve_spawned_task per ready task — depends on a public iterator over TaskScheduler.callables that does not exist in the current scheduler API surface (W7-cv-async owns only call_convention.rs per W7 playbook §10 forbidden zones — task_scheduler.rs is out-of-territory). When a future cluster lands the iteration API, the loop body in this function is the natural extension point: while there is a Pending-with-callable task, call resolve_spawned_task(id) and discard the per-task result; the program-level result still comes from execute_fast at the end.

Source

pub fn call_value_immediate_nb( &mut self, callee: &KindedSlot, args: &[KindedSlot], ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

call_value_immediate (kinded carrier form): dispatches on the callee’s KindedSlot.kind. ADR-006 §2.7.11 / Q12 caller-side shape — both callee and args travel as KindedSlot.

Filled by W7-cv-static (Round 2 close). Per W7 playbook §4: matches on callee.kind and routes — Ptr(HeapKind::Closure) recovers the OwnedClosureBlock via slot.as_heap_value() + HeapValue::ClosureRaw (single discriminator per ADR-005 §1) and routes to call_closure_with_nb_args_keepalive; UInt64 callee bits are the function-id and route to call_function_with_nb_args. Both arms drive the callee to completion via execute_until_call_depth(saved_depth, ctx) (the call-stack-bounded run loop in dispatch.rs) and pop the result from the value stack via pop_kinded. Other kinds fall through to a RuntimeError (VMError::TypeError is &'static str-bound and incompatible with the format!-style dynamic-kind error message; the convention used by the existing op_call_value surfaces is RuntimeError(format!(...))). The HeapValue::HostClosure variant referenced in pre-Wave-7 docs has been deleted; only ClosureRaw survives in the closure-dispatch path.

Source

pub fn jit_trampoline_call_closure( &mut self, func_id: u16, upvalue_bits: &[(u64, NativeKind)], args: &[(u64, NativeKind)], ctx: Option<&mut ExecutionContext>, ) -> Result<u64, VMError>

Trampoline entry: call a closure by func_id with pre-extracted raw upvalue bits and raw args, returning the result as raw u64 bits.

W7-cv-method (Round 3 close). This is the only _raw survivor in call_convention.rs per ADR-006 §2.7.11 migration-scope refinement: it is the §2.7.5 cross-crate stable-FFI consumer where the parallel-pair shape (raw u64 data + NativeKind) is canonical. Consumers translate &[KindedSlot] → raw u64 at the FFI boundary; this function is the inverse hop on the runtime side.

Body wraps args as a transient &[KindedSlot] slice (no Arc bump — the JIT pre-incremented each share before crossing the boundary), constructs a fresh OwnedClosureBlock from upvalue_bits per the existing closure-construction convention (allocate → write each capture’s bits at its layout offset → OwnedClosureBlock::from_raw), routes through [call_closure_with_nb_args_keepalive], drives the callee, pops the result via pop_kinded, and returns the bits as raw u64 (the kind is discarded — the JIT caller knows the static return kind from the callee signature).

Ownership. Each (bits, kind) in upvalue_bits carries a pre-incremented share. We transfer those shares into the new closure block via write_capture_typed (which stores the bit pattern without bumping the refcount). The OwnedClosureBlock then owns the captures’ shares — its Drop walks the layout’s capture masks and releases them. Same for args: the JIT pre-incremented; we hand each transient KindedSlot over by move (no clone), and call_closure_with_nb_args_keepalive transfers the shares into the new frame via stack_write_kinded. We mem::forget the transient args vec so its Drop does not double-free.

Source

pub fn jit_trampoline_call_method( &mut self, method_name: &str, receiver: (u64, NativeKind), args: &[(u64, NativeKind)], ctx: Option<&mut ExecutionContext>, ) -> Result<u64, VMError>

Trampoline entry: dispatch a method call on a kinded receiver + kinded args, returning the result as raw u64 bits.

W12-jit-call-method-shell-rebuild (Phase 3 cluster-0 Round 10 / 8B.2 close). Sibling to [jit_trampoline_call_closure]; same §2.7.5 cross-crate stable-FFI consumer shape. The JIT-side jit_call_method shell pops (bits, kind) pairs from the JIT’s ctx.stack + ctx.stack_kinds parallel-kind track per §2.7.7 / Q9, passes them across the FFI boundary as &[(u64, NativeKind)] pair-slices, and this function converts them to the kinded carrier form before delegating to dispatch_method_kinded — the §2.7.10 / Q11 kinded method-dispatch entry shared with op_call_method.

Pair-slice → KindedSlot conversion is single-direction per the module-level docstring’s “sole _raw survivor” rule. The pair-slice is the canonical §2.7.5 boundary shape; internally only &[KindedSlot] flows. Forbidden alternatives (per ADR-006 §2.7.6 / Q8 + §2.7.10 / Q11):

  • parallel &[NativeKind] second-slice parameter (carrier-API- bound rejection — kind goes on the carrier struct, not a side-channel);
  • decoding receiver kind from receiver.0 raw bits via tag-bit probe (the deleted §2.7.7 #4 / #7 dispatch);
  • Bool-default kinded carrier for unknown receiver kind (§2.7.7 #9 — the surface-and-stop discipline forbids this).

Ownership. Each (bits, kind) pair carries a pre-incremented share installed by the JIT producer (per §2.7.7 retain-on-read semantics on the JIT-side stack). The transient KindedSlot carriers adopt those shares for the call duration. PHF handlers borrow-only (&[KindedSlot] per §2.7.10 / Q11), so the carriers retain ownership of the JIT-pre-incremented shares throughout dispatch. When the carriers Drop at end of scope, each kind’s drop_with_kind releases its share — balancing the JIT-side retain-before-crossing pattern. The returned KindedSlot’s share is transferred back to the JIT caller as raw u64 bits via mem::forget; the kind is discarded — the JIT caller knows the static return kind from the callee method signature at the §2.7.5 stamp-at-compile-time producing site.

Lifetime accounting contrast vs. jit_trampoline_call_closure. The closure trampoline’s mem::forget(kinded_args) (line 1035) is because the args were transferred into the callee’s frame locals via stack_write_kinded — the shares moved into the frame, so the transient carriers must NOT release them. Method dispatch’s PHF handlers do not transfer the shares anywhere — they only borrow — so the transient carriers DO release at end of scope. Both patterns preserve §2.7.7 retain-on-read + drop-on-write discipline; the difference is which slot owns the share at the call’s exit boundary.

Source§

impl VirtualMachine

Source

pub fn execute( &mut self, ctx: Option<&mut ExecutionContext>, ) -> Result<KindedSlot, VMError>

Execute the loaded program.

§Arguments
  • ctx - Optional ExecutionContext for trading operations (rows, indicators, etc.)

Returns a KindedSlot — the canonical post-ValueWord runtime-value carrier (ADR-006 §2.7 / Q7). The slot’s NativeKind is sourced from BytecodeProgram::top_level_frame. return_kind when present (the compiler-proven kind), with the raw u64 bits taken from the top of the value stack. Hosts dispatch on result.kind() and use the per-variant KindedSlot::as_* accessors per §2.7.6.

Source

pub fn execute_raw( &mut self, ctx: Option<&mut ExecutionContext>, ) -> Result<u64, VMError>

Execute the loaded program and return the raw u64 bits at the top of stack. Top-level emission pushes raw native values (e.g. i64, f64::to_bits(), 0u64/1u64 for bool, raw heap pointer for ptr); the kind is recovered from program_top_level_return_kind(). Use this when the host wants the raw bits without a KindedSlot wrapper.

Source

pub fn execute_with_suspend( &mut self, ctx: Option<&mut ExecutionContext>, ) -> Result<ExecutionResult, VMError>

Execute the loaded program, returning either a completed value or suspension info.

Unlike execute(), this method distinguishes between completion and suspension, allowing the host to resume execution after resolving a future.

Source§

impl VirtualMachine

Source

pub fn op_call_method( &mut self, instruction: &Instruction, ctx: Option<&mut ExecutionContext>, ) -> Result<(), VMError>

CallMethod dispatch shell (W16-op-call-method close).

ADR-006 §2.7.10 / Q11 dispatch shell — pops the receiver + arg-count call args from the §2.7.7 kinded stack, classifies the receiver kind to pick the matching PHF method registry, dispatches through MethodFnV2, and pushes the kinded result.

Body shape per the W7-op-call-value precedent (close commit 27812cf, executor/control_flow/mod.rs:dispatch_call_value_immediate):

  1. Pop arg_count + 1 slots via pop_kinded() (receiver included). Each pop transfers one share (heap-bearing kinds) into the returned (bits, kind) pair (WB2.4 retain-on-read, §2.7.7); the KindedSlot::new carrier takes ownership of that share. Pop order is reverse of push order, so reverse the vec back to position-aligned order with args[0] = receiver.
  2. Decode arg_count + method name from Operand::TypedMethodCall { arg_count, string_id, .. } (bytecode/opcode_defs.rs:2023). The method name string is indexed via string_id into self.program.strings.
  3. Classify args[0].kind to pick a PHF registry per the §2.7.6 / Q8 heterogeneous-kind body pattern. Numeric / Bool / String scalars route to the matching scalar registry; Ptr(HeapKind::*) heap kinds route to the per-heap-kind registry, with HeapKind::TypedArray sub-classified on the inner TypedArrayData::{I64, F64, Bool, ...} variant via slot.as_heap_value() and HeapKind::Temporal sub-classified on the inner TemporalData::{DateTime, TimeSpan, ...} variant. The v2 typed-array fast path (UInt64-tagged raw *mut TypedArray<T> pointer) routes through as_v2_typed_array; post-W16.2-J.1 every numeric element kind falls through to the kind-generic ARRAY_METHODS PHF (per the typed_array_method_registry helper, which returns None for V2ElemType::{I*, U*, F*}). V2ElemType::Bool continues to route via BOOL_ARRAY_METHODS.
  4. PHF lookup keyed on &str method name returns the MethodFnV2 handler. A miss surfaces a RuntimeError citing the receiver kind + method name; user-defined methods on HeapValue::TypedObject fall through to a UFCS function-name lookup (function_name_index) before the final Unknown method error. Closure / Future / Reference / SharedCell / FilterExpr receivers reject — they are not method-call targets.
  5. Dispatch: handler(self, &args, ctx) returns Result<KindedSlot, VMError>. The &[KindedSlot] borrow leaves the shares with the carriers in this stack frame — handlers borrow each entry per §2.7.10 / Q11 borrow-only ABI.
  6. Push the result via push_kinded(result.raw(), result.kind()) and std::mem::forget(result) so the result share transfers cleanly to the stack (no double-drop). The args carriers drop at end of scope; KindedSlot::Drop dispatches on kind and releases each share via drop_with_kind (no bare vw_drop, no Bool-default fallback).

Forbidden surfaces (per CLAUDE.md “Renames to refuse on sight”

  • ADR-006 §2.7.10 / Q11): Vec<KindedSlot> by-move into a dispatch helper; args: &mut [KindedSlot]; tag-bits decode on receiver bits; is_heap() probe on raw bits; Bool-default fallback for unknown kind; defection-attractor framing on the method-dispatch ABI (MethodFn / MethodFnLegacy / dispatch_method_handler_raw / call_handler_with_u64_slice).

Surfaces remaining (out of W16 territory):

  • IC fast-path recording / hit: method_ic_check / method_ic_record already accept the kinded MethodFnV2 transmute (ic_fast_paths.rs:42-44) — wiring the IC recording at the dispatch shell is a downstream JIT-IC follow-up, not a correctness gate. The dispatch shell stays correct without IC; the IC adds speed only.
  • HeapKind::Closure receivers (e.g. closure-as-trait- object dispatch). Trait-object dispatch goes through op_dyn_method_call, not op_call_method; the closure arm here rejects with a clear error.
Source§

impl VirtualMachine

Source

pub fn snapshot(&self, store: &SnapshotStore) -> Result<VmSnapshot, VMError>

Create a serializable snapshot of VM state.

W17-snapshot-roundtrip (Phase 2d Wave 2.6, 2026-05-11). Uses the kind-threaded slot_to_serializable(bits, kind, store) API landed alongside the §2.7.5.1 wire-format extension. Round-trips the stack, module bindings, IP, and exception-handler stack at landing. Call-stack frames, loop contexts, locals living in register windows on the stack (versus their inlined-into-stack projection), and timeframe state are landed via the existing VmSnapshot carrier with the per-slot kind threaded through slot_to_serializable per slot. Deep heap kinds that don’t yet have a wire-format arm surface via the per-slot error path — callers observe a structured VMError::NotImplemented, not silent state loss.

Source

pub fn from_snapshot( program: BytecodeProgram, snapshot: &VmSnapshot, store: &SnapshotStore, ) -> Result<Self, VMError>

Restore a VM from a snapshot and bytecode program.

W17-snapshot-roundtrip (Phase 2d Wave 2.6, 2026-05-11). Symmetric inverse of snapshot(): rebuilds the VM from the kind-threaded serializable_to_slot API. Stack, module bindings, IP, exception handlers, and structural loop/ timeframe state restore deterministically when the snapshot’s per-slot kinds align with the program’s FrameDescriptor.slots at the resume IP. Discriminator-vs-kind mismatches surface as structured errors per §2.7.5.1.

Per-slot kind reconstruction: the snapshot’s SerializableVMValue discriminator (its variant tag) is the authoritative carrier of the slot’s kind. serializable_to_slot takes an expected_kind hint per Q9 §2.7.7 (the post-proof stack-kind invariant) but the discriminator wins on actual projection. Restore picks the kind from the discriminator and hands (bits, kind) back to the parallel-kind tracks.

Source§

impl VirtualMachine

Source

pub fn op_builtin_call( &mut self, instruction: &Instruction, ctx: Option<&mut ExecutionContext>, ) -> Result<(), VMError>

Source§

impl VirtualMachine

Source

pub fn new(config: VMConfig) -> Self

Source

pub fn with_resource_limits(self, limits: ResourceLimits) -> Self

Attach resource limits to this VM. The dispatch loop will enforce them.

Source

pub fn set_interrupt(&mut self, flag: Arc<AtomicU8>)

Set the interrupt flag (shared with Ctrl+C handler).

Source

pub fn enable_time_travel(&mut self, mode: CaptureMode, max_entries: usize)

Enable time-travel debugging with the given capture mode and history limit.

Source

pub fn disable_time_travel(&mut self)

Disable time-travel debugging and discard history.

Source

pub fn set_jit_compiled(&mut self)

Mark this VM as having been JIT-compiled selectively.

Call this after using shape_jit::JITCompiler::compile_program_selective externally to JIT-compile functions that benefit from native execution. The caller is responsible for performing the compilation via shape-jit (which depends on shape-vm, so the dependency flows one way).

§Example (in a crate that depends on both shape-vm and shape-jit):
ⓘ
let mut compiler = shape_jit::JITCompiler::new()?;
let (_jitted_fn, _table) = compiler.compile_program_selective("main", vm.program())?;
vm.set_jit_compiled();
Source

pub fn is_jit_compiled(&self) -> bool

Returns whether selective JIT compilation has been applied to this VM.

Source

pub fn register_jit_function(&mut self, function_id: u16, ptr: JitFnPtr)

Register a JIT-compiled function in the dispatch table.

After registration, calls to this function_id will attempt JIT dispatch before falling back to bytecode interpretation.

Source

pub fn jit_dispatch_table(&self) -> &HashMap<u16, JitFnPtr>

Get the JIT dispatch table for inspection or external use.

Source

pub fn enable_tiered_compilation( &mut self, ) -> (Receiver<CompilationRequest>, Sender<CompilationResult>)

Enable tiered compilation for this VM.

Must be called after load_program() so the function count is known. The caller is responsible for spawning a background compilation thread that reads from the request channel and sends results back.

Returns (request_rx, result_tx) that the background thread should use.

Source

pub fn tier_manager(&self) -> Option<&TierManager>

Get a reference to the tier manager, if tiered compilation is enabled.

Source

pub fn feedback_vectors(&self) -> &[Option<FeedbackVector>]

Access the feedback vectors (for JIT compilation).

Source

pub fn program(&self) -> &BytecodeProgram

Get a reference to the loaded program (for external JIT compilation).

Source

pub fn time_travel(&self) -> Option<&TimeTravel>

Get a reference to the time-travel debugger, if enabled.

Source

pub fn time_travel_mut(&mut self) -> Option<&mut TimeTravel>

Get a mutable reference to the time-travel debugger, if enabled.

Source

pub fn module_registry(&self) -> &ModuleExportRegistry

Get a reference to the extension module registry.

Source

pub fn get_function_id(&self, name: &str) -> Option<u16>

Get function ID for fast repeated calls (avoids name lookup in hot loops)

Source§

impl VirtualMachine

Source

pub fn register_stdlib_module(&mut self, module: ModuleExports)

Register a built-in stdlib module into the VM’s module registry. Delegates to register_extension — this is a semantic alias to distinguish VM-native stdlib modules from user-installed extension plugins.

Source

pub fn register_extension(&mut self, module: ModuleExports)

Register an external/user extension module (e.g. loaded from a .so plugin) into the VM’s module registry. Also merges any method intrinsics for fast Object dispatch.

Phase-2c surface (ADR-006 §2.7.4 / §2.7.5): the body wraps each TypedModuleFunction into a ModuleFn whose signature is Fn(&[ValueWord], &ModuleContext) -> Result<ValueWord, String>. ValueWord was deleted by the strict-typing bulldozer (no type to import); the kinded rebuild per §2.7.5 makes ModuleFn’s argument slice &[KindedSlot] and its return Result<KindedSlot, String>. Extensions stay on the stable raw-bits ABI and convert at the RawCallableInvoker boundary inside shape-runtime.

The cross-crate ModuleFn signature change is shape-runtime territory (R-shape-runtime sub-cluster) and the corresponding TypedReturn::into_value_word() helper is also deleted; this caller hand-off lands in the Phase-2c rebuild session.

Source

pub fn register_module_fn_entry(&mut self, entry: ModuleFnEntry) -> usize

Register a module-function entry in the table and return its ID.

Phase-2c surface (ADR-006 §2.7.4 / §2.7.5): the ValueWord::ModuleFunction carrier shape this function feeds depends on the deleted ValueWord runtime representation. Replaced with a kinded NativeKind::ModuleFunction-style ID carrier in the Phase-2c rebuild.

Source

pub fn populate_module_objects(&mut self)

Populate extension module objects as module_bindings — W17-comptime-vm-dispatch rebuild.

W17-comptime-vm-dispatch (Phase 2d Wave 3, 2026-05-12). Per ADR-006 §2.7.26 amendment. Builds a kinded TypedObject per registered extension module, with field slots that store module-function-id field references as Ptr(HeapKind::ModuleFn) inline-scalar payloads. The dispatch chain LoadModuleBinding(idx) + GetFieldTyped(...) + CallValue routes through:

  1. LoadModuleBinding(idx) reads the kinded module-binding slot (TypedObject + Ptr(HeapKind::TypedObject) kind) and pushes it via clone_with_kind retain-on-read (§2.7.7).
  2. GetFieldTyped { type_id, field_idx, field_type_tag } pops the receiver, recovers the Arc<TypedObjectStorage> per ADR-005 §1, and reads the field. The compiler emits field_type_tag = FIELD_TAG_ANY for schema fields of FieldType::Any (the comptime predeclared schema shape). The op_get_field_typed body falls through to push_field_value_with_kind, which sources the kind from storage.field_kinds[field_idx] (the §2.7.7 parallel-kind track) — resolving hardening item (f) — and pushes the module_fn_id as u64 bits with kind Ptr(HeapKind::ModuleFn).
  3. CallValue pops args + callee, dispatches via call_value_immediate_nb whose Ptr(HeapKind::ModuleFn) arm routes to invoke_module_fn_id_stub(bits as usize, args) — the same path used by W17-snapshot-roundtrip for direct module-fn invocation.

Per-module construction:

  • Look up the predeclared __mod_<name> schema (registered by compiler/comptime.rs::ensure_module_object_schema before bytecode compilation). The schema field names define the storage’s field order; missing schemas are skipped (the module’s exports remain unreachable through this binding).
  • For each typed export (sync and async), register a ModuleFnEntry::Typed / TypedAsync into module_fn_table to obtain a module_fn_id.
  • For each schema field, look up the matching module_fn_id, write a ValueSlot::from_raw(module_fn_id as u64) with field_kinds[i] = Ptr(HeapKind::ModuleFn) and heap_mask bit set. Unmatched fields (a schema field with no corresponding export) get the (0u64, NativeKind::Bool) sentinel pair — same shape as the module_binding_pad_to_kinded uninitialised-slot convention.
  • Construct Arc<TypedObjectStorage> via the typed constructor per ADR-006 §2.4 and write the Ptr(HeapKind::TypedObject) slot to the module-binding via module_binding_write_kinded (§2.7.8 / Q10 lockstep).

Resolves the upstream populate_module_objects no-op blocker flagged by W17-snapshot-roundtrip (commit fbfbfb6). The 4 comptime introspection forms wired by C2-comptime-rebuild (a5df165) — build_config / implements / warning / error — now dispatch end-to-end via VM mode.

Source§

impl VirtualMachine

Source

pub fn enable_output_capture(&mut self)

Enable output capture for testing When enabled, print output goes to an internal buffer instead of stdout

Source

pub fn disable_output_capture(&mut self)

Disable output capture — print goes to stdout again.

Source

pub fn get_captured_output(&self) -> Vec<String>

Get captured output (returns empty vec if capture not enabled)

Source

pub fn clear_captured_output(&mut self)

Clear captured output

Source

pub fn last_error_line(&self) -> Option<u32>

Get the line number of the last error (for LSP integration)

Source

pub fn last_error_file(&self) -> Option<&str>

Get the file path of the last error (for LSP integration)

Source

pub fn take_last_uncaught_exception(&mut self) -> Option<KindedSlot>

Take the last uncaught exception payload if present.

Source§

impl VirtualMachine

Source

pub fn load_program(&mut self, program: BytecodeProgram)

Load a program into the VM

Source

pub fn load_linked_program(&mut self, linked: LinkedProgram)

Load a LinkedProgram into the VM, extracting content-addressed metadata directly from the linked function table.

This converts the LinkedProgram into the flat BytecodeProgram layout that the executor expects, then populates function_hashes and function_entry_points from the linked function metadata.

Source

pub fn patch_function( &mut self, fn_id: u16, new_blob: FunctionBlob, ) -> Result<Option<FunctionHash>, String>

Hot-patch a single function in the loaded program with a new blob.

The new blob’s instructions, constants, and strings replace the existing function’s bytecode in-place. The function’s metadata (arity, param names, locals count, etc.) is also updated. The content hash is recorded so that in-flight frames referencing the old hash remain valid (they execute from their saved IP which is now stale, but callers that resolve by function ID will pick up the new code on the next call).

Returns Ok(old_hash) on success (the previous content hash, if any), or Err(msg) if the function ID is out of range.

Source

pub fn load_program_with_permissions( &mut self, program: Program, granted: &PermissionSet, ) -> Result<(), PermissionError>

Load a content-addressed Program with permission checking.

Links the program, checks that total_required_permissions is a subset of granted, and loads normally if the check passes. Returns an error listing the missing permissions if the check fails.

Source

pub fn load_linked_program_with_permissions( &mut self, linked: LinkedProgram, granted: &PermissionSet, ) -> Result<(), PermissionError>

Load a LinkedProgram with permission checking.

Checks that total_required_permissions is a subset of granted, then loads normally. Returns an error listing the missing permissions if the check fails.

Source

pub fn module_bindings_snapshot(&self) -> Vec<KindedSlot>

Reset VM state Get a snapshot of all module binding values.

Phase-1b-vm Wave-ε E-vm-impl-tail: the legacy Vec<ValueWord> return type referenced the deleted runtime carrier (CLAUDE.md “Forbidden Patterns”). The signature is flipped to the kinded carrier Vec<shape_value::KindedSlot> per ADR-006 §2.7 / Q7. The §2.7.8 / Q10 parallel-kind track is now live on module_binding_kinds, and module_binding_read_owned_kinded returns KindedSlot shares per binding — the kinded read backbone is in place. The body remains a todo!() until the Phase-2c snapshot revival lands the host-API surface (§2.7.4) — at that point the body is one map+collect over (0..self.module_bindings_len()).map(|i| self.module_binding_read_owned_kinded(i)).collect().

Source

pub fn reset_for_trampoline(&mut self)

Reset VM execution state for trampoline use. Clears stack, call frames, error state, and exception handlers but preserves the loaded program, module bindings, module_fn_table, and registered extensions.

Source

pub fn reset(&mut self)

Source

pub fn reset_stack(&mut self)

Reset stack only (for reusing compiled program across iterations) Keeps program, module_bindings, and GC state intact - only clears execution state

Source

pub fn reset_minimal(&mut self)

Minimal reset for hot loops - only clears essential state Use this when you know the function doesn’t create GC objects or use exceptions

Source

pub fn push_value(&mut self, _value: KindedSlot)

Push a value onto the stack (public, for testing and host integration).

Phase-1b-vm Wave-ε E-vm-impl-tail: the legacy ValueWord parameter type referenced the deleted runtime carrier (CLAUDE.md “Forbidden Patterns”). The signature is flipped to the kinded carrier shape_value::KindedSlot per ADR-006 §2.7 / Q7. The kinded direct-write path on the VM is push_kinded(bits, kind); host-integration / test helpers that still build values via the legacy carrier need a Phase-2c host-API rebuild (see ADR-006 §2.7.4 — output-adapter cluster). The body is preserved as a todo!() so callers fail loudly rather than silently dropping the value.

Trait Implementations§

Source§

impl DebuggerIntegration for VirtualMachine

Source§

fn trace_state(&self)

Trace VM state (for debugging)
Source§

fn debug_break(&self)

Trigger a debug break
Source§

fn instruction_pointer(&self) -> usize

Get current instruction pointer
Source§

fn stack_size(&self) -> usize

Get stack size
Source§

fn stack_top(&self) -> Option<String>

Get top of stack as a debug-formatted string (display only).
Source§

fn stack_values_vec(&self) -> Vec<String>

Get all stack values as debug-formatted strings (display only).
Source§

fn call_stack_depth(&self) -> usize

Get call stack depth
Source§

fn call_frames(&self) -> &[CallFrame]

Get call frames (for debugging)
Source§

fn local_values_vec(&self) -> Vec<String>

Get local variables as debug-formatted strings (display only).
Source§

fn module_binding_values(&self) -> Vec<(u64, NativeKind)>

Get module-binding values for data-flow inspection. Read more
Source§

fn set_module_binding(&mut self, index: usize, bits: u64, kind: NativeKind)

Set a module-binding variable by index. Read more
Source§

fn set_trace_mode(&mut self, enabled: bool)

Set trace mode
Source§

fn debugger_mut(&mut self) -> Option<&mut VMDebugger>

Get mutable reference to debugger
Source§

fn has_debugger(&self) -> bool

Check if debugger is enabled
Source§

impl Drop for VirtualMachine

Drop implementation for VirtualMachine.

Releases the strong-count share that each live stack slot and each module-binding slot owns over its heap-tagged payload. Per ADR-006 §2.7.7 / §2.7.8, the stack and the module-binding store each carry a parallel Vec<NativeKind> track; teardown dispatches drop_with_kind(bits, kind) per slot — the kind-aware counterpart of the deleted vw_drop_slice call (forbidden #8 per §2.7.7).

The shared_module_bindings Arc reclamation still runs first because those slots hold raw Arc::into_raw pointer bits (not heap-tagged values) and the producer is the unique strong owner — the kind-aware drop loop over module_bindings skips them because the slot bits have been zeroed and drop_with_kind is a no-op on the zero bit pattern.

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl GCIntegration for VirtualMachine

Available on non-crate feature gc only.
Source§

fn maybe_collect_garbage(&mut self)

Maybe trigger garbage collection based on config
Source§

fn force_gc(&mut self) -> GCResult

Force garbage collection
Source§

fn gc_stats(&self) -> GCStats

Get GC statistics
Source§

fn gc_heap_size(&self) -> usize

Get GC heap size
Source§

fn gc_object_count(&self) -> usize

Get GC object count
Source§

fn gc(&self) -> &GarbageCollector

Access the garbage collector
Source§

fn gc_mut(&mut self) -> &mut GarbageCollector

Access the garbage collector mutably
Source§

impl TypedObjectOps for VirtualMachine

Source§

fn op_get_field_typed( &mut self, instruction: &Instruction, ) -> Result<(), VMError>

Get field from typed object using precomputed field type tag.

ADR-006 §2.7.7 / Wave 6.5 cluster D-typed-obj-ops — receiver pop uses the kinded API; heap dispatch is slot.as_heap_value() + HeapValue::TypedObject(arc) match (Q8 single-discriminator). The receiver share is dropped via drop_with_kind after the field load completes (the loaded value owns its own retained share via clone_with_kind inside push_field_value).

Source§

fn op_set_field_typed( &mut self, instruction: &Instruction, ) -> Result<(), VMError>

Set field on typed object using precomputed field type tag.

W17-typed-object-mutation (2026-05-11) — write-path rebuild on top of TypedObjectStorage::write_slot_in_place (the kinded in-place projection writer added by W17-references-mutation close 30b9ebf, ADR-006 §2.7.13 / Q14). Mirror of the RefTarget::TypedField arm in write_ref_target (variables/mod.rs:3100) — same single-threaded VM contract, same kind-invariance debug_assert, same heap_mask-driven drop_with_kind on the prior occupant.

Stack contract (per assignment.rs:611-625 emit pattern): pop value, pop receiver; mutate the receiver’s slot in place; push the (now-mutated) receiver back so emit_nested_store_back can either store it back to a local/binding identifier or Pop it for non-identifier roots. Schema-mismatch falls back through the same name-based + IC lookup chain as op_get_field_typed.

Auto Trait Implementations§

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> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

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

Source§

type Error = !

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

fn try_from(value: U) -> Result<T, !>

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.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more