codegen_lang/backend.rs
1//! The [`Backend`] abstraction and the [`Bytecode`] reference backend.
2
3use ir_lang::Function;
4
5use crate::error::CodegenError;
6use crate::lower::lower;
7use crate::program::Program;
8
9/// A code generator: lowers a function in SSA form to a concrete target representation.
10///
11/// This is the abstraction the crate is built around. A backend reads the IR a
12/// front-end produced and turns it into the form a particular target consumes. The
13/// [`Bytecode`] backend shipped here emits a flat [`Program`]; a backend layered on a
14/// native code generator later — LLVM, Cranelift — produces that generator's own module
15/// type, named by the [`Output`](Backend::Output) associated type. Drawing the boundary
16/// as a trait is what lets a front-end be written against codegen without committing to
17/// any one target.
18///
19/// A backend does not own the function and keeps no per-call state, so a single instance
20/// compiles many functions and is cheap to pass around and share.
21///
22/// # Implementing a backend
23///
24/// A backend's `compile` is responsible for rejecting input it cannot lower. The
25/// convention the bytecode backend follows is to call
26/// [`Function::validate`](ir_lang::Function::validate) first and return
27/// [`CodegenError::InvalidIr`] on failure, so the lowering proper only ever runs on
28/// well-formed SSA.
29///
30/// # Examples
31///
32/// Compile with the shipped backend through the trait:
33///
34/// ```
35/// use codegen_lang::{Backend, Bytecode};
36/// use ir_lang::{Builder, Type};
37///
38/// let mut b = Builder::new("noop", &[], Type::Unit);
39/// b.ret(None);
40///
41/// let program = Bytecode.compile(&b.finish()).expect("noop is well-formed");
42/// assert_eq!(program.name(), "noop");
43/// ```
44pub trait Backend {
45 /// The representation this backend emits.
46 type Output;
47
48 /// Lowers `func` to this backend's [`Output`](Backend::Output).
49 ///
50 /// # Errors
51 ///
52 /// Returns [`CodegenError::InvalidIr`] when `func` is not well-formed SSA, and any
53 /// target-specific failure a particular backend defines.
54 fn compile(&self, func: &Function) -> Result<Self::Output, CodegenError>;
55}
56
57/// The reference backend: lowers a function to a flat, register-based [`Program`].
58///
59/// `Bytecode` is the concrete target shipped with the crate and the one
60/// [`compile`] uses. It validates the function, then lowers each block in order to a
61/// linear op stream — small enough to read, serialize, and execute, which makes it the
62/// natural target for testing a front-end's output before a native backend exists.
63///
64/// It is a zero-sized type with no configuration; construct it directly or with
65/// [`Default`].
66///
67/// # Examples
68///
69/// ```
70/// use codegen_lang::{Backend, Bytecode};
71/// use ir_lang::{Builder, BinOp, Type};
72///
73/// // fn inc(x: int) -> int { x + 1 }
74/// let mut b = Builder::new("inc", &[Type::Int], Type::Int);
75/// let x = b.block_params(b.entry())[0];
76/// let one = b.iconst(1);
77/// let sum = b.bin(BinOp::Add, x, one);
78/// b.ret(Some(sum));
79///
80/// let program = Bytecode.compile(&b.finish()).expect("inc is well-formed");
81/// assert_eq!(program.register_count(), 3); // x, the constant, the sum
82/// ```
83#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
84pub struct Bytecode;
85
86impl Backend for Bytecode {
87 type Output = Program;
88
89 fn compile(&self, func: &Function) -> Result<Program, CodegenError> {
90 func.validate().map_err(CodegenError::InvalidIr)?;
91 Ok(lower(func))
92 }
93}
94
95/// Lowers a function to bytecode with the default [`Bytecode`] backend.
96///
97/// A shortcut for `Bytecode.compile(func)` for the common case where the bytecode target
98/// is the one you want. Use [`Bytecode`] through the [`Backend`] trait when a generic
99/// over backends is what you need.
100///
101/// # Errors
102///
103/// Returns [`CodegenError::InvalidIr`] if `func` is not well-formed SSA.
104///
105/// # Examples
106///
107/// ```
108/// use codegen_lang::compile;
109/// use ir_lang::{Builder, Type, UnOp};
110///
111/// // fn negate(x: int) -> int { -x }
112/// let mut b = Builder::new("negate", &[Type::Int], Type::Int);
113/// let x = b.block_params(b.entry())[0];
114/// let neg = b.un(UnOp::Neg, x);
115/// b.ret(Some(neg));
116///
117/// let program = compile(&b.finish()).expect("negate is well-formed");
118/// assert_eq!(program.name(), "negate");
119/// ```
120pub fn compile(func: &Function) -> Result<Program, CodegenError> {
121 Bytecode.compile(func)
122}
123
124#[cfg(test)]
125#[allow(
126 clippy::unwrap_used,
127 reason = "tests build known-valid functions, so compilation cannot fail"
128)]
129mod tests {
130 use super::{Backend, Bytecode, compile};
131 use ir_lang::{Builder, Type};
132
133 #[test]
134 fn test_compile_shortcut_matches_the_backend() {
135 let func = {
136 let mut b = Builder::new("f", &[Type::Int], Type::Int);
137 let x = b.block_params(b.entry())[0];
138 b.ret(Some(x));
139 b.finish()
140 };
141 assert_eq!(compile(&func).unwrap(), Bytecode.compile(&func).unwrap());
142 }
143
144 #[test]
145 fn test_backend_is_zero_sized_and_reusable() {
146 assert_eq!(core::mem::size_of::<Bytecode>(), 0);
147
148 let backend = Bytecode;
149 let mut first = Builder::new("a", &[], Type::Unit);
150 first.ret(None);
151 let mut second = Builder::new("b", &[], Type::Unit);
152 second.ret(None);
153
154 // One instance compiles many functions.
155 assert_eq!(backend.compile(&first.finish()).unwrap().name(), "a");
156 assert_eq!(backend.compile(&second.finish()).unwrap().name(), "b");
157 }
158}