Skip to main content

lashlang/runtime/
entry_points.rs

1//! Public compile/execute entry points for lashlang programs.
2
3use crate::ast::Program;
4use crate::tracking::LashlangExecutionContext;
5use crate::{LinkedModule, ModuleArtifact, ProcessRef};
6
7use super::record::intern_symbol;
8use super::{
9    CompiledProgram, Compiler, ExecutionHost, ExecutionOutcome, ExecutionScratch, LASH_TYPE_KEY,
10    ProjectedBindings, RuntimeError, SlotState, State, Vm,
11};
12
13pub enum ExecutableProgram<'program> {
14    Program(&'program Program),
15    Compiled(&'program CompiledProgram),
16}
17
18impl<'program> From<&'program Program> for ExecutableProgram<'program> {
19    fn from(program: &'program Program) -> Self {
20        Self::Program(program)
21    }
22}
23
24impl<'program> From<&'program CompiledProgram> for ExecutableProgram<'program> {
25    fn from(program: &'program CompiledProgram) -> Self {
26        Self::Compiled(program)
27    }
28}
29
30pub fn compile(source: &str) -> Result<CompiledProgram, crate::parser::ParseError> {
31    crate::parse(source).map(|program| compile_program_internal(&program))
32}
33
34pub(crate) fn compile_program_internal(program: &Program) -> CompiledProgram {
35    let (chunk, compile_stats) = Compiler::compile_program(program);
36    CompiledProgram {
37        chunk,
38        compile_stats,
39    }
40}
41
42pub fn compile_linked(linked: &LinkedModule) -> CompiledProgram {
43    let (chunk, compile_stats) = Compiler::compile_linked_program(
44        linked.program(),
45        (&linked.artifact).into(),
46        LashlangExecutionContext::main(linked.artifact.module_ref.clone()),
47    );
48    CompiledProgram {
49        chunk,
50        compile_stats,
51    }
52}
53
54pub fn compile_process(
55    program: &Program,
56    process_name: &str,
57) -> Result<CompiledProgram, RuntimeError> {
58    let process = program
59        .process(process_name)
60        .ok_or_else(|| RuntimeError::ValueError {
61            message: format!("unknown process `{process_name}`"),
62        })?;
63    let process_program = Program {
64        declarations: program.declarations.clone(),
65        main: process.body.clone(),
66        declaration_spans: program.declaration_spans.clone(),
67        expression_spans: Vec::new(),
68        expression_source_spans: Vec::new(),
69    };
70    Ok(compile_program_internal(&process_program))
71}
72
73pub fn compile_linked_process(
74    linked: &LinkedModule,
75    process_name: &str,
76) -> Result<CompiledProgram, RuntimeError> {
77    let linked_program = linked.program();
78    let process = linked_program
79        .process(process_name)
80        .ok_or_else(|| RuntimeError::ValueError {
81            message: format!("unknown process `{process_name}`"),
82        })?;
83    let process_program = Program {
84        declarations: linked_program.declarations.clone(),
85        main: process.body.clone(),
86        declaration_spans: linked_program.declaration_spans.clone(),
87        expression_spans: Vec::new(),
88        expression_source_spans: Vec::new(),
89    };
90    let process_ref = linked
91        .artifact
92        .process_ref(process_name)
93        .cloned()
94        .ok_or_else(|| RuntimeError::ValueError {
95            message: format!("linked module does not export process `{process_name}`"),
96        })?;
97    let (chunk, compile_stats) = Compiler::compile_linked_process_program(
98        &process_program,
99        (&linked.artifact).into(),
100        LashlangExecutionContext::process(
101            linked.artifact.module_ref.clone(),
102            process_ref,
103            process_name,
104        ),
105    );
106    Ok(CompiledProgram {
107        chunk,
108        compile_stats,
109    })
110}
111
112pub fn compile_module_artifact_process(
113    artifact: &ModuleArtifact,
114    process_ref: &ProcessRef,
115) -> Result<CompiledProgram, RuntimeError> {
116    let process_name =
117        artifact
118            .process_name_for_ref(process_ref)
119            .ok_or_else(|| RuntimeError::ValueError {
120                message: format!(
121                    "module artifact `{}` does not export process ref {:?}",
122                    artifact.module_ref, process_ref
123                ),
124            })?;
125    let process =
126        artifact
127            .canonical_ir
128            .process(process_name)
129            .ok_or_else(|| RuntimeError::ValueError {
130                message: format!(
131                    "module artifact `{}` is missing process `{process_name}`",
132                    artifact.module_ref
133                ),
134            })?;
135    let process_program = Program {
136        declarations: artifact.canonical_ir.declarations.clone(),
137        main: process.body.clone(),
138        declaration_spans: artifact.canonical_ir.declaration_spans.clone(),
139        expression_spans: Vec::new(),
140        expression_source_spans: Vec::new(),
141    };
142    let (chunk, compile_stats) = Compiler::compile_linked_process_program(
143        &process_program,
144        artifact.into(),
145        LashlangExecutionContext::process(
146            artifact.module_ref.clone(),
147            process_ref.clone(),
148            process_name,
149        ),
150    );
151    Ok(CompiledProgram {
152        chunk,
153        compile_stats,
154    })
155}
156
157pub fn prewarm() {
158    for name in [
159        "ok",
160        "value",
161        "error",
162        "__handle__",
163        "handle",
164        LASH_TYPE_KEY,
165        "type",
166        "properties",
167        "required",
168        "items",
169        "enum",
170        "id",
171        "label",
172        "size",
173        "width",
174        "height",
175    ] {
176        intern_symbol(name);
177    }
178}
179
180pub async fn execute<'program, H: ExecutionHost>(
181    program: impl Into<ExecutableProgram<'program>>,
182    state: &mut State,
183    host: &H,
184) -> Result<ExecutionOutcome, RuntimeError> {
185    match program.into() {
186        ExecutableProgram::Program(program) => {
187            let compiled = compile_program_internal(program);
188            execute_compiled_internal(&compiled, state, host).await
189        }
190        ExecutableProgram::Compiled(compiled) => {
191            execute_compiled_internal(compiled, state, host).await
192        }
193    }
194}
195
196pub(crate) async fn execute_compiled_internal<H: ExecutionHost>(
197    program: &CompiledProgram,
198    state: &mut State,
199    host: &H,
200) -> Result<ExecutionOutcome, RuntimeError> {
201    let projected = host.projected_bindings();
202    if let Some(mut scratch) = host.take_scratch() {
203        let result =
204            execute_with_optional_scratch(program, state, host, &projected, Some(&mut scratch))
205                .await;
206        host.store_scratch(scratch);
207        result
208    } else {
209        execute_with_optional_scratch(program, state, host, &projected, None).await
210    }
211}
212
213async fn execute_with_optional_scratch<H: ExecutionHost>(
214    program: &CompiledProgram,
215    state: &mut State,
216    host: &H,
217    projected: &ProjectedBindings,
218    scratch: Option<&mut ExecutionScratch>,
219) -> Result<ExecutionOutcome, RuntimeError> {
220    if let Some(scratch) = scratch {
221        let slots = SlotState::from_globals_with_scratch(
222            std::mem::take(&mut state.globals),
223            &program.chunk.slot_names,
224            scratch,
225            projected,
226        );
227        let mut vm = Vm::new_with_scratch_and_mode(
228            &program.chunk,
229            slots,
230            host,
231            scratch,
232            host.execution_mode(),
233        );
234        let result = run_vm(program, host, &mut vm).await;
235        state.globals = vm.recycle_into_globals(scratch);
236        result
237    } else {
238        let slots = SlotState::from_globals(
239            std::mem::take(&mut state.globals),
240            &program.chunk.slot_names,
241            projected,
242        );
243        let mut vm = Vm::new_with_mode(&program.chunk, slots, host, host.execution_mode());
244        let result = run_vm(program, host, &mut vm).await;
245        state.globals = vm.into_globals();
246        result
247    }
248}
249
250async fn run_vm<H: ExecutionHost>(
251    program: &CompiledProgram,
252    host: &H,
253    vm: &mut Vm<'_, H>,
254) -> Result<ExecutionOutcome, RuntimeError> {
255    if host.profile_execution() {
256        vm.enable_profile();
257    }
258
259    let result = if host.trace_runtime_errors() {
260        vm.run_traced_for_mode().await.map_err(|failure| {
261            let error = failure.error.clone();
262            host.observe_runtime_failure(failure);
263            error
264        })
265    } else {
266        vm.run_for_mode().await
267    };
268
269    if host.profile_execution() {
270        let mut profile = vm.take_profile();
271        profile.compile_stats = program.compile_stats;
272        host.observe_profile(profile);
273    }
274
275    result
276}