Skip to main content

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}