Skip to main content

hara_native/
vm.rs

1//! Experimental staged bytecode VM for the Rust runtime (issue #195).
2//!
3//! Milestone 4 compiles literals, lexical locals, arithmetic,
4//! comparisons, `if`, `do`, `let`, `loop`/`recur`, `fn` closures
5//! (including variadic), exceptions, and the registry-direct global
6//! forms (`def`, `defn` single- and multi-arity, `var`, `set!`,
7//! `declare`, `defstruct`, `field`, `instance?`) into a typed
8//! instruction program and executes it on a stack machine (issue #223).
9//! See `notes/rust-bytecode-vm.md` for the design.
10//!
11//! The main `hara-wasm` crate enables `bytecode-vm` in its default feature
12//! set. VM entry points remain feature-gated for compiler-free and minimal
13//! builds, while live snapshots and one-boundary stepping are separately
14//! opt-in through `bytecode-observation`. The VM never falls back to the
15//! tree-walking evaluator: unsupported forms are typed compile errors.
16
17#[path = "vm/artifact.rs"]
18pub mod artifact;
19#[path = "vm/bundle.rs"]
20pub mod bundle;
21#[path = "vm/compiler.rs"]
22pub mod compiler;
23#[cfg(feature = "code-vm-conformance")]
24#[path = "vm/conformance.rs"]
25pub mod conformance;
26#[path = "vm/disassemble.rs"]
27pub mod disassemble;
28#[path = "vm/error.rs"]
29pub mod error;
30#[path = "vm/fiber.rs"]
31pub mod fiber;
32#[path = "vm/frame.rs"]
33pub mod frame;
34#[path = "vm/machine.rs"]
35pub mod machine;
36#[path = "vm/opcode.rs"]
37pub mod opcode;
38#[path = "vm/prepared.rs"]
39pub mod prepared;
40#[path = "vm/program.rs"]
41pub mod program;
42#[cfg(feature = "bytecode-observation")]
43#[path = "vm/session.rs"]
44pub mod session;
45#[path = "vm/slot.rs"]
46mod slot;
47#[path = "vm/source_map.rs"]
48pub mod source_map;
49#[path = "vm/validate.rs"]
50pub mod validate;
51
52#[cfg(test)]
53#[path = "vm/conformance_tests.rs"]
54mod conformance_tests;
55#[cfg(test)]
56#[path = "vm/language_conformance_tests.rs"]
57mod language_conformance_tests;
58#[cfg(test)]
59#[path = "vm/execution_tests.rs"]
60mod execution_tests;
61#[cfg(all(test, feature = "bytecode-vm"))]
62mod numeric_predicate_tests;
63#[cfg(test)]
64#[path = "vm/tests.rs"]
65mod tests;
66
67/// Normalizes an error message to a coarse category for comparison. The
68/// fiber and the synchronous fallback phrase some shape errors
69/// differently ("let expects bindings" vs "let expects a binding list or
70/// vector"); each bucket covers every phrasing of one failure class.
71/// Shared by the differential tests and the corpus-driven conformance
72/// tests; the bucket names are pinned by
73/// `specs/01-lang/010-bytecode/draft/conformance/bytecode-vm.edn`.
74#[cfg(test)]
75pub(crate) fn error_category(message: &str) -> &'static str {
76    let buckets: &[(&[&str], &str)] = &[
77        (&["division by zero"], "division by zero"),
78        (&["integer overflow"], "integer overflow"),
79        (&["expects numbers"], "expects numbers"),
80        (
81            &["expects at least", "expects arguments"],
82            "primitive arity",
83        ),
84        (&["expects 2 or 3 arguments"], "if arity"),
85        (
86            &["expects bindings and a body", "expects bindings and body"],
87            "binding body shape",
88        ),
89        (
90            &["expects a binding list or vector", "expects bindings"],
91            "binding bindings shape",
92        ),
93        (&["require name/value pairs"], "binding pairs"),
94        (&["function expects"], "function arity"),
95        (&["value is not callable"], "not callable"),
96        (
97            &[
98                "function parameters must be a vector",
99                "defn arity must contain parameters and a body",
100            ],
101            "fn params shape",
102        ),
103        (&["conj expects a collection"], "conj receiver"),
104        (&["throw expects one value"], "throw arity"),
105        (&["thrown: "], "thrown"),
106        // "unbound var" is checked first: its message contains "unbound
107        // var", not "unbound symbol", so order is safe either way.
108        (&["unbound var"], "unbound var"),
109        (&["unbound symbol"], "unbound symbol"),
110        (
111            &[
112                "ns+ does not accept",
113                "ns accepts only one",
114                "ns clause",
115                ":config",
116                "Namespace alias",
117            ],
118            "namespace config",
119        ),
120        (&["recur"], "recur"),
121        (
122            &[
123                "Invalid number",
124                "Legacy numeric suffixes",
125                "EOF while reading",
126            ],
127            "reader",
128        ),
129    ];
130    for (markers, bucket) in buckets {
131        if markers.iter().any(|marker| message.contains(marker)) {
132            return bucket;
133        }
134    }
135    panic!("unclassified error message: {message}")
136}
137
138pub use artifact::{decode_program, encode_program};
139pub use bundle::{
140    compile_bytecode_bundle, compile_embedded_cli_bundle,
141    compile_embedded_foundation_bootstrap_bundle, compile_embedded_standard_library_bundle,
142    compile_package_bytecode_bundle, decode_bytecode_bundle, embedded_cli_sources,
143    embedded_foundation_bootstrap_sources, encode_bytecode_bundle, eval_bytecode_bundle,
144    eval_eager_bytecode_bundle_with_registries, BytecodeBundleModule, ModuleSource,
145};
146pub(crate) use compiler::rewrite_spanned_form;
147pub use compiler::{
148    compile_form_with_config_allow_unbound_globals, compile_halc_module, compile_source,
149    compile_source_with, compile_source_with_allow_unbound_globals, compile_source_with_config,
150    compile_source_with_config_allow_unbound_globals,
151    compile_spanned_form_with_config_allow_unbound_globals,
152    compile_spanned_forms_with_config_allow_unbound_globals, source_namespace_config,
153    source_uses_dynamic_evaluation,
154};
155pub use disassemble::disassemble;
156pub use error::{CompileError, CompileErrorKind, ValidationError, VmError};
157pub use fiber::{VmFiber, VmFiberState};
158#[cfg(feature = "bytecode-instrumentation")]
159pub use machine::instrumentation::{
160    BytecodeMetrics, CounterProbe, EventRing, InstructionEvent, NoProbe, Opcode, OpcodeCount,
161    SampledProbe, TerminalEvent, TerminalKind, TransitionEvent, TransitionKind, VmEvent, VmProbe,
162    BYTECODE_EVENTS_SCHEMA, BYTECODE_METRICS_SCHEMA,
163};
164#[cfg(feature = "bytecode-observation")]
165pub use machine::observation::{
166    CallFrameSnapshot, HandlerSnapshot, InstructionOperand, InstructionSnapshot,
167    MachineObservationStatus, MachineSnapshot, ObservationEventKind, ObservationEventStatus,
168    ObservationLimits, ObservedStep, ObservedStepOutcome, ProgramSnapshot, SourcePositionSnapshot,
169    ValueSnapshot, BYTECODE_TRACE_SCHEMA,
170};
171pub use machine::{execute_program, execute_program_with_globals, Machine, VmOutcome};
172pub use opcode::Instruction;
173pub use prepared::{prepare_call, PreparedCall};
174pub use program::{FunctionId, FunctionPrototype, Program};
175#[cfg(feature = "bytecode-observation")]
176pub use session::{
177    BytecodeObservationSession, BytecodeSessionError, BytecodeSessionStatus, SessionRetentionLimits,
178};
179pub use validate::validate;
180
181/// Compiles, validates, and executes a closed source string in one step.
182/// Errors from either stage flatten to their display form (which carries
183/// source positions). No fallback to the tree-walking evaluator.
184pub fn eval_source(source: &str) -> Result<crate::core::Value, String> {
185    let program = compile_source(source).map_err(|error| error.to_string())?;
186    execute_program(std::rc::Rc::new(program)).map_err(|error| error.to_string())
187}