Skip to main content

nova_codegen_llvm/
lib.rs

1//! LLVM codegen backend for Nova — the optimizing release backend.
2//!
3//! Rather than link against `llvm-sys`/`inkwell` (which would require a
4//! matching LLVM installed at build time), this backend emits **textual LLVM
5//! IR** (`.ll`) from monomorphized MIR. `nova build --release` writes the IR
6//! and hands it to a discovered LLVM toolchain (`clang`/`llc`) for optimizing
7//! compilation, reusing the existing native linker path for the final
8//! executable. The IR requires LLVM >= 15 (opaque pointers).
9//!
10//! The emitted code mirrors the Cranelift backend's semantics exactly — the
11//! same 8-byte-slot heap layouts (sum `{ tag, fields… }`, record `{ fields… }`,
12//! array `{ len, elems… }`, closure fat pointer `{ code, env }`), the same
13//! env-first indirect-call ABI, and the same `nova_rt_*` runtime — so a
14//! program behaves identically under `nova run`, debug `nova build`, and
15//! `nova build --release`.
16//!
17//! MIR temps are not SSA (they are reassigned across blocks, e.g. loop
18//! counters), so each temp becomes an entry-block `alloca` with `load`/`store`
19//! at every use/def. LLVM's `mem2reg` promotes these to SSA registers with
20//! phis during optimization, keeping this emitter simple and obviously correct.
21
22use 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
28/// The symbol the MIR `main` is emitted under, leaving the conventional `main`
29/// name for the exported C entry wrapper (matching the Cranelift object path).
30pub const NOVA_ENTRY_SYMBOL: &str = "nova_main";
31
32/// The LLVM trap intrinsic used by `Terminator::Trap`. Not `RtFunc`-driven:
33/// it's an LLVM builtin, not a Nova runtime symbol.
34const TRAP_DECL: &str = "declare void @llvm.trap()";
35
36/// The string constructor used by `ConstStr`, called directly by its raw
37/// symbol (never through `Stmt::CallRuntime`), so it has no `RtFunc` variant
38/// to generate a declaration from.
39const STR_NEW_DECL: &str = "declare ptr @nova_rt_str_new(ptr, i64)";
40
41/// The LLVM declaration for one `RtFunc`, generated from `symbol()` +
42/// `signature()` rather than hand-written. This is the actual fix for the
43/// bug class this backend used to be exposed to: previously `DECLS` was a
44/// hand-copied string list with no compile-time tie to `RtFunc` at all, so
45/// a new variant could be added and its declaration forgotten here — the
46/// crate still compiled clean, and `CallRuntime`'s `call {ret} @{symbol}`
47/// (driven directly by `RtFunc::signature`/`symbol`) would then reference an
48/// undeclared function, which is invalid LLVM IR. Generating the
49/// declaration from the same `signature()`/`symbol()` the call site uses
50/// makes that impossible: the two can never disagree. See the `nova-mir`
51/// module docs on `RtFunc::ALL` for how the variant list itself is kept
52/// exhaustive.
53fn 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
64/// Compile a monomorphized MIR module to a textual LLVM IR module.
65pub 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    // Return class of every function, so a call site can spell the call's
70    // result type even when the result is discarded.
71    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    // Declare each extern (FFI) symbol; resolved at run time by the system
107    // linker against the C runtime (the call sites spell `@"symbol"`).
108    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    // String literal globals (order-independent from their uses).
122    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    // Exported C entry point: `int main(int, char**) { nova_main(); return 0; }`.
138    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
144/// The LLVM type spelling for a value class (`void` for unit).
145fn 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
155/// Escape bytes for an LLVM `c"..."` string constant.
156fn escape_bytes(bytes: &[u8]) -> String {
157    let mut s = String::with_capacity(bytes.len());
158    for &b in bytes {
159        // Printable ASCII except `"` and `\` may appear literally.
160        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
169/// A `double` constant as its exact IEEE-754 bit pattern (LLVM hex-float form),
170/// avoiding any decimal round-tripping.
171fn float_hex(v: f64) -> String {
172    format!("0x{:016X}", v.to_bits())
173}
174
175/// Per-function IR emission state.
176struct 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    /// A fresh, named SSA temporary (named values dodge LLVM's implicit
192    /// sequential numbering rules for unnamed `%N` values).
193    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    /// Load a temp's current value into a fresh SSA name.
208    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    /// Store a value into a temp's slot (no-op for unit temps).
217    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    /// Number of leading ABI parameter temps (real params + optional env ptr).
227    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        // Signature: non-unit ABI param temps become named LLVM parameters.
250        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        // Entry block: allocate a slot per non-unit temp, seed parameters, then
265        // jump to the first MIR block (so `bb0` may itself be a loop target).
266        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        // One labeled block per MIR block. Copy the `&'a Function` reference
289        // into a local so iterating its blocks (borrowing `'a` data) does not
290        // conflict with mutating `self` during emission.
291        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    /// Emit a byte-offset GEP into a heap object, yielding a fresh address.
305    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` is a marker `nova-mir`'s async state-machine
383            // transform consumes; there is no IR for it, because the state
384            // object the resumed body reads its values back out of is built by
385            // that transform and named by nothing here. Reported as a
386            // malformed-MIR error rather than as a panic, matching this
387            // emitter's other "MIR this backend cannot express" arms.
388            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                // `callee` is a fat pointer `{ code_ptr, env_ptr }`; call
409                // `code_ptr(env_ptr, args...)` (env-first ABI).
410                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                // A unit-typed field has no machine representation to store.
527                if *ty == MirTy::Unit {
528                    return Ok(());
529                }
530                let v = self.load(*value);
531                let p = self.load(*record);
532                // The same `8 * index` offset `RecordField` loads from.
533                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                // Same layout as `MakeArray`, but the element count is only
553                // known at runtime: `8 + 8*len` bytes with `len` at offset 0.
554                // The MIR lowering has already guaranteed `len >= 0`, and the
555                // allocator zeroes, so the elements start valid and are then
556                // filled by the lowering's own loop.
557                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    /// Address of `arr[index]`: `arr + 8 (len header) + index*8`.
605    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    /// Build a comma-separated `<ty> <val>` argument list, skipping unit temps.
620    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    /// Emit a call, storing the result into `dst` when the callee returns a
637    /// value; a void callee is a statement.
638    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        // Comparisons produce an `i1`, widened to the `i8` bool representation.
660        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                // The discriminant carries its own integer class: a sum tag /
727                // `Int` / `Char` switch is `i64`, but a `Bool` match switches
728                // on the `i8` scrutinee directly. The condition and every case
729                // constant must share that type.
730                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}