1use anyhow::{bail, Result};
23use nova_hir::BinOp;
24use nova_mir::{Function, MirTy, Module, OperandClass, RtFunc, Stmt, Temp, Terminator};
25use std::collections::HashMap;
26use std::fmt::Write as _;
27
28pub const NOVA_ENTRY_SYMBOL: &str = "nova_main";
31
32const TRAP_DECL: &str = "declare void @llvm.trap()";
35
36const STR_NEW_DECL: &str = "declare ptr @nova_rt_str_new(ptr, i64)";
40
41fn rt_func_decl(rt: RtFunc) -> String {
54 let (params, ret) = rt.signature();
55 let params: Vec<&str> = params.iter().map(|&p| llty(p)).collect();
56 format!(
57 "declare {} @{}({})",
58 llty(ret),
59 rt.symbol(),
60 params.join(", ")
61 )
62}
63
64pub fn compile_ir(mir: &Module) -> Result<String> {
66 if !mir.functions.iter().any(|f| f.name == "main") {
67 bail!("no `main` function in MIR module");
68 }
69 let ret_of: HashMap<&str, MirTy> = mir
72 .functions
73 .iter()
74 .map(|f| (f.name.as_str(), f.ret))
75 .chain(mir.externs.iter().map(|e| (e.symbol.as_str(), e.ret)))
76 .collect();
77
78 let mut strings: Vec<String> = Vec::new();
79 let mut string_index: HashMap<String, usize> = HashMap::new();
80 let mut body = String::new();
81
82 for f in &mir.functions {
83 let mut fe = FnEmit {
84 out: &mut body,
85 f,
86 ret_of: &ret_of,
87 strings: &mut strings,
88 string_index: &mut string_index,
89 ssa: 0,
90 };
91 fe.emit()?;
92 body.push('\n');
93 }
94
95 let mut out = String::new();
96 out.push_str("; LLVM IR emitted by the Nova compiler (release backend).\n");
97 out.push_str("; Requires LLVM >= 15 (opaque pointers).\n\n");
98 out.push_str(TRAP_DECL);
99 out.push('\n');
100 for rt in RtFunc::ALL {
101 out.push_str(&rt_func_decl(rt));
102 out.push('\n');
103 }
104 out.push_str(STR_NEW_DECL);
105 out.push('\n');
106 for ext in &mir.externs {
109 let params: Vec<&str> = ext.params.iter().map(|&t| llty(t)).collect();
110 let _ = writeln!(
111 out,
112 "declare {} @\"{}\"({})",
113 llty(ext.ret),
114 ext.symbol,
115 params.join(", ")
116 );
117 }
118 out.push('\n');
119 out.push_str(&body);
120
121 for (i, s) in strings.iter().enumerate() {
123 let bytes: Vec<u8> = if s.is_empty() {
124 vec![0]
125 } else {
126 s.as_bytes().to_vec()
127 };
128 let _ = writeln!(
129 out,
130 "@str{i} = private unnamed_addr constant [{} x i8] c\"{}\", align 1",
131 bytes.len(),
132 escape_bytes(&bytes),
133 );
134 }
135 out.push('\n');
136
137 out.push_str("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n");
139 out.push_str(" call void @nova_main()\n ret i32 0\n}\n");
140
141 Ok(out)
142}
143
144fn llty(ty: MirTy) -> &'static str {
146 match ty {
147 MirTy::I64 => "i64",
148 MirTy::F64 => "double",
149 MirTy::I8 => "i8",
150 MirTy::Ptr => "ptr",
151 MirTy::Unit => "void",
152 }
153}
154
155fn escape_bytes(bytes: &[u8]) -> String {
157 let mut s = String::with_capacity(bytes.len());
158 for &b in bytes {
159 if (0x20..=0x7e).contains(&b) && b != b'"' && b != b'\\' {
161 s.push(b as char);
162 } else {
163 let _ = write!(s, "\\{b:02X}");
164 }
165 }
166 s
167}
168
169fn float_hex(v: f64) -> String {
172 format!("0x{:016X}", v.to_bits())
173}
174
175struct FnEmit<'a> {
177 out: &'a mut String,
178 f: &'a Function,
179 ret_of: &'a HashMap<&'a str, MirTy>,
180 strings: &'a mut Vec<String>,
181 string_index: &'a mut HashMap<String, usize>,
182 ssa: u32,
183}
184
185impl<'a> FnEmit<'a> {
186 fn line(&mut self, s: impl AsRef<str>) {
187 self.out.push_str(s.as_ref());
188 self.out.push('\n');
189 }
190
191 fn fresh(&mut self) -> String {
194 let n = self.ssa;
195 self.ssa += 1;
196 format!("%r{n}")
197 }
198
199 fn temp_ty(&self, t: Temp) -> MirTy {
200 self.f.temps[t.0 as usize]
201 }
202
203 fn slot(&self, t: Temp) -> String {
204 format!("%t{}.slot", t.0)
205 }
206
207 fn load(&mut self, t: Temp) -> String {
209 let ty = llty(self.temp_ty(t));
210 let slot = self.slot(t);
211 let r = self.fresh();
212 self.line(format!(" {r} = load {ty}, ptr {slot}"));
213 r
214 }
215
216 fn store(&mut self, t: Temp, val: &str) {
218 if self.temp_ty(t) == MirTy::Unit {
219 return;
220 }
221 let ty = llty(self.temp_ty(t));
222 let slot = self.slot(t);
223 self.line(format!(" store {ty} {val}, ptr {slot}"));
224 }
225
226 fn abi_count(&self) -> usize {
228 self.f.params as usize + if self.f.takes_env { 1 } else { 0 }
229 }
230
231 fn intern_string(&mut self, s: &str) -> usize {
232 if let Some(&i) = self.string_index.get(s) {
233 return i;
234 }
235 let i = self.strings.len();
236 self.strings.push(s.to_string());
237 self.string_index.insert(s.to_string(), i);
238 i
239 }
240
241 fn emit(&mut self) -> Result<()> {
242 let symbol = if self.f.name == "main" {
243 NOVA_ENTRY_SYMBOL
244 } else {
245 self.f.name.as_str()
246 };
247 let ret = llty(self.f.ret);
248
249 let abi = self.abi_count();
251 let mut params = String::new();
252 for i in 0..abi {
253 let t = Temp(i as u32);
254 if self.temp_ty(t) == MirTy::Unit {
255 continue;
256 }
257 if !params.is_empty() {
258 params.push_str(", ");
259 }
260 let _ = write!(params, "{} %p{i}", llty(self.temp_ty(t)));
261 }
262 self.line(format!("define {ret} @\"{symbol}\"({params}) {{"));
263
264 self.line("entry:");
267 let temp_count = self.f.temps.len();
268 for i in 0..temp_count {
269 let t = Temp(i as u32);
270 if self.temp_ty(t) == MirTy::Unit {
271 continue;
272 }
273 let ty = llty(self.temp_ty(t));
274 let slot = self.slot(t);
275 self.line(format!(" {slot} = alloca {ty}"));
276 }
277 for i in 0..abi {
278 let t = Temp(i as u32);
279 if self.temp_ty(t) == MirTy::Unit {
280 continue;
281 }
282 let ty = llty(self.temp_ty(t));
283 let slot = self.slot(t);
284 self.line(format!(" store {ty} %p{i}, ptr {slot}"));
285 }
286 self.line(" br label %bb0");
287
288 let f: &'a Function = self.f;
292 for (i, block) in f.blocks.iter().enumerate() {
293 self.line(format!("bb{i}:"));
294 for stmt in &block.stmts {
295 self.stmt(stmt)?;
296 }
297 self.terminator(&block.term)?;
298 }
299
300 self.line("}");
301 Ok(())
302 }
303
304 fn gep_byte(&mut self, base: &str, byte_off: i64) -> String {
306 let r = self.fresh();
307 self.line(format!(
308 " {r} = getelementptr inbounds i8, ptr {base}, i64 {byte_off}"
309 ));
310 r
311 }
312
313 fn stmt(&mut self, stmt: &Stmt) -> Result<()> {
314 match stmt {
315 Stmt::ConstInt(t, v) => self.store(*t, &v.to_string()),
316 Stmt::ConstFloat(t, v) => {
317 let hex = float_hex(*v);
318 self.store(*t, &hex);
319 }
320 Stmt::ConstBool(t, v) => self.store(*t, if *v { "1" } else { "0" }),
321 Stmt::ConstUnit(_) => {}
322 Stmt::ConstStr(t, s) => {
323 let idx = self.intern_string(s);
324 let n = if s.is_empty() { 1 } else { s.len() };
325 let p = self.fresh();
326 self.line(format!(
327 " {p} = getelementptr inbounds [{n} x i8], ptr @str{idx}, i64 0, i64 0"
328 ));
329 let r = self.fresh();
330 self.line(format!(
331 " {r} = call ptr @nova_rt_str_new(ptr {p}, i64 {})",
332 s.len()
333 ));
334 self.store(*t, &r);
335 }
336 Stmt::Copy { dst, src } => {
337 if self.temp_ty(*src) != MirTy::Unit {
338 let v = self.load(*src);
339 self.store(*dst, &v);
340 }
341 }
342 Stmt::Bin {
343 dst,
344 op,
345 class,
346 lhs,
347 rhs,
348 } => {
349 let l = self.load(*lhs);
350 let r = self.load(*rhs);
351 let opnd = llty(self.temp_ty(*lhs));
352 let v = self.binop(*op, *class, opnd, &l, &r)?;
353 self.store(*dst, &v);
354 }
355 Stmt::Neg { dst, class, src } => {
356 let v = self.load(*src);
357 let ty = llty(self.temp_ty(*src));
358 let r = self.fresh();
359 match class {
360 OperandClass::Float => self.line(format!(" {r} = fneg {ty} {v}")),
361 _ => self.line(format!(" {r} = sub {ty} 0, {v}")),
362 }
363 self.store(*dst, &r);
364 }
365 Stmt::Not { dst, src } => {
366 let v = self.load(*src);
367 let r = self.fresh();
368 self.line(format!(" {r} = xor i8 {v}, 1"));
369 self.store(*dst, &r);
370 }
371 Stmt::BitNot { dst, src } => {
372 let v = self.load(*src);
373 let r = self.fresh();
374 self.line(format!(" {r} = xor i64 {v}, -1"));
375 self.store(*dst, &r);
376 }
377 Stmt::Call { dst, callee, args } => {
378 let ret = *self.ret_of.get(callee.as_str()).unwrap_or(&MirTy::Unit);
379 let arglist = self.arg_list(args);
380 self.emit_call(*dst, ret, &format!("@\"{callee}\""), &arglist);
381 }
382 Stmt::Await { .. } => {
389 bail!(
390 "an `.await` marker reached codegen: `nova-mir`'s async \
391 state-machine transform must split every `async fn` body \
392 at its await points before a module is emitted"
393 );
394 }
395 Stmt::CallRuntime { dst, func, args } => {
396 let (_, ret) = func.signature();
397 let arglist = self.arg_list(args);
398 let callee = format!("@{}", func.symbol());
399 self.emit_call(*dst, ret, &callee, &arglist);
400 }
401 Stmt::CallIndirect {
402 dst,
403 callee,
404 params: _,
405 ret,
406 args,
407 } => {
408 let fat = self.load(*callee);
411 let code = self.fresh();
412 self.line(format!(" {code} = load ptr, ptr {fat}"));
413 let env_addr = self.gep_byte(&fat, 8);
414 let env = self.fresh();
415 self.line(format!(" {env} = load ptr, ptr {env_addr}"));
416 let mut arglist = format!("ptr {env}");
417 for a in args {
418 if self.temp_ty(*a) == MirTy::Unit {
419 continue;
420 }
421 let ty = llty(self.temp_ty(*a));
422 let v = self.load(*a);
423 let _ = write!(arglist, ", {ty} {v}");
424 }
425 self.emit_call(*dst, *ret, &code, &arglist);
426 }
427 Stmt::MakeClosure {
428 dst,
429 code,
430 captures,
431 } => {
432 let env = if captures.is_empty() {
433 "null".to_string()
434 } else {
435 let size = 8 * captures.len();
436 let env = self.fresh();
437 self.line(format!(" {env} = call ptr @nova_rt_alloc(i64 {size})"));
438 for (i, (cap, ty)) in captures.iter().enumerate() {
439 if *ty == MirTy::Unit {
440 continue;
441 }
442 let v = self.load(*cap);
443 let addr = self.gep_byte(&env, (8 * i) as i64);
444 self.line(format!(" store {} {v}, ptr {addr}", llty(*ty)));
445 }
446 env
447 };
448 let fat = self.fresh();
449 self.line(format!(" {fat} = call ptr @nova_rt_alloc(i64 16)"));
450 self.line(format!(" store ptr @\"{code}\", ptr {fat}"));
451 let env_addr = self.gep_byte(&fat, 8);
452 self.line(format!(" store ptr {env}, ptr {env_addr}"));
453 self.store(*dst, &fat);
454 }
455 Stmt::MakeSum { dst, tag, fields } => {
456 let size = 8 + 8 * fields.len();
457 let ptr = self.fresh();
458 self.line(format!(" {ptr} = call ptr @nova_rt_alloc(i64 {size})"));
459 self.line(format!(" store i64 {tag}, ptr {ptr}"));
460 for (i, (field, ty)) in fields.iter().enumerate() {
461 if *ty == MirTy::Unit {
462 continue;
463 }
464 let v = self.load(*field);
465 let addr = self.gep_byte(&ptr, (8 + 8 * i) as i64);
466 self.line(format!(" store {} {v}, ptr {addr}", llty(*ty)));
467 }
468 self.store(*dst, &ptr);
469 }
470 Stmt::SumTag { dst, sum } => {
471 let p = self.load(*sum);
472 let r = self.fresh();
473 self.line(format!(" {r} = load i64, ptr {p}"));
474 self.store(*dst, &r);
475 }
476 Stmt::SumField {
477 dst,
478 sum,
479 index,
480 ty,
481 } => {
482 if *ty == MirTy::Unit {
483 return Ok(());
484 }
485 let p = self.load(*sum);
486 let addr = self.gep_byte(&p, (8 + 8 * index) as i64);
487 let r = self.fresh();
488 self.line(format!(" {r} = load {}, ptr {addr}", llty(*ty)));
489 self.store(*dst, &r);
490 }
491 Stmt::MakeRecord { dst, fields } => {
492 let size = 8 * fields.len().max(1);
493 let ptr = self.fresh();
494 self.line(format!(" {ptr} = call ptr @nova_rt_alloc(i64 {size})"));
495 for (i, (field, ty)) in fields.iter().enumerate() {
496 if *ty == MirTy::Unit {
497 continue;
498 }
499 let v = self.load(*field);
500 let addr = self.gep_byte(&ptr, (8 * i) as i64);
501 self.line(format!(" store {} {v}, ptr {addr}", llty(*ty)));
502 }
503 self.store(*dst, &ptr);
504 }
505 Stmt::RecordField {
506 dst,
507 record,
508 index,
509 ty,
510 } => {
511 if *ty == MirTy::Unit {
512 return Ok(());
513 }
514 let p = self.load(*record);
515 let addr = self.gep_byte(&p, (8 * index) as i64);
516 let r = self.fresh();
517 self.line(format!(" {r} = load {}, ptr {addr}", llty(*ty)));
518 self.store(*dst, &r);
519 }
520 Stmt::SetField {
521 record,
522 index,
523 value,
524 ty,
525 } => {
526 if *ty == MirTy::Unit {
528 return Ok(());
529 }
530 let v = self.load(*value);
531 let p = self.load(*record);
532 let addr = self.gep_byte(&p, (8 * index) as i64);
534 self.line(format!(" store {} {v}, ptr {addr}", llty(*ty)));
535 }
536 Stmt::MakeArray { dst, elems } => {
537 let size = 8 + 8 * elems.len();
538 let ptr = self.fresh();
539 self.line(format!(" {ptr} = call ptr @nova_rt_alloc(i64 {size})"));
540 self.line(format!(" store i64 {}, ptr {ptr}", elems.len()));
541 for (i, (el, ty)) in elems.iter().enumerate() {
542 if *ty == MirTy::Unit {
543 continue;
544 }
545 let v = self.load(*el);
546 let addr = self.gep_byte(&ptr, (8 + 8 * i) as i64);
547 self.line(format!(" store {} {v}, ptr {addr}", llty(*ty)));
548 }
549 self.store(*dst, &ptr);
550 }
551 Stmt::ArrayAlloc { dst, len } => {
552 let n = self.load(*len);
558 let bytes = self.fresh();
559 self.line(format!(" {bytes} = mul i64 {n}, 8"));
560 let size = self.fresh();
561 self.line(format!(" {size} = add i64 {bytes}, 8"));
562 let ptr = self.fresh();
563 self.line(format!(" {ptr} = call ptr @nova_rt_alloc(i64 {size})"));
564 self.line(format!(" store i64 {n}, ptr {ptr}"));
565 self.store(*dst, &ptr);
566 }
567 Stmt::ArrayLen { dst, arr } => {
568 let p = self.load(*arr);
569 let r = self.fresh();
570 self.line(format!(" {r} = load i64, ptr {p}"));
571 self.store(*dst, &r);
572 }
573 Stmt::ArrayGet {
574 dst,
575 arr,
576 index,
577 ty,
578 } => {
579 if *ty == MirTy::Unit {
580 return Ok(());
581 }
582 let addr = self.array_elem_addr(*arr, *index);
583 let r = self.fresh();
584 self.line(format!(" {r} = load {}, ptr {addr}", llty(*ty)));
585 self.store(*dst, &r);
586 }
587 Stmt::ArraySet {
588 arr,
589 index,
590 value,
591 ty,
592 } => {
593 if *ty == MirTy::Unit {
594 return Ok(());
595 }
596 let v = self.load(*value);
597 let addr = self.array_elem_addr(*arr, *index);
598 self.line(format!(" store {} {v}, ptr {addr}", llty(*ty)));
599 }
600 }
601 Ok(())
602 }
603
604 fn array_elem_addr(&mut self, arr: Temp, index: Temp) -> String {
606 let base = self.load(arr);
607 let idx = self.load(index);
608 let scaled = self.fresh();
609 self.line(format!(" {scaled} = mul i64 {idx}, 8"));
610 let off = self.fresh();
611 self.line(format!(" {off} = add i64 {scaled}, 8"));
612 let addr = self.fresh();
613 self.line(format!(
614 " {addr} = getelementptr inbounds i8, ptr {base}, i64 {off}"
615 ));
616 addr
617 }
618
619 fn arg_list(&mut self, args: &[Temp]) -> String {
621 let mut out = String::new();
622 for a in args {
623 if self.temp_ty(*a) == MirTy::Unit {
624 continue;
625 }
626 let ty = llty(self.temp_ty(*a));
627 let v = self.load(*a);
628 if !out.is_empty() {
629 out.push_str(", ");
630 }
631 let _ = write!(out, "{ty} {v}");
632 }
633 out
634 }
635
636 fn emit_call(&mut self, dst: Option<Temp>, ret: MirTy, callee: &str, arglist: &str) {
639 if ret == MirTy::Unit {
640 self.line(format!(" call void {callee}({arglist})"));
641 } else {
642 let r = self.fresh();
643 self.line(format!(" {r} = call {} {callee}({arglist})", llty(ret)));
644 if let Some(dst) = dst {
645 self.store(dst, &r);
646 }
647 }
648 }
649
650 fn binop(
651 &mut self,
652 op: BinOp,
653 class: OperandClass,
654 opnd: &str,
655 l: &str,
656 r: &str,
657 ) -> Result<String> {
658 use BinOp::*;
659 let cmp_pred = match (class, op) {
661 (OperandClass::Float, Eq) => Some("fcmp oeq"),
662 (OperandClass::Float, Ne) => Some("fcmp une"),
663 (OperandClass::Float, Lt) => Some("fcmp olt"),
664 (OperandClass::Float, Le) => Some("fcmp ole"),
665 (OperandClass::Float, Gt) => Some("fcmp ogt"),
666 (OperandClass::Float, Ge) => Some("fcmp oge"),
667 (_, Eq) => Some("icmp eq"),
668 (_, Ne) => Some("icmp ne"),
669 (_, Lt) => Some("icmp slt"),
670 (_, Le) => Some("icmp sle"),
671 (_, Gt) => Some("icmp sgt"),
672 (_, Ge) => Some("icmp sge"),
673 _ => None,
674 };
675 if let Some(pred) = cmp_pred {
676 let c = self.fresh();
677 self.line(format!(" {c} = {pred} {opnd} {l}, {r}"));
678 let z = self.fresh();
679 self.line(format!(" {z} = zext i1 {c} to i8"));
680 return Ok(z);
681 }
682
683 let instr = match (class, op) {
684 (OperandClass::Float, Add) => "fadd",
685 (OperandClass::Float, Sub) => "fsub",
686 (OperandClass::Float, Mul) => "fmul",
687 (OperandClass::Float, Div) => "fdiv",
688 (OperandClass::Float, Rem) => {
689 bail!("float remainder (%) is not supported by the LLVM backend")
690 }
691 (OperandClass::Float, _) => bail!("bitwise operators are not defined for Float"),
692 (_, Add) => "add",
693 (_, Sub) => "sub",
694 (_, Mul) => "mul",
695 (_, Div) => "sdiv",
696 (_, Rem) => "srem",
697 (_, BitAnd) => "and",
698 (_, BitOr) => "or",
699 (_, BitXor) => "xor",
700 (_, Shl) => "shl",
701 (_, Shr) => "ashr",
702 (_, Eq | Ne | Lt | Le | Gt | Ge) => unreachable!("handled as comparison above"),
703 };
704 let d = self.fresh();
705 self.line(format!(" {d} = {instr} {opnd} {l}, {r}"));
706 Ok(d)
707 }
708
709 fn terminator(&mut self, term: &Terminator) -> Result<()> {
710 match term {
711 Terminator::Goto(b) => self.line(format!(" br label %bb{}", b.0)),
712 Terminator::Branch { cond, then_, else_ } => {
713 let c = self.load(*cond);
714 let c1 = self.fresh();
715 self.line(format!(" {c1} = icmp ne i8 {c}, 0"));
716 self.line(format!(
717 " br i1 {c1}, label %bb{}, label %bb{}",
718 then_.0, else_.0
719 ));
720 }
721 Terminator::Switch {
722 disc,
723 arms,
724 default,
725 } => {
726 let ty = llty(self.temp_ty(*disc));
731 let d = self.load(*disc);
732 let mut table = String::new();
733 for (value, block) in arms {
734 let _ = write!(table, " {ty} {value}, label %bb{}", block.0);
735 }
736 self.line(format!(
737 " switch {ty} {d}, label %bb{} [{table} ]",
738 default.0
739 ));
740 }
741 Terminator::Return(value) => match value {
742 Some(v) if self.f.ret != MirTy::Unit => {
743 let val = self.load(*v);
744 self.line(format!(" ret {} {val}", llty(self.f.ret)));
745 }
746 _ => self.line(" ret void"),
747 },
748 Terminator::Trap => {
749 self.line(" call void @llvm.trap()");
750 self.line(" unreachable");
751 }
752 }
753 Ok(())
754 }
755}