1use 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 };
69 Ok(compile_program_internal(&process_program))
70}
71
72pub fn compile_linked_process(
73 linked: &LinkedModule,
74 process_name: &str,
75) -> Result<CompiledProgram, RuntimeError> {
76 let linked_program = linked.program();
77 let process = linked_program
78 .process(process_name)
79 .ok_or_else(|| RuntimeError::ValueError {
80 message: format!("unknown process `{process_name}`"),
81 })?;
82 let process_program = Program {
83 declarations: linked_program.declarations.clone(),
84 main: process.body.clone(),
85 declaration_spans: linked_program.declaration_spans.clone(),
86 expression_spans: Vec::new(),
87 };
88 let process_ref = linked
89 .artifact
90 .process_ref(process_name)
91 .cloned()
92 .ok_or_else(|| RuntimeError::ValueError {
93 message: format!("linked module does not export process `{process_name}`"),
94 })?;
95 let (chunk, compile_stats) = Compiler::compile_linked_process_program(
96 &process_program,
97 (&linked.artifact).into(),
98 LashlangExecutionContext::process(
99 linked.artifact.module_ref.clone(),
100 process_ref,
101 process_name,
102 ),
103 );
104 Ok(CompiledProgram {
105 chunk,
106 compile_stats,
107 })
108}
109
110pub fn compile_module_artifact_process(
111 artifact: &ModuleArtifact,
112 process_ref: &ProcessRef,
113) -> Result<CompiledProgram, RuntimeError> {
114 let process_name =
115 artifact
116 .process_name_for_ref(process_ref)
117 .ok_or_else(|| RuntimeError::ValueError {
118 message: format!(
119 "module artifact `{}` does not export process ref {:?}",
120 artifact.module_ref, process_ref
121 ),
122 })?;
123 let process =
124 artifact
125 .canonical_ir
126 .process(process_name)
127 .ok_or_else(|| RuntimeError::ValueError {
128 message: format!(
129 "module artifact `{}` is missing process `{process_name}`",
130 artifact.module_ref
131 ),
132 })?;
133 let process_program = Program {
134 declarations: artifact.canonical_ir.declarations.clone(),
135 main: process.body.clone(),
136 declaration_spans: artifact.canonical_ir.declaration_spans.clone(),
137 expression_spans: Vec::new(),
138 };
139 let (chunk, compile_stats) = Compiler::compile_linked_process_program(
140 &process_program,
141 artifact.into(),
142 LashlangExecutionContext::process(
143 artifact.module_ref.clone(),
144 process_ref.clone(),
145 process_name,
146 ),
147 );
148 Ok(CompiledProgram {
149 chunk,
150 compile_stats,
151 })
152}
153
154pub fn prewarm() {
155 for name in [
156 "ok",
157 "value",
158 "error",
159 "__handle__",
160 "handle",
161 LASH_TYPE_KEY,
162 "type",
163 "properties",
164 "required",
165 "items",
166 "enum",
167 "id",
168 "label",
169 "size",
170 "width",
171 "height",
172 ] {
173 intern_symbol(name);
174 }
175}
176
177pub async fn execute<'program, H: ExecutionHost>(
178 program: impl Into<ExecutableProgram<'program>>,
179 state: &mut State,
180 host: &H,
181) -> Result<ExecutionOutcome, RuntimeError> {
182 match program.into() {
183 ExecutableProgram::Program(program) => {
184 let compiled = compile_program_internal(program);
185 execute_compiled_internal(&compiled, state, host).await
186 }
187 ExecutableProgram::Compiled(compiled) => {
188 execute_compiled_internal(compiled, state, host).await
189 }
190 }
191}
192
193pub(crate) async fn execute_compiled_internal<H: ExecutionHost>(
194 program: &CompiledProgram,
195 state: &mut State,
196 host: &H,
197) -> Result<ExecutionOutcome, RuntimeError> {
198 let projected = host.projected_bindings();
199 if let Some(mut scratch) = host.take_scratch() {
200 let result =
201 execute_with_optional_scratch(program, state, host, &projected, Some(&mut scratch))
202 .await;
203 host.store_scratch(scratch);
204 result
205 } else {
206 execute_with_optional_scratch(program, state, host, &projected, None).await
207 }
208}
209
210async fn execute_with_optional_scratch<H: ExecutionHost>(
211 program: &CompiledProgram,
212 state: &mut State,
213 host: &H,
214 projected: &ProjectedBindings,
215 scratch: Option<&mut ExecutionScratch>,
216) -> Result<ExecutionOutcome, RuntimeError> {
217 if let Some(scratch) = scratch {
218 let slots = SlotState::from_globals_with_scratch(
219 std::mem::take(&mut state.globals),
220 &program.chunk.slot_names,
221 scratch,
222 projected,
223 );
224 let mut vm = Vm::new_with_scratch_and_mode(
225 &program.chunk,
226 slots,
227 host,
228 scratch,
229 host.execution_mode(),
230 );
231 let result = run_vm(program, host, &mut vm).await;
232 state.globals = vm.recycle_into_globals(scratch);
233 result
234 } else {
235 let slots = SlotState::from_globals(
236 std::mem::take(&mut state.globals),
237 &program.chunk.slot_names,
238 projected,
239 );
240 let mut vm = Vm::new_with_mode(&program.chunk, slots, host, host.execution_mode());
241 let result = run_vm(program, host, &mut vm).await;
242 state.globals = vm.into_globals();
243 result
244 }
245}
246
247async fn run_vm<H: ExecutionHost>(
248 program: &CompiledProgram,
249 host: &H,
250 vm: &mut Vm<'_, H>,
251) -> Result<ExecutionOutcome, RuntimeError> {
252 if host.profile_execution() {
253 vm.enable_profile();
254 }
255
256 let result = if host.trace_runtime_errors() {
257 vm.run_traced_for_mode().await.map_err(|failure| {
258 let error = failure.error.clone();
259 host.observe_runtime_failure(failure);
260 error
261 })
262 } else {
263 vm.run_for_mode().await
264 };
265
266 if host.profile_execution() {
267 let mut profile = vm.take_profile();
268 profile.compile_stats = program.compile_stats;
269 host.observe_profile(profile);
270 }
271
272 result
273}