hara_native/vm/program.rs
1//! Program representation for the experimental bytecode VM.
2//!
3//! Constants reuse `core::Value` directly: the VM does not duplicate the
4//! Hara value model. The versioned `vm::artifact` codec persists validated
5//! programs for packaging and browser startup without reparsing source.
6
7use super::opcode::Instruction;
8use super::source_map::SourceMap;
9use crate::core::Value;
10use crate::kernel::SchemaType;
11use std::collections::HashMap;
12
13/// Maximum number of entries in the constant pool.
14pub const MAX_CONSTANTS: usize = 1 << 24;
15/// Maximum number of instructions per function prototype.
16pub const MAX_INSTRUCTIONS: usize = 1 << 24;
17/// Maximum number of local slots per frame (inherent to the `u16` operands).
18pub const MAX_LOCALS: usize = u16::MAX as usize;
19/// Maximum computed operand-stack depth for any function.
20pub const MAX_OPERAND_STACK: usize = 4096;
21/// Maximum number of arguments in one primitive call (`u8` operand).
22pub const MAX_PRIMITIVE_ARGUMENTS: usize = u8::MAX as usize;
23/// Maximum number of captured values in one closure (`u8` operand).
24pub const MAX_CAPTURES: usize = u8::MAX as usize;
25
26/// Index of a function prototype inside [`Program::functions`].
27pub type FunctionId = u16;
28
29/// One `catch` clause of a [`TryEntry`]: the machine stores the caught
30/// value into `binding` and jumps to `target` when `class` matches.
31#[derive(Debug, Clone)]
32pub struct CatchEntry {
33 /// The dispatch class; `Exception` for the implicit 3-element shape.
34 pub class: String,
35 pub binding: u16,
36 pub target: u32,
37}
38
39/// A static handler table entry: the protected range `[start, end)` and
40/// its catch/finally regions. Registered outermost-first, so the machine's
41/// reverse-order search finds the innermost covering entry.
42#[derive(Debug, Clone)]
43pub struct TryEntry {
44 pub start: u32,
45 pub end: u32,
46 /// Operand-stack height at try entry; the machine truncates to this on
47 /// unwind. Patched in after stack analysis and verified by validation.
48 pub depth: u16,
49 pub catches: Vec<CatchEntry>,
50 pub finally: Option<u32>,
51 /// Hidden slots holding the pending result (a value or an error
52 /// message string) and the error flag; present exactly when `finally`
53 /// is present.
54 pub pending_value: Option<u16>,
55 pub pending_error: Option<u16>,
56}
57
58/// A compiled function. The entry function has arity and capture count 0;
59/// `fn`/`defn` forms contribute the remaining prototypes.
60#[derive(Debug, Clone)]
61pub struct FunctionPrototype {
62 pub name: Option<String>,
63 /// Calling this prototype creates a child async execution and returns
64 /// its stable result promise instead of the direct body value.
65 pub async_function: bool,
66 /// Required argument count. Always 0 for the entry function. When
67 /// `variadic` is set this counts only the fixed parameters.
68 pub arity: u16,
69 /// Whether the last parameter binds the remaining arguments as a
70 /// `Value::List` (`[a b & rest]`).
71 pub variadic: bool,
72 /// Number of captured values the frame expects in the slots directly
73 /// above the parameters. Always 0 for the entry function.
74 pub capture_count: u16,
75 /// Number of local slots the frame allocates.
76 pub local_count: u16,
77 /// Declared operand-stack high-water mark; the validator recomputes
78 /// and verifies it.
79 pub max_stack: u16,
80 pub code: Vec<Instruction>,
81 pub source_map: SourceMap,
82 /// Static handler table for `try`/`catch`/`finally`; empty for
83 /// functions without protected regions.
84 pub handlers: Vec<TryEntry>,
85}
86
87/// A compiled program: a constant pool plus function prototypes.
88#[derive(Debug, Clone)]
89pub struct Program {
90 /// Owning namespace for a module lowered from HALC; source snippets have none.
91 pub namespace: Option<String>,
92 pub constants: Vec<Value>,
93 /// Hara metadata tables for `DefGlobal` (docstrings, attr maps,
94 /// computed arglists), assembled at compile time from the source
95 /// forms. Empty for programs without global definitions.
96 pub var_metadata: Vec<std::rc::Rc<crate::lang::data::Metadata>>,
97 /// Canonical named-schema graph supplied by HALC lowering.
98 pub schema_types: HashMap<String, SchemaType>,
99 /// Function annotations normalized against `schema_types`.
100 pub function_types: HashMap<String, SchemaType>,
101 /// Conservative body-derived facts. These never replace declarations.
102 pub inferred_function_types: HashMap<String, SchemaType>,
103 pub functions: Vec<FunctionPrototype>,
104 pub entry: FunctionId,
105}
106
107impl Program {
108 /// The prototype execution starts from.
109 pub fn entry_function(&self) -> &FunctionPrototype {
110 &self.functions[self.entry as usize]
111 }
112
113 /// Returns the normalized annotation for a compiled prototype, following
114 /// named-schema references without expanding recursive schema graphs.
115 pub fn function_schema(&self, function: FunctionId) -> Option<&SchemaType> {
116 let prototype = self.functions.get(function as usize)?;
117 let name = prototype.name.as_deref()?;
118 let qualified = if name.contains('/') {
119 name.to_owned()
120 } else {
121 format!("{}/{}", self.namespace.as_deref()?, name)
122 };
123 let mut schema = self
124 .function_types
125 .get(&qualified)
126 .or_else(|| self.inferred_function_types.get(&qualified))?;
127 let mut visited = std::collections::HashSet::new();
128 while let SchemaType::Reference(target) = schema {
129 if !visited.insert(target.as_str()) {
130 return Some(schema);
131 }
132 let Some(resolved) = self.schema_types.get(target) else {
133 return Some(schema);
134 };
135 schema = resolved;
136 }
137 Some(schema)
138 }
139
140 /// Whether every argument slot for this prototype has a proven i64
141 /// representation. The tracing JIT still emits entry guards; this fact
142 /// only allows it to begin recording before the generic hot threshold.
143 pub fn function_has_i64_parameters(&self, function: FunctionId) -> bool {
144 let Some(prototype) = self.functions.get(function as usize) else {
145 return false;
146 };
147 let Some(SchemaType::Function(arities)) = self.function_schema(function) else {
148 return false;
149 };
150 arities.iter().any(|arity| {
151 arity.fixed.len() == usize::from(prototype.arity)
152 && arity.rest.is_some() == prototype.variadic
153 && arity
154 .fixed
155 .iter()
156 .all(|value| {
157 matches!(value, SchemaType::Primitive(name) if name == "int" || name == "long")
158 })
159 && arity.rest.as_deref().is_none_or(
160 |value| {
161 matches!(value, SchemaType::Primitive(name) if name == "int" || name == "long")
162 },
163 )
164 })
165 }
166}