1use crate::compiler::CompileOutput;
27use crate::compiler::Namespace;
28use crate::compiler::compile;
29use crate::compiler::compiler_data::Ctx;
30use crate::compiler::compiler_data::Dynamiclib;
31use crate::compiler::compiler_data::Function;
32use crate::compiler::compiler_data::State;
33use crate::compiler::compiler_data::Variable;
34use crate::compiler::expr::Expr;
35use candela_vm::data::Data;
36use candela_vm::data::NULL;
37use candela_vm::embed::HostDispatch;
38use candela_vm::embed::HostType;
39use candela_vm::embed::IntoHostFn;
40use candela_vm::embed::Value;
41use candela_vm::embed::marshal_value;
42use candela_vm::embed::unmarshal_value;
43use candela_vm::errors::Diagnostic;
44use candela_vm::errors::ErrorCtx;
45use candela_vm::errors::collect_diagnostic;
46use candela_vm::instr::Instr;
47use candela_vm::rt::DataType;
48use candela_vm::rt::DynamicLibFn;
49use candela_vm::rt::EnumType;
50use candela_vm::rt::HostFnSig;
51use candela_vm::rt::InstrSrc;
52use candela_vm::rt::Pools;
53use candela_vm::rt::Source;
54use candela_vm::rt::Span;
55use candela_vm::rt::Struct;
56use candela_vm::vm;
57use candela_vm::vm::RegisterFile;
58use rustc_hash::FxHashMap;
59use smol_strc::SmolStr;
60use std::collections::HashMap;
61use std::rc::Rc;
62
63struct RegisteredFn {
67 func: HostDispatch,
68 arg_types: Vec<HostType>,
69 ret_type: HostType,
70 variadic: bool,
75}
76
77#[derive(Default)]
84pub struct Engine {
85 registry: HashMap<(String, String), RegisteredFn>,
86}
87
88impl Engine {
89 #[must_use]
90 pub fn new() -> Self {
91 Self {
92 registry: HashMap::new(),
93 }
94 }
95
96 pub fn register_host_fn<Marker, F>(&mut self, namespace: &str, name: &str, f: F)
104 where
105 F: IntoHostFn<Marker>,
106 {
107 let (func, arg_types, ret_type) = f.into_host_fn_parts();
108 self.registry.insert(
109 (namespace.to_owned(), name.to_owned()),
110 RegisteredFn {
111 func,
112 arg_types,
113 ret_type,
114 variadic: false,
115 },
116 );
117 }
118
119 pub fn register_host_fn_variadic<F>(&mut self, namespace: &str, name: &str, f: F)
138 where
139 F: Fn(&[Value]) -> Value + 'static,
140 {
141 self.registry.insert(
142 (namespace.to_owned(), name.to_owned()),
143 RegisteredFn {
144 func: Rc::new(f),
145 arg_types: Vec::new(),
146 ret_type: HostType::Unit,
147 variadic: true,
148 },
149 );
150 }
151
152 pub fn compile(&self, src: &str, filename: &str) -> Result<Program, Diagnostic> {
165 let filename_owned = filename.to_owned();
166 let out: CompileOutput =
167 collect_diagnostic(|| compile(src.to_owned(), &filename_owned, false))?;
168
169 let mut host_dispatch: Vec<HostDispatch> = Vec::with_capacity(out.host_fns.len());
172 for sig in &out.host_fns {
173 let key = (sig.namespace.to_string(), sig.name.to_string());
174 let registered = self.registry.get(&key).ok_or_else(|| Diagnostic {
175 filename: filename.to_owned(),
176 span: 0..0,
177 message: format!(
178 "no host function registered for `{}.{}` (declared in a `host` block)",
179 sig.namespace, sig.name
180 ),
181 code: String::from("unregistered_host_fn"),
182 })?;
183 validate_host_fn(sig, registered, filename)?;
184 host_dispatch.push(Rc::clone(®istered.func));
185 }
186
187 let mut registers = out.registers;
193 let mut const_registers = out.const_registers;
194 if registers.is_empty() {
195 registers.push(NULL);
196 const_registers.entry(NULL).or_insert(0);
197 }
198
199 let mut program = Program {
200 instructions: out.instructions,
201 registers,
202 pools: out.pools,
203 instr_src: out.instr_src,
204 fn_registers: out.fn_registers,
205 dyn_lib_fns: out.dyn_lib_fns,
206 host_sigs: out.host_fns,
207 host_dispatch,
208 allocated_arg_count: out.allocated_arg_count,
209 allocated_call_depth: out.allocated_call_depth,
210 sources: out.sources,
211 structs: out.structs,
212 enums: out.enums,
213 functions: out.functions,
214 dyn_libs: out.dyn_libs,
215 namespace: out.namespace,
216 const_registers,
217 free_registers: out.free_registers,
218 };
219
220 program.execute_from(0)?;
223 Ok(program)
224 }
225}
226
227fn validate_host_fn(
230 sig: &HostFnSig,
231 registered: &RegisteredFn,
232 filename: &str,
233) -> Result<(), Diagnostic> {
234 let err = |message: String| Diagnostic {
235 filename: filename.to_owned(),
236 span: 0..0,
237 message,
238 code: String::from("host_fn_signature_mismatch"),
239 };
240
241 if sig.variadic || registered.variadic {
245 if sig.variadic != registered.variadic {
246 let (decl, reg) = if sig.variadic {
247 ("variadic (`...`)", "a fixed signature")
248 } else {
249 ("a fixed signature", "variadic")
250 };
251 return Err(err(format!(
252 "host function `{}.{}` is declared with {decl} but the registered closure has {reg}",
253 sig.namespace, sig.name,
254 )));
255 }
256 return Ok(());
257 }
258
259 if sig.arg_count() != registered.arg_types.len() {
260 return Err(err(format!(
261 "host function `{}.{}` is declared with {} argument(s) but the registered closure takes {}",
262 sig.namespace,
263 sig.name,
264 sig.arg_count(),
265 registered.arg_types.len(),
266 )));
267 }
268
269 for (idx, want) in registered.arg_types.iter().enumerate() {
270 let declared = HostType::from_datatype(sig.get_arg(idx)).ok_or_else(|| {
271 err(format!(
272 "host function `{}.{}` argument {} has a type that cannot cross the host boundary",
273 sig.namespace,
274 sig.name,
275 idx + 1,
276 ))
277 })?;
278 if declared != *want {
279 return Err(err(format!(
280 "host function `{}.{}` argument {} is declared `{}` but the registered closure expects `{}`",
281 sig.namespace,
282 sig.name,
283 idx + 1,
284 declared.describe(),
285 want.describe(),
286 )));
287 }
288 }
289
290 let declared_ret = HostType::from_datatype(sig.get_return_type()).ok_or_else(|| {
291 err(format!(
292 "host function `{}.{}` has a return type that cannot cross the host boundary",
293 sig.namespace, sig.name,
294 ))
295 })?;
296 if declared_ret != registered.ret_type {
297 return Err(err(format!(
298 "host function `{}.{}` is declared to return `{}` but the registered closure returns `{}`",
299 sig.namespace,
300 sig.name,
301 declared_ret.describe(),
302 registered.ret_type.describe(),
303 )));
304 }
305
306 Ok(())
307}
308
309pub struct Program {
318 instructions: Vec<Instr>,
320 registers: Vec<Data>,
321 pools: Pools,
322 instr_src: Vec<InstrSrc>,
323 fn_registers: Vec<Vec<u16>>,
324 dyn_lib_fns: Vec<DynamicLibFn>,
325 host_sigs: Vec<HostFnSig>,
326 host_dispatch: Vec<HostDispatch>,
327 allocated_arg_count: usize,
328 allocated_call_depth: usize,
329 sources: Vec<Source>,
330 structs: Vec<Struct>,
331 enums: Vec<EnumType>,
332 functions: Vec<Function>,
334 dyn_libs: Vec<Dynamiclib>,
335 namespace: Namespace,
336 const_registers: FxHashMap<Data, u16>,
337 free_registers: Vec<u16>,
338}
339
340impl Program {
341 pub fn call(&mut self, fn_name: &str, args: &[Value]) -> Result<Value, Diagnostic> {
354 let dummy = Span { start: 0, end: 0 };
358 let mut arg_exprs: Vec<Expr> = Vec::with_capacity(args.len());
359 let mut seed_vars: Vec<Variable> = Vec::new();
360 for (i, v) in args.iter().enumerate() {
361 if let Some(expr) = value_to_expr(v) {
362 arg_exprs.push(expr);
363 } else {
364 let handle = marshal_value(
365 v,
366 &mut self.pools.objs,
367 &mut self.pools.maps,
368 &mut self.pools.strings,
369 );
370 let register_id = self.registers.len() as u16;
371 self.registers.push(handle);
372 let name = SmolStr::from(format!("__host_arg{i}"));
373 seed_vars.push(Variable {
374 name: name.clone(),
375 register_id,
376 var_type: value_datatype(v),
377 });
378 arg_exprs.push(Expr::Var(name, dummy));
379 }
380 }
381
382 let arg_spans: Box<[Span]> = args.iter().map(|_| dummy).collect();
383 let call_expr = Expr::FunctionCall(
384 arg_exprs.into_boxed_slice(),
385 Box::from([SmolStr::from(fn_name)]),
386 dummy,
387 arg_spans,
388 );
389
390 let (mut output, ret_id) =
392 collect_diagnostic(|| self.build_trampoline(&call_expr, seed_vars))?;
393
394 let ret_id = ret_id.unwrap_or(0);
395 output.push(Instr::Halt(0));
396 let start = self.instructions.len();
397 self.instructions.extend(output);
398
399 self.execute_from(start)?;
400
401 Ok(unmarshal_value(
402 self.registers[ret_id as usize],
403 &self.pools.objs,
404 &self.pools.maps,
405 &self.pools.strings,
406 &self.structs,
407 ))
408 }
409
410 fn build_trampoline(
415 &mut self,
416 call_expr: &Expr,
417 seed_vars: Vec<Variable>,
418 ) -> (Vec<Instr>, Option<u16>) {
419 crate::compiler::type_system::reset_inference_state();
423
424 let offset = self.instructions.len() as u16;
425 let ctx = Ctx {
426 block_id: 0,
427 is_compiling_recursive: false,
428 single_run: false,
429 file_idx: 0,
430 offset,
431 };
432 let mut variables = seed_vars;
434 let mut output = Vec::new();
435 let mut state = State {
436 registers: &mut self.registers,
437 fns: &mut self.functions,
438 structs: &mut self.structs,
439 enums: &mut self.enums,
440 pools: &mut self.pools,
441 instr_src: &mut self.instr_src,
442 fn_registers: &mut self.fn_registers,
443 dyn_libs: &mut self.dyn_libs,
444 allocated_arg_count: &mut self.allocated_arg_count,
445 allocated_call_depth: &mut self.allocated_call_depth,
446 const_registers: &mut self.const_registers,
447 free_registers: &mut self.free_registers,
448 sources: &mut self.sources,
449 reserved_registers: rustc_hash::FxHashSet::default(),
450 namespace: &mut self.namespace,
451 };
452 let ret = call_expr.compile(
453 &mut variables,
454 ctx,
455 &mut state,
456 &mut output,
457 None,
458 false,
459 true,
460 );
461 (output, ret)
462 }
463
464 fn execute_from(&mut self, start: usize) -> Result<(), Diagnostic> {
467 let err_ctx = ErrorCtx {
468 instr_src: self.instr_src.clone(),
469 sources: self
470 .sources
471 .iter()
472 .map(|s| Source {
473 filename: s.filename.clone(),
474 contents: s.contents.clone(),
475 })
476 .collect(),
477 };
478
479 let mut register_file = RegisterFile(std::mem::take(&mut self.registers));
482
483 let instructions = &self.instructions;
484 let pools = &mut self.pools;
485 let fn_registers = &self.fn_registers;
486 let dyn_lib_fns = &self.dyn_lib_fns;
487 let structs = &self.structs;
488 let enums = &self.enums;
489 let host_sigs = &self.host_sigs;
490 let host_dispatch = &self.host_dispatch;
491 let allocated_arg_count = self.allocated_arg_count;
492 let allocated_call_depth = self.allocated_call_depth;
493
494 let result = collect_diagnostic(|| {
495 vm::execute(
496 instructions,
497 &mut register_file,
498 pools,
499 &err_ctx,
500 fn_registers,
501 dyn_lib_fns,
502 structs,
503 enums,
504 allocated_arg_count,
505 allocated_call_depth,
506 host_sigs,
507 host_dispatch,
508 start,
509 );
510 });
511
512 self.registers = std::mem::take(&mut register_file.0);
513 result
514 }
515}
516
517fn value_to_expr(v: &Value) -> Option<Expr> {
523 Some(match v {
524 Value::Null => Expr::Null,
525 Value::Int(i) => Expr::Int(*i as i32),
526 Value::Float(f) => Expr::Float(*f),
527 Value::Bool(b) => Expr::Bool(*b),
528 Value::String(s) => Expr::String(SmolStr::from(s.as_str())),
529 Value::Array(_) | Value::Map(_) => return None,
530 })
531}
532
533fn value_datatype(v: &Value) -> DataType {
538 match v {
539 Value::Null => DataType::Null,
540 Value::Int(_) => DataType::Int,
541 Value::Float(_) => DataType::Float,
542 Value::Bool(_) => DataType::Bool,
543 Value::String(_) => DataType::String,
544 Value::Array(items) => DataType::Array(items.first().map(|e| Box::new(value_datatype(e)))),
545 Value::Map(entries) => {
546 let value = entries.values().next().map(value_datatype);
547 DataType::Map(Box::new((Some(DataType::String), value)))
548 }
549 }
550}