Skip to main content

lashlang/runtime/
mod.rs

1//! Lashlang runtime: bytecode `Compiler`, executor `Vm`, value system,
2//! plus the long tail of free helpers (ops/format/json/access).
3//!
4//! `mod.rs` only owns the cross-cutting types (`RuntimeError`,
5//! `RuntimeFailure`, `ExecutionScratch`, `ExecutionOutcome`,
6//! `CompiledProgram`, `ProfileReport` + friends) and the `pub use` /
7//! `pub(crate) use` wiring that re-exports each focused submodule's items
8//! both publicly (for `lashlang::lib.rs`) and crate-internally (so
9//! sibling submodules can write `use super::*` without caring which
10//! file an item lives in).
11
12use crate::lexer::Span;
13use thiserror::Error;
14
15mod access;
16mod cache;
17mod compiler;
18mod entry_points;
19mod format;
20mod host;
21mod instruction;
22mod json;
23mod ops;
24mod record;
25mod schema;
26mod state;
27mod value;
28mod vm;
29
30pub use cache::{
31    CompiledLinkedProgram, CompiledProcessCache, CompiledProcessCacheKey, CompiledProgramCache,
32    CompiledProgramCacheStats, LinkedProgramCache, LinkedProgramCacheError,
33};
34#[allow(unused_imports)]
35pub(crate) use compiler::*;
36pub use entry_points::{
37    ExecutableProgram, compile, compile_linked, compile_linked_process,
38    compile_module_artifact_process, compile_process, execute, prewarm,
39};
40pub use host::{
41    AbilityOp, AbilityResult, ExecutionEnvironment, ExecutionHost, ExecutionHostError,
42    ExecutionMode, ProcessEvent, ProcessEventKind, ProcessSignal, ProcessStart, ResourceOperation,
43    Sleep, SleepKind,
44};
45#[allow(unused_imports)]
46pub(crate) use instruction::*;
47pub use json::from_json;
48pub use record::Record;
49#[allow(unused_imports)]
50pub(crate) use record::{Symbol, intern_symbol, lookup_symbol, record_with_capacity, symbol_name};
51#[allow(unused_imports)]
52pub(crate) use schema::{
53    ValidationPlan, compile_schema_value, execute_validate_builtin, execute_validation_plan,
54};
55#[allow(unused_imports)]
56pub(crate) use vm::*;
57// Re-exports of helpers that live in the focused submodules but need to be
58// reachable via `use super::*` from sibling submodules + via `super::name`
59// from `vm.rs` / `compiler.rs`. These look "unused" from mod.rs's POV but
60// are load-bearing for the rest of the runtime crate.
61#[allow(unused_imports)]
62pub(crate) use access::*;
63#[allow(unused_imports)]
64pub(crate) use format::*;
65#[allow(unused_imports)]
66pub(crate) use json::*;
67#[allow(unused_imports)]
68pub(crate) use ops::*;
69pub use state::{Snapshot, State};
70pub use value::{
71    ImageValue, LASH_HOST_VALUE_KEY, LASH_HOST_VALUE_TYPE_KEY, LASH_MODULE_REF_KEY,
72    LASH_PROCESS_NAME_KEY, LASH_PROCESS_REF_KEY, LASH_PROCESS_VALUE_KEY,
73    LASH_REQUIRED_SURFACE_REF_KEY, LASH_TYPE_KEY, ListValue, ProjectedBindingError,
74    ProjectedBindings, ProjectedFuture, ProjectedHostValue, ProjectedReadRequest,
75    ProjectedReadResponse, ProjectedValue, ResourceHandle, Value,
76};
77use vm::IterState;
78
79#[derive(Clone, Debug, Error, PartialEq)]
80pub enum RuntimeError {
81    #[error("unknown name `{name}`")]
82    UndefinedVariable { name: String },
83    #[error("`for` expects a list")]
84    NonListIteration,
85    #[error("`{keyword}` can only be used inside a process body")]
86    ProcessControlOutsideProcess { keyword: &'static str },
87    #[error("`{keyword}` can't be used inside a process body")]
88    ForegroundControlInsideProcess { keyword: &'static str },
89    #[error("unknown builtin `{name}`")]
90    UnknownBuiltin { name: String },
91    #[error("{message}")]
92    TypeError { message: String },
93    #[error("{message}")]
94    ValueError { message: String },
95}
96
97#[derive(Clone, Debug, Error, PartialEq)]
98#[error("{error}")]
99pub struct RuntimeFailure {
100    pub error: RuntimeError,
101    pub span: Option<Span>,
102}
103
104#[derive(Default)]
105pub struct ExecutionScratch {
106    stack: Vec<Value>,
107    iter_stack: Vec<IterState>,
108    slot_values: Vec<Option<Value>>,
109}
110
111impl ExecutionScratch {
112    pub fn new() -> Self {
113        Self::default()
114    }
115}
116
117pub(crate) const COOPERATIVE_YIELD_INSTRUCTION_BUDGET: usize = 1024;
118
119#[derive(Clone)]
120pub struct CompiledProgram {
121    pub(crate) chunk: Chunk,
122    pub(crate) compile_stats: CompileStats,
123}
124
125impl std::fmt::Debug for CompiledProgram {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        f.debug_struct("CompiledProgram")
128            .field("instruction_count", &self.chunk.code.len())
129            .field("compile_stats", &self.compile_stats)
130            .finish()
131    }
132}
133
134impl CompiledProgram {
135    pub fn compile_stats(&self) -> &CompileStats {
136        &self.compile_stats
137    }
138
139    pub fn static_graph_json(&self, module_ref: impl Into<String>) -> serde_json::Value {
140        if let Some(context) = &self.chunk.module_context {
141            crate::graph::static_graph_json_for_module_ref(
142                context.module_ref.clone(),
143                &context.process_refs,
144            )
145        } else {
146            crate::graph::static_graph_json_without_ir(module_ref)
147        }
148    }
149}
150
151#[derive(Clone, Debug, PartialEq)]
152pub enum ExecutionOutcome {
153    Continued,
154    Finished(Value),
155    Failed(Value),
156}
157
158#[derive(Clone, Debug, Default)]
159pub struct ProfileReport {
160    instruction_stats: Vec<ProfileStat>,
161    builtin_stats: Vec<ProfileStat>,
162    compile_stats: CompileStats,
163}
164
165impl ProfileReport {
166    pub fn instruction_stats(&self) -> &[ProfileStat] {
167        &self.instruction_stats
168    }
169
170    pub fn builtin_stats(&self) -> &[ProfileStat] {
171        &self.builtin_stats
172    }
173
174    pub fn compile_stats(&self) -> &CompileStats {
175        &self.compile_stats
176    }
177
178    pub fn merge(&mut self, other: &Self) {
179        merge_stats(&mut self.instruction_stats, &other.instruction_stats);
180        merge_stats(&mut self.builtin_stats, &other.builtin_stats);
181        self.compile_stats.merge(&other.compile_stats);
182    }
183}
184
185/// Compile-time statistics captured when a program is compiled. Independent
186/// of run-time profiling — these counts reflect the shape of the compiled
187/// program itself (how many Type literals it contains, how many got
188/// const-folded, etc.). Runtime cost of `Type` evaluation appears in the
189/// instruction profile under `build_type_ref` / `build_record` / etc.
190#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
191pub struct CompileStats {
192    pub type_literals_total: u64,
193    pub type_literals_const_folded: u64,
194    pub type_literals_dynamic: u64,
195    pub type_ref_sites: u64,
196}
197
198impl CompileStats {
199    pub fn merge(&mut self, other: &Self) {
200        self.type_literals_total += other.type_literals_total;
201        self.type_literals_const_folded += other.type_literals_const_folded;
202        self.type_literals_dynamic += other.type_literals_dynamic;
203        self.type_ref_sites += other.type_ref_sites;
204    }
205}
206
207#[derive(Clone, Debug, Default)]
208pub struct ProfileStat {
209    pub name: &'static str,
210    pub count: u64,
211    pub total_ns: u128,
212}
213
214impl ProfileStat {
215    pub fn avg_ns(&self) -> u128 {
216        if self.count == 0 {
217            0
218        } else {
219            self.total_ns / self.count as u128
220        }
221    }
222}
223/// Unwrap a `Value::Record` that carries the `$lash_type` marker back into the
224/// inner JSON-Schema value. Returns `None` when the value is not a wrapped
225/// Type literal.
226pub fn unwrap_type_value(value: &Value) -> Option<&Value> {
227    let record = value.as_record()?;
228    if record.len() != 1 {
229        return None;
230    }
231    record.get(LASH_TYPE_KEY)
232}
233
234#[cfg(test)]
235mod tests;