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