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 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}