1use anyhow::{anyhow, Context, Result};
17use cranelift::codegen::ir::{types, AbiParam, InstBuilder, MemFlags, Signature, Value};
18use cranelift::codegen::isa::TargetIsa;
19use cranelift::codegen::settings::{self, Configurable};
20use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
21use cranelift::prelude::{EntityRef, FloatCC, IntCC, TrapCode};
22use cranelift_jit::{JITBuilder, JITModule};
23use cranelift_module::{DataDescription, DataId, FuncId, Linkage, Module as ClModule};
24use cranelift_object::{ObjectBuilder, ObjectModule};
25use nova_mir::{MirTy, Module as MirModule, OperandClass, RtFunc, Stmt, Terminator};
26use rustc_hash::FxHashMap;
27use std::sync::Arc;
28
29pub const NOVA_ENTRY_SYMBOL: &str = "nova_main";
32
33pub use nova_mir::mangle;
35
36#[derive(Debug)]
40pub struct UnresolvedExternSymbol(pub String);
41
42impl std::fmt::Display for UnresolvedExternSymbol {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 write!(f, "cannot resolve external symbol `{}`", self.0)
45 }
46}
47
48impl std::error::Error for UnresolvedExternSymbol {}
49
50pub struct CompiledProgram {
52 _module: JITModule,
54 main: *const u8,
55}
56
57impl CompiledProgram {
58 pub fn run(&self) {
60 let entry: extern "C" fn() = unsafe { std::mem::transmute(self.main) };
63 entry();
64 }
65
66 pub fn run_with_args(&self, args: Vec<String>) {
70 let _ = nova_runtime::process::set_args(args);
71 self.run();
72 }
73}
74
75pub fn compile_jit(mir: &MirModule) -> Result<CompiledProgram> {
80 let isa = native_isa(false)?;
81 let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
82 for (name, ptr) in nova_runtime::symbols() {
83 builder.symbol(name, ptr);
84 }
85 let mut module = JITModule::new(builder);
86
87 let functions = {
88 let mut cg = Codegen::new(&mut module);
89 cg.declare_runtime()?;
90 cg.declare_externs(mir)?;
91 cg.declare_functions(mir, None)?;
92 cg.define_functions(mir)?;
93 cg.functions
94 };
95
96 let prev_hook = std::panic::take_hook();
103 std::panic::set_hook(Box::new(|_| {}));
104 let finalize = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
107 module.finalize_definitions().map_err(|e| e.to_string())
108 }));
109 std::panic::set_hook(prev_hook);
110 match finalize {
111 Ok(Ok(())) => {}
112 Ok(Err(msg)) => return Err(anyhow!("failed to finalize JIT code: {msg}")),
113 Err(payload) => {
114 let detail = payload
115 .downcast_ref::<String>()
116 .map(String::as_str)
117 .or_else(|| payload.downcast_ref::<&str>().copied())
118 .unwrap_or("unknown error");
119 let symbol = detail
122 .strip_prefix("can't resolve symbol ")
123 .unwrap_or(detail)
124 .trim()
125 .to_string();
126 return Err(anyhow::Error::new(UnresolvedExternSymbol(symbol)));
127 }
128 }
129
130 let main_id = *functions
131 .get("main")
132 .ok_or_else(|| anyhow!("no `main` function in MIR module"))?;
133 let main = module.get_finalized_function(main_id);
134
135 Ok(CompiledProgram {
136 _module: module,
137 main,
138 })
139}
140
141pub fn compile_object(mir: &MirModule) -> Result<Vec<u8>> {
147 let isa = native_isa(!cfg!(windows))?;
149 let obj_builder = ObjectBuilder::new(isa, "nova", cranelift_module::default_libcall_names())
150 .context("creating object builder")?;
151 let mut module = ObjectModule::new(obj_builder);
152
153 {
154 let mut cg = Codegen::new(&mut module);
155 cg.declare_runtime()?;
156 cg.declare_externs(mir)?;
157 cg.declare_functions(mir, Some(NOVA_ENTRY_SYMBOL))?;
158 cg.define_functions(mir)?;
159 cg.emit_c_main()?;
160 }
161
162 let product = module.finish();
163 product.emit().context("emitting object file")
164}
165
166fn native_isa(pic: bool) -> Result<Arc<dyn TargetIsa>> {
167 let mut flags = settings::builder();
168 flags
169 .set("use_colocated_libcalls", "false")
170 .context("setting cranelift flags")?;
171 flags
172 .set("is_pic", if pic { "true" } else { "false" })
173 .context("setting cranelift flags")?;
174 let isa_builder = cranelift_native::builder()
175 .map_err(|e| anyhow!("host machine is not supported by cranelift: {e}"))?;
176 isa_builder
177 .finish(settings::Flags::new(flags))
178 .context("building native ISA")
179}
180
181struct Codegen<'m, M: ClModule> {
184 module: &'m mut M,
185 functions: FxHashMap<String, FuncId>,
186 runtime: FxHashMap<&'static str, FuncId>,
187 strings: FxHashMap<String, DataId>,
188}
189
190impl<'m, M: ClModule> Codegen<'m, M> {
191 fn new(module: &'m mut M) -> Self {
192 Self {
193 module,
194 functions: FxHashMap::default(),
195 runtime: FxHashMap::default(),
196 strings: FxHashMap::default(),
197 }
198 }
199
200 fn ptr_ty(&self) -> types::Type {
201 self.module.target_config().pointer_type()
202 }
203
204 fn cl_ty(&self, ty: MirTy) -> Option<types::Type> {
205 match ty {
206 MirTy::I64 => Some(types::I64),
207 MirTy::F64 => Some(types::F64),
208 MirTy::I8 => Some(types::I8),
209 MirTy::Ptr => Some(self.ptr_ty()),
210 MirTy::Unit => None,
211 }
212 }
213
214 fn abi_param_count(f: &nova_mir::Function) -> usize {
217 f.params as usize + if f.takes_env { 1 } else { 0 }
218 }
219
220 fn make_signature(&self, params: &[MirTy], ret: MirTy) -> Signature {
221 let mut sig = self.module.make_signature();
222 for p in params {
223 if let Some(t) = self.cl_ty(*p) {
224 sig.params.push(AbiParam::new(t));
225 }
226 }
227 if let Some(t) = self.cl_ty(ret) {
228 sig.returns.push(AbiParam::new(t));
229 }
230 sig
231 }
232
233 fn declare_runtime(&mut self) -> Result<()> {
234 for rt in RtFunc::ALL {
235 let (params, ret) = rt.signature();
236 let sig = self.make_signature(¶ms, ret);
237 let id = self
238 .module
239 .declare_function(rt.symbol(), Linkage::Import, &sig)
240 .with_context(|| format!("declaring runtime fn {}", rt.symbol()))?;
241 self.runtime.insert(rt.symbol(), id);
242 }
243 let ptr = self.ptr_ty();
245 let mut sig = self.module.make_signature();
246 sig.params.push(AbiParam::new(ptr));
247 sig.params.push(AbiParam::new(types::I64));
248 sig.returns.push(AbiParam::new(ptr));
249 let id = self
250 .module
251 .declare_function("nova_rt_str_new", Linkage::Import, &sig)
252 .context("declaring nova_rt_str_new")?;
253 self.runtime.insert("nova_rt_str_new", id);
254 Ok(())
255 }
256
257 fn declare_externs(&mut self, mir: &MirModule) -> Result<()> {
262 for ext in &mir.externs {
263 let sig = self.make_signature(&ext.params, ext.ret);
264 let id = self
265 .module
266 .declare_function(&ext.symbol, Linkage::Import, &sig)
267 .with_context(|| format!("declaring extern `{}`", ext.symbol))?;
268 self.functions.insert(ext.symbol.clone(), id);
269 }
270 Ok(())
271 }
272
273 fn declare_functions(&mut self, mir: &MirModule, rename_main: Option<&str>) -> Result<()> {
278 for f in &mir.functions {
279 let params: Vec<MirTy> = f.temps[..Self::abi_param_count(f)].to_vec();
280 let sig = self.make_signature(¶ms, f.ret);
281 let symbol = match rename_main {
282 Some(entry) if f.name == "main" => entry,
283 _ => f.name.as_str(),
284 };
285 let id = self
286 .module
287 .declare_function(symbol, Linkage::Local, &sig)
288 .with_context(|| format!("declaring `{}`", f.name))?;
289 self.functions.insert(f.name.clone(), id);
290 }
291 Ok(())
292 }
293
294 fn emit_c_main(&mut self) -> Result<()> {
297 let ptr = self.ptr_ty();
298 let mut sig = self.module.make_signature();
299 sig.params.push(AbiParam::new(types::I32));
300 sig.params.push(AbiParam::new(ptr));
301 sig.returns.push(AbiParam::new(types::I32));
302 let main_id = self
303 .module
304 .declare_function("main", Linkage::Export, &sig)
305 .context("declaring C main wrapper")?;
306
307 let nova_main = *self
308 .functions
309 .get("main")
310 .ok_or_else(|| anyhow!("no `main` function in MIR module"))?;
311
312 let mut ctx = self.module.make_context();
313 ctx.func.signature = sig;
314 let mut fb_ctx = FunctionBuilderContext::new();
315 let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fb_ctx);
316 let block = builder.create_block();
317 builder.append_block_params_for_function_params(block);
318 builder.switch_to_block(block);
319 let func_ref = self.module.declare_func_in_func(nova_main, builder.func);
320 builder.ins().call(func_ref, &[]);
321 let zero = builder.ins().iconst(types::I32, 0);
322 builder.ins().return_(&[zero]);
323 builder.seal_all_blocks();
324 builder.finalize();
325
326 self.module
327 .define_function(main_id, &mut ctx)
328 .context("defining C main wrapper")?;
329 self.module.clear_context(&mut ctx);
330 Ok(())
331 }
332
333 fn string_data(&mut self, s: &str) -> Result<DataId> {
334 if let Some(id) = self.strings.get(s) {
335 return Ok(*id);
336 }
337 let name = format!("str{}", self.strings.len());
338 let id = self
339 .module
340 .declare_data(&name, Linkage::Local, false, false)
341 .context("declaring string data")?;
342 let mut desc = DataDescription::new();
343 let bytes: Box<[u8]> = if s.is_empty() {
345 Box::new([0u8])
346 } else {
347 s.as_bytes().into()
348 };
349 desc.define(bytes);
350 self.module
351 .define_data(id, &desc)
352 .context("defining string data")?;
353 self.strings.insert(s.to_string(), id);
354 Ok(id)
355 }
356
357 fn define_functions(&mut self, mir: &MirModule) -> Result<()> {
358 let mut fb_ctx = FunctionBuilderContext::new();
359 for f in &mir.functions {
360 self.define_function(f, &mut fb_ctx)
361 .with_context(|| format!("compiling `{}`", f.name))?;
362 }
363 Ok(())
364 }
365
366 fn define_function(
367 &mut self,
368 f: &nova_mir::Function,
369 fb_ctx: &mut FunctionBuilderContext,
370 ) -> Result<()> {
371 let func_id = self.functions[&f.name];
372 let abi_count = Self::abi_param_count(f);
373 let params: Vec<MirTy> = f.temps[..abi_count].to_vec();
374 let sig = self.make_signature(¶ms, f.ret);
375
376 let mut ctx = self.module.make_context();
377 ctx.func.signature = sig;
378
379 let mut builder = FunctionBuilder::new(&mut ctx.func, fb_ctx);
381
382 let mut vars: Vec<Option<Variable>> = Vec::with_capacity(f.temps.len());
384 for (i, ty) in f.temps.iter().enumerate() {
385 match self.cl_ty(*ty) {
386 Some(t) => {
387 let var = Variable::new(i);
388 builder.declare_var(var, t);
389 vars.push(Some(var));
390 }
391 None => vars.push(None),
392 }
393 }
394
395 let cl_blocks: Vec<_> = f.blocks.iter().map(|_| builder.create_block()).collect();
397 let entry = cl_blocks[0];
398 builder.append_block_params_for_function_params(entry);
399 builder.switch_to_block(entry);
400
401 let entry_params: Vec<Value> = builder.block_params(entry).to_vec();
404 let param_vars = vars.iter().take(abi_count).copied().flatten();
405 for (var, value) in param_vars.zip(entry_params) {
406 builder.def_var(var, value);
407 }
408
409 let mut tr = Translator {
410 cg: self,
411 builder,
412 vars,
413 cl_blocks,
414 };
415 for (i, block) in f.blocks.iter().enumerate() {
416 if i != 0 {
417 tr.builder.switch_to_block(tr.cl_blocks[i]);
418 }
419 for stmt in &block.stmts {
420 tr.stmt(stmt)?;
421 }
422 tr.terminator(&block.term, f.ret)?;
423 }
424 tr.builder.seal_all_blocks();
425 tr.builder.finalize();
426
427 self.module
428 .define_function(func_id, &mut ctx)
429 .with_context(|| format!("defining `{}`", f.name))?;
430 self.module.clear_context(&mut ctx);
431 Ok(())
432 }
433}
434
435struct Translator<'a, 'm, M: ClModule> {
437 cg: &'a mut Codegen<'m, M>,
438 builder: FunctionBuilder<'a>,
439 vars: Vec<Option<Variable>>,
440 cl_blocks: Vec<cranelift::codegen::ir::Block>,
441}
442
443impl<'a, 'm, M: ClModule> Translator<'a, 'm, M> {
444 fn use_temp(&mut self, t: nova_mir::Temp) -> Result<Value> {
445 let var = self.vars[t.0 as usize]
446 .ok_or_else(|| anyhow!("attempted to read a unit temp %{}", t.0))?;
447 Ok(self.builder.use_var(var))
448 }
449
450 fn def_temp(&mut self, t: nova_mir::Temp, v: Value) {
451 if let Some(var) = self.vars[t.0 as usize] {
452 self.builder.def_var(var, v);
453 }
454 }
455
456 fn array_elem_addr(&mut self, arr: nova_mir::Temp, index: nova_mir::Temp) -> Result<Value> {
459 let base = self.use_temp(arr)?;
460 let idx = self.use_temp(index)?;
461 let byte_off = self.builder.ins().imul_imm(idx, 8);
462 let elem = self.builder.ins().iadd(base, byte_off);
463 Ok(self.builder.ins().iadd_imm(elem, 8))
464 }
465
466 fn call_func_id(&mut self, id: FuncId, args: &[Value]) -> Result<Option<Value>> {
467 let func_ref = self.cg.module.declare_func_in_func(id, self.builder.func);
468 let inst = self.builder.ins().call(func_ref, args);
469 let results = self.builder.inst_results(inst);
470 Ok(results.first().copied())
471 }
472
473 fn rt(&self, name: &'static str) -> FuncId {
474 self.cg.runtime[name]
475 }
476
477 fn stmt(&mut self, stmt: &Stmt) -> Result<()> {
478 match stmt {
479 Stmt::ConstInt(t, v) => {
480 let val = self.builder.ins().iconst(types::I64, *v);
481 self.def_temp(*t, val);
482 }
483 Stmt::ConstFloat(t, v) => {
484 let val = self.builder.ins().f64const(*v);
485 self.def_temp(*t, val);
486 }
487 Stmt::ConstBool(t, v) => {
488 let val = self.builder.ins().iconst(types::I8, *v as i64);
489 self.def_temp(*t, val);
490 }
491 Stmt::ConstUnit(_) => {}
492 Stmt::ConstStr(t, s) => {
493 let data_id = self.cg.string_data(s)?;
494 let gv = self
495 .cg
496 .module
497 .declare_data_in_func(data_id, self.builder.func);
498 let ptr_ty = self.cg.ptr_ty();
499 let ptr = self.builder.ins().global_value(ptr_ty, gv);
500 let len = self.builder.ins().iconst(types::I64, s.len() as i64);
501 let id = self.rt("nova_rt_str_new");
502 let result = self
503 .call_func_id(id, &[ptr, len])?
504 .ok_or_else(|| anyhow!("str_new returns a value"))?;
505 self.def_temp(*t, result);
506 }
507 Stmt::Copy { dst, src } => {
508 let v = self.use_temp(*src)?;
509 self.def_temp(*dst, v);
510 }
511 Stmt::Bin {
512 dst,
513 op,
514 class,
515 lhs,
516 rhs,
517 } => {
518 let l = self.use_temp(*lhs)?;
519 let r = self.use_temp(*rhs)?;
520 let v = self.binop(*op, *class, l, r)?;
521 self.def_temp(*dst, v);
522 }
523 Stmt::Neg { dst, class, src } => {
524 let v = self.use_temp(*src)?;
525 let out = match class {
526 OperandClass::Float => self.builder.ins().fneg(v),
527 _ => self.builder.ins().ineg(v),
528 };
529 self.def_temp(*dst, out);
530 }
531 Stmt::Not { dst, src } => {
532 let v = self.use_temp(*src)?;
533 let out = self.builder.ins().bxor_imm(v, 1);
534 self.def_temp(*dst, out);
535 }
536 Stmt::BitNot { dst, src } => {
537 let v = self.use_temp(*src)?;
538 let out = self.builder.ins().bnot(v);
539 self.def_temp(*dst, out);
540 }
541 Stmt::Call { dst, callee, args } => {
542 let id = *self
543 .cg
544 .functions
545 .get(callee)
546 .ok_or_else(|| anyhow!("call to undeclared function `{callee}`"))?;
547 let arg_vals = self.arg_values(args)?;
548 let result = self.call_func_id(id, &arg_vals)?;
549 if let (Some(dst), Some(v)) = (dst, result) {
550 self.def_temp(*dst, v);
551 }
552 }
553 Stmt::Await { .. } => {
561 return Err(anyhow!(
562 "an `.await` marker reached codegen: `nova-mir`'s async \
563 state-machine transform must split every `async fn` body \
564 at its await points before a module is emitted"
565 ));
566 }
567 Stmt::CallRuntime { dst, func, args } => {
568 let id = self.rt(func.symbol());
569 let arg_vals = self.arg_values(args)?;
570 let result = self.call_func_id(id, &arg_vals)?;
571 if let (Some(dst), Some(v)) = (dst, result) {
572 self.def_temp(*dst, v);
573 }
574 }
575 Stmt::CallIndirect {
576 dst,
577 callee,
578 params,
579 ret,
580 args,
581 } => {
582 let ptr_ty = self.cg.ptr_ty();
585 let fat = self.use_temp(*callee)?;
586 let code = self.builder.ins().load(ptr_ty, MemFlags::trusted(), fat, 0);
587 let env = self.builder.ins().load(ptr_ty, MemFlags::trusted(), fat, 8);
588 let mut sig_params = vec![MirTy::Ptr];
590 sig_params.extend_from_slice(params);
591 let sig = self.cg.make_signature(&sig_params, *ret);
592 let sig_ref = self.builder.import_signature(sig);
593 let mut arg_vals = vec![env];
594 arg_vals.extend(self.arg_values(args)?);
595 let inst = self.builder.ins().call_indirect(sig_ref, code, &arg_vals);
596 let result = self.builder.inst_results(inst).first().copied();
597 if let (Some(dst), Some(v)) = (dst, result) {
598 self.def_temp(*dst, v);
599 }
600 }
601 Stmt::MakeClosure {
602 dst,
603 code,
604 captures,
605 } => {
606 let ptr_ty = self.cg.ptr_ty();
607 let env = if captures.is_empty() {
609 self.builder.ins().iconst(ptr_ty, 0)
610 } else {
611 let size = (8 * captures.len()) as i64;
612 let size_val = self.builder.ins().iconst(types::I64, size);
613 let alloc = self.rt("nova_rt_alloc");
614 let env = self
615 .call_func_id(alloc, &[size_val])?
616 .ok_or_else(|| anyhow!("alloc returns a value"))?;
617 for (i, (cap, ty)) in captures.iter().enumerate() {
618 if *ty == MirTy::Unit {
619 continue;
620 }
621 let v = self.use_temp(*cap)?;
622 self.builder
623 .ins()
624 .store(MemFlags::trusted(), v, env, (8 * i) as i32);
625 }
626 env
627 };
628 let size_val = self.builder.ins().iconst(types::I64, 16);
630 let alloc = self.rt("nova_rt_alloc");
631 let fat = self
632 .call_func_id(alloc, &[size_val])?
633 .ok_or_else(|| anyhow!("alloc returns a value"))?;
634 let id = *self
635 .cg
636 .functions
637 .get(code)
638 .ok_or_else(|| anyhow!("closure code `{code}` is undeclared"))?;
639 let func_ref = self.cg.module.declare_func_in_func(id, self.builder.func);
640 let code_ptr = self.builder.ins().func_addr(ptr_ty, func_ref);
641 self.builder
642 .ins()
643 .store(MemFlags::trusted(), code_ptr, fat, 0);
644 self.builder.ins().store(MemFlags::trusted(), env, fat, 8);
645 self.def_temp(*dst, fat);
646 }
647 Stmt::MakeSum { dst, tag, fields } => {
648 let size = 8 + 8 * fields.len() as i64;
649 let size_val = self.builder.ins().iconst(types::I64, size);
650 let id = self.rt("nova_rt_alloc");
651 let ptr = self
652 .call_func_id(id, &[size_val])?
653 .ok_or_else(|| anyhow!("alloc returns a value"))?;
654 let tag_val = self.builder.ins().iconst(types::I64, *tag as i64);
655 self.builder
656 .ins()
657 .store(MemFlags::trusted(), tag_val, ptr, 0);
658 for (i, (field, ty)) in fields.iter().enumerate() {
659 if *ty == MirTy::Unit {
660 continue;
661 }
662 let v = self.use_temp(*field)?;
663 let offset = (8 + 8 * i) as i32;
664 self.builder
665 .ins()
666 .store(MemFlags::trusted(), v, ptr, offset);
667 }
668 self.def_temp(*dst, ptr);
669 }
670 Stmt::MakeRecord { dst, fields } => {
671 let size = (8 * fields.len().max(1)) as i64;
673 let size_val = self.builder.ins().iconst(types::I64, size);
674 let id = self.rt("nova_rt_alloc");
675 let ptr = self
676 .call_func_id(id, &[size_val])?
677 .ok_or_else(|| anyhow!("alloc returns a value"))?;
678 for (i, (field, ty)) in fields.iter().enumerate() {
679 if *ty == MirTy::Unit {
680 continue;
681 }
682 let v = self.use_temp(*field)?;
683 let offset = (8 * i) as i32;
684 self.builder
685 .ins()
686 .store(MemFlags::trusted(), v, ptr, offset);
687 }
688 self.def_temp(*dst, ptr);
689 }
690 Stmt::RecordField {
691 dst,
692 record,
693 index,
694 ty,
695 } => {
696 let Some(cl_ty) = self.cg.cl_ty(*ty) else {
697 return Ok(());
698 };
699 let ptr = self.use_temp(*record)?;
700 let offset = (8 * index) as i32;
701 let v = self
702 .builder
703 .ins()
704 .load(cl_ty, MemFlags::trusted(), ptr, offset);
705 self.def_temp(*dst, v);
706 }
707 Stmt::SetField {
708 record,
709 index,
710 value,
711 ty,
712 } => {
713 if self.cg.cl_ty(*ty).is_none() {
715 return Ok(());
716 }
717 let ptr = self.use_temp(*record)?;
718 let v = self.use_temp(*value)?;
719 let offset = (8 * index) as i32;
721 self.builder
722 .ins()
723 .store(MemFlags::trusted(), v, ptr, offset);
724 }
725 Stmt::MakeArray { dst, elems } => {
726 let size = (8 + 8 * elems.len()) as i64;
728 let size_val = self.builder.ins().iconst(types::I64, size);
729 let alloc = self.rt("nova_rt_alloc");
730 let ptr = self
731 .call_func_id(alloc, &[size_val])?
732 .ok_or_else(|| anyhow!("alloc returns a value"))?;
733 let len_val = self.builder.ins().iconst(types::I64, elems.len() as i64);
734 self.builder
735 .ins()
736 .store(MemFlags::trusted(), len_val, ptr, 0);
737 for (i, (el, ty)) in elems.iter().enumerate() {
738 if *ty == MirTy::Unit {
739 continue;
740 }
741 let v = self.use_temp(*el)?;
742 let offset = (8 + 8 * i) as i32;
743 self.builder
744 .ins()
745 .store(MemFlags::trusted(), v, ptr, offset);
746 }
747 self.def_temp(*dst, ptr);
748 }
749 Stmt::ArrayAlloc { dst, len } => {
750 let n = self.use_temp(*len)?;
756 let eight = self.builder.ins().iconst(types::I64, 8);
757 let bytes = self.builder.ins().imul(n, eight);
758 let size = self.builder.ins().iadd(bytes, eight);
759 let alloc = self.rt("nova_rt_alloc");
760 let ptr = self
761 .call_func_id(alloc, &[size])?
762 .ok_or_else(|| anyhow!("alloc returns a value"))?;
763 self.builder.ins().store(MemFlags::trusted(), n, ptr, 0);
764 self.def_temp(*dst, ptr);
765 }
766 Stmt::ArrayLen { dst, arr } => {
767 let ptr = self.use_temp(*arr)?;
768 let len = self
769 .builder
770 .ins()
771 .load(types::I64, MemFlags::trusted(), ptr, 0);
772 self.def_temp(*dst, len);
773 }
774 Stmt::ArrayGet {
775 dst,
776 arr,
777 index,
778 ty,
779 } => {
780 let Some(cl_ty) = self.cg.cl_ty(*ty) else {
781 return Ok(());
782 };
783 let addr = self.array_elem_addr(*arr, *index)?;
784 let v = self.builder.ins().load(cl_ty, MemFlags::trusted(), addr, 0);
785 self.def_temp(*dst, v);
786 }
787 Stmt::ArraySet {
788 arr,
789 index,
790 value,
791 ty,
792 } => {
793 if self.cg.cl_ty(*ty).is_none() {
794 return Ok(());
795 }
796 let addr = self.array_elem_addr(*arr, *index)?;
797 let v = self.use_temp(*value)?;
798 self.builder.ins().store(MemFlags::trusted(), v, addr, 0);
799 }
800 Stmt::SumTag { dst, sum } => {
801 let ptr = self.use_temp(*sum)?;
802 let tag = self
803 .builder
804 .ins()
805 .load(types::I64, MemFlags::trusted(), ptr, 0);
806 self.def_temp(*dst, tag);
807 }
808 Stmt::SumField {
809 dst,
810 sum,
811 index,
812 ty,
813 } => {
814 let Some(cl_ty) = self.cg.cl_ty(*ty) else {
815 return Ok(());
816 };
817 let ptr = self.use_temp(*sum)?;
818 let offset = (8 + 8 * index) as i32;
819 let v = self
820 .builder
821 .ins()
822 .load(cl_ty, MemFlags::trusted(), ptr, offset);
823 self.def_temp(*dst, v);
824 }
825 }
826 Ok(())
827 }
828
829 fn arg_values(&mut self, args: &[nova_mir::Temp]) -> Result<Vec<Value>> {
831 let mut vals = Vec::with_capacity(args.len());
832 for a in args {
833 if self.vars[a.0 as usize].is_some() {
834 vals.push(self.use_temp(*a)?);
835 }
836 }
837 Ok(vals)
838 }
839
840 fn binop(
841 &mut self,
842 op: nova_hir::BinOp,
843 class: OperandClass,
844 l: Value,
845 r: Value,
846 ) -> Result<Value> {
847 use nova_hir::BinOp::*;
848 let ins = self.builder.ins();
849 let v = match (class, op) {
850 (OperandClass::Float, Add) => ins.fadd(l, r),
851 (OperandClass::Float, Sub) => ins.fsub(l, r),
852 (OperandClass::Float, Mul) => ins.fmul(l, r),
853 (OperandClass::Float, Div) => ins.fdiv(l, r),
854 (OperandClass::Float, Rem) => {
855 return Err(anyhow!(
856 "float remainder (%) is not supported yet by the Cranelift backend"
857 ))
858 }
859 (OperandClass::Float, Eq) => ins.fcmp(FloatCC::Equal, l, r),
860 (OperandClass::Float, Ne) => ins.fcmp(FloatCC::NotEqual, l, r),
861 (OperandClass::Float, Lt) => ins.fcmp(FloatCC::LessThan, l, r),
862 (OperandClass::Float, Le) => ins.fcmp(FloatCC::LessThanOrEqual, l, r),
863 (OperandClass::Float, Gt) => ins.fcmp(FloatCC::GreaterThan, l, r),
864 (OperandClass::Float, Ge) => ins.fcmp(FloatCC::GreaterThanOrEqual, l, r),
865 (OperandClass::Float, _) => {
866 return Err(anyhow!("bitwise operators are not defined for Float"))
867 }
868 (_, Add) => ins.iadd(l, r),
869 (_, Sub) => ins.isub(l, r),
870 (_, Mul) => ins.imul(l, r),
871 (_, Div) => ins.sdiv(l, r),
872 (_, Rem) => ins.srem(l, r),
873 (_, Eq) => ins.icmp(IntCC::Equal, l, r),
874 (_, Ne) => ins.icmp(IntCC::NotEqual, l, r),
875 (_, Lt) => ins.icmp(IntCC::SignedLessThan, l, r),
876 (_, Le) => ins.icmp(IntCC::SignedLessThanOrEqual, l, r),
877 (_, Gt) => ins.icmp(IntCC::SignedGreaterThan, l, r),
878 (_, Ge) => ins.icmp(IntCC::SignedGreaterThanOrEqual, l, r),
879 (_, BitAnd) => ins.band(l, r),
880 (_, BitOr) => ins.bor(l, r),
881 (_, BitXor) => ins.bxor(l, r),
882 (_, Shl) => ins.ishl(l, r),
883 (_, Shr) => ins.sshr(l, r),
884 };
885 Ok(v)
886 }
887
888 fn terminator(&mut self, term: &Terminator, ret: MirTy) -> Result<()> {
889 match term {
890 Terminator::Goto(b) => {
891 let target = self.cl_blocks[b.0 as usize];
892 self.builder.ins().jump(target, &[]);
893 }
894 Terminator::Branch { cond, then_, else_ } => {
895 let c = self.use_temp(*cond)?;
896 let t = self.cl_blocks[then_.0 as usize];
897 let e = self.cl_blocks[else_.0 as usize];
898 self.builder.ins().brif(c, t, &[], e, &[]);
899 }
900 Terminator::Switch {
901 disc,
902 arms,
903 default,
904 } => {
905 let mut switch = Switch::new();
906 for (value, block) in arms {
907 switch.set_entry(*value as u128, self.cl_blocks[block.0 as usize]);
908 }
909 let d = self.use_temp(*disc)?;
910 let default_block = self.cl_blocks[default.0 as usize];
911 switch.emit(&mut self.builder, d, default_block);
912 }
913 Terminator::Return(value) => match (value, ret) {
914 (Some(v), r) if r != MirTy::Unit => {
915 let val = self.use_temp(*v)?;
916 self.builder.ins().return_(&[val]);
917 }
918 _ => {
919 self.builder.ins().return_(&[]);
920 }
921 },
922 Terminator::Trap => {
923 self.builder.ins().trap(TrapCode::UnreachableCodeReached);
924 }
925 }
926 Ok(())
927 }
928}
929
930pub use nova_mir::lower_module;
932
933#[cfg(test)]
934mod tests {
935 use nova_mir::RtFunc;
940
941 #[test]
966 fn every_rt_func_symbol_is_registered_with_the_jit() {
967 let registered: Vec<&str> = nova_runtime::symbols()
968 .into_iter()
969 .map(|(name, _)| name)
970 .collect();
971 let missing: Vec<&str> = RtFunc::ALL
972 .iter()
973 .map(|f| f.symbol())
974 .filter(|s| !registered.contains(s))
975 .collect();
976 assert!(
977 missing.is_empty(),
978 "RtFunc variants with no nova_runtime::symbols() entry: {missing:?}. \
979 Compiled code calling one of these makes cranelift-jit panic in \
980 finalize_definitions; add it to symbols() in nova-runtime/src/lib.rs."
981 );
982 }
983
984 #[test]
1010 fn the_state_layout_matches_nova_runtimes() {
1011 use nova_runtime::task;
1012 assert_eq!(
1013 nova_mir::STATE_SLOT_TAG as usize,
1014 task::STATE_SLOT_TAG,
1015 "resume tag slot"
1016 );
1017 assert_eq!(
1018 nova_mir::STATE_SLOT_OUTPUT as usize,
1019 task::STATE_SLOT_OUTPUT,
1020 "output slot -- read unconditionally on completion"
1021 );
1022 assert_eq!(
1023 nova_mir::STATE_SLOT_TEMPS as usize,
1024 task::STATE_SLOT_TEMPS,
1025 "first per-temp slot"
1026 );
1027 assert_eq!(
1028 nova_mir::STATE_MIN_SIZE as usize,
1029 task::STATE_MIN_SIZE,
1030 "smallest state object -- a shortfall here is a read past the \
1031 allocation, and it compiles clean"
1032 );
1033 assert_eq!(
1034 nova_mir::FUTURE_SLOT_POLL as usize,
1035 task::FUTURE_SLOT_POLL,
1036 "future word 0: the poll function's address"
1037 );
1038 assert_eq!(
1039 nova_mir::FUTURE_SLOT_STATE as usize,
1040 task::FUTURE_SLOT_STATE,
1041 "future word 1: the state object's address"
1042 );
1043 assert_eq!(
1044 nova_mir::POLL_PENDING,
1045 task::POLL_PENDING,
1046 "the executor re-queues on exactly this value"
1047 );
1048 assert_eq!(
1049 nova_mir::POLL_READY,
1050 task::POLL_READY,
1051 "the executor completes a task on exactly this value and panics on \
1052 anything else"
1053 );
1054 }
1055
1056 #[test]
1064 fn registered_runtime_symbols_are_distinct() {
1065 let mut names: Vec<&str> = nova_runtime::symbols()
1066 .into_iter()
1067 .map(|(name, _)| name)
1068 .collect();
1069 let before = names.len();
1070 names.sort_unstable();
1071 names.dedup();
1072 assert_eq!(
1073 names.len(),
1074 before,
1075 "nova_runtime::symbols() has a duplicate entry"
1076 );
1077 }
1078}