Skip to main content

codegen_lang/
program.rs

1//! The compiled program and the bytecode it is made of.
2
3use alloc::string::String;
4use alloc::vec::Vec;
5use core::fmt;
6
7use ir_lang::{BinOp, UnOp};
8
9/// A virtual register: a numbered slot that holds one value while a program runs.
10///
11/// The bytecode is register-based rather than stack-based. Every value the source
12/// function defines is given its own register, so an [`Op`] names its operands and its
13/// result by register instead of by a position on an operand stack. Registers are dense
14/// from zero; [`Program::register_count`] is one past the highest in use. A function's
15/// parameters occupy the first registers — read them from [`Program::params`].
16///
17/// # Examples
18///
19/// ```
20/// use codegen_lang::Reg;
21///
22/// let r = Reg(2);
23/// assert_eq!(r.0, 2);
24/// assert_eq!(r.to_string(), "r2");
25/// ```
26#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Reg(pub u32);
29
30impl fmt::Display for Reg {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "r{}", self.0)
33    }
34}
35
36/// A jump target: a position in a program's [op stream](Program::ops) that a
37/// control-flow op transfers to.
38///
39/// Each basic block of the source function becomes a label, numbered by block index, so
40/// the entry block is always [`Label(0)`](Program::entry). Laying out a two-way branch
41/// needs one extra position for the second arm, so a backend appends a few internal
42/// labels past the block labels. Resolve a label to an op index with
43/// [`Program::label_offset`].
44///
45/// # Examples
46///
47/// ```
48/// use codegen_lang::Label;
49///
50/// assert_eq!(Label(0).to_string(), "L0");
51/// assert_eq!(Label(3).to_string(), "L3");
52/// ```
53#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct Label(pub u32);
56
57impl fmt::Display for Label {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "L{}", self.0)
60    }
61}
62
63/// A constant operand loaded by [`Op::Const`].
64///
65/// The three cases mirror the IR's three constant instructions
66/// ([`Iconst`](ir_lang::Inst::Iconst), [`Fconst`](ir_lang::Inst::Fconst),
67/// [`Bconst`](ir_lang::Inst::Bconst)) and carry the same payloads, so a constant is
68/// reproduced exactly rather than widened or reinterpreted.
69///
70/// # Examples
71///
72/// ```
73/// use codegen_lang::Const;
74///
75/// assert_eq!(Const::Int(-7).to_string(), "-7");
76/// assert_eq!(Const::Bool(true).to_string(), "true");
77/// ```
78#[derive(Clone, Copy, PartialEq, Debug)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80pub enum Const {
81    /// A signed-integer constant.
82    Int(i64),
83    /// A floating-point constant.
84    Float(f64),
85    /// A boolean constant.
86    Bool(bool),
87}
88
89impl fmt::Display for Const {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            Const::Int(value) => write!(f, "{value}"),
93            Const::Float(value) => write!(f, "{value}"),
94            Const::Bool(value) => write!(f, "{value}"),
95        }
96    }
97}
98
99/// One bytecode instruction.
100///
101/// An op is the unit a [`Program`] is a sequence of. The arithmetic ops
102/// ([`Const`](Op::Const), [`Bin`](Op::Bin), [`Un`](Op::Un)) write their result to a
103/// destination [`Reg`] and read their operands from registers; [`Move`](Op::Move) copies
104/// one register to another; and the control-flow ops ([`Jump`](Op::Jump),
105/// [`JumpUnless`](Op::JumpUnless), [`Return`](Op::Return)) carry a [`Label`] or a result
106/// register. The set is closed and every variant is `Copy`, so an op stream is a flat
107/// `&[Op]` an interpreter or a further pass can walk with no indirection.
108///
109/// # Examples
110///
111/// ```
112/// use codegen_lang::{Const, Op, Reg};
113///
114/// let load = Op::Const { dst: Reg(0), value: Const::Int(1) };
115/// assert_eq!(load.to_string(), "r0 = const 1");
116///
117/// let ret = Op::Return { value: Some(Reg(0)) };
118/// assert_eq!(ret.to_string(), "ret r0");
119/// ```
120#[derive(Clone, Copy, PartialEq, Debug)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122pub enum Op {
123    /// Load a constant into `dst`.
124    Const {
125        /// Register the constant is written to.
126        dst: Reg,
127        /// The constant value.
128        value: Const,
129    },
130    /// Apply a binary operation: `dst = lhs <op> rhs`.
131    Bin {
132        /// The operation, reusing the IR's [`BinOp`].
133        op: BinOp,
134        /// Register the result is written to.
135        dst: Reg,
136        /// Left operand register.
137        lhs: Reg,
138        /// Right operand register.
139        rhs: Reg,
140    },
141    /// Apply a unary operation: `dst = <op> src`.
142    Un {
143        /// The operation, reusing the IR's [`UnOp`].
144        op: UnOp,
145        /// Register the result is written to.
146        dst: Reg,
147        /// Operand register.
148        src: Reg,
149    },
150    /// Copy a register: `dst = src`. Emitted on a control-flow edge to move a block
151    /// argument into the parameter register of the block being entered — the bytecode's
152    /// stand-in for an SSA phi.
153    Move {
154        /// Destination register.
155        dst: Reg,
156        /// Source register.
157        src: Reg,
158    },
159    /// Jump unconditionally to `target`.
160    Jump {
161        /// The label to continue at.
162        target: Label,
163    },
164    /// Jump to `target` when `cond` holds `false`; otherwise fall through to the next op.
165    JumpUnless {
166        /// Register holding the boolean condition.
167        cond: Reg,
168        /// The label taken when the condition is `false`.
169        target: Label,
170    },
171    /// Return from the function, optionally yielding the value in a register.
172    Return {
173        /// The register whose value is returned, or `None` for a unit return.
174        value: Option<Reg>,
175    },
176}
177
178impl fmt::Display for Op {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        match self {
181            Op::Const { dst, value } => write!(f, "{dst} = const {value}"),
182            Op::Bin { op, dst, lhs, rhs } => write!(f, "{dst} = {op} {lhs}, {rhs}"),
183            Op::Un { op, dst, src } => write!(f, "{dst} = {op} {src}"),
184            Op::Move { dst, src } => write!(f, "{dst} = {src}"),
185            Op::Jump { target } => write!(f, "jump {target}"),
186            Op::JumpUnless { cond, target } => write!(f, "jump_unless {cond}, {target}"),
187            Op::Return { value: Some(reg) } => write!(f, "ret {reg}"),
188            Op::Return { value: None } => write!(f, "ret"),
189        }
190    }
191}
192
193/// A lowered function: a flat bytecode program ready to be inspected, serialized, or run.
194///
195/// A program is produced by a [`Backend`](crate::Backend) — for the bytecode target, by
196/// [`Bytecode`](crate::Bytecode) or the [`compile`](crate::compile) shortcut. It owns
197/// the function's name, the registers holding its parameters, a count of every register
198/// it uses, and the [op stream](Program::ops). Control-flow ops refer to positions in
199/// that stream through [`Label`]s, which [`label_offset`](Program::label_offset)
200/// resolves to op indices. Execution begins at the first op, the [entry](Program::entry)
201/// block.
202///
203/// The [`Display`](fmt::Display) implementation renders the program as a readable
204/// disassembly, which is the easiest way to see what a backend produced.
205///
206/// # Examples
207///
208/// ```
209/// use codegen_lang::compile;
210/// use ir_lang::{Builder, BinOp, Type};
211///
212/// // fn double(x: int) -> int { x + x }
213/// let mut b = Builder::new("double", &[Type::Int], Type::Int);
214/// let x = b.block_params(b.entry())[0];
215/// let sum = b.bin(BinOp::Add, x, x);
216/// b.ret(Some(sum));
217/// let program = compile(&b.finish()).expect("double is well-formed");
218///
219/// assert_eq!(program.name(), "double");
220/// assert_eq!(program.params().len(), 1);
221/// assert_eq!(program.register_count(), 2); // x and the sum
222/// assert!(!program.is_empty());
223/// ```
224#[derive(Clone, PartialEq, Debug)]
225#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
226pub struct Program {
227    pub(crate) name: String,
228    pub(crate) params: Vec<Reg>,
229    pub(crate) registers: u32,
230    pub(crate) ops: Vec<Op>,
231    pub(crate) labels: Vec<u32>,
232}
233
234impl Program {
235    /// Returns the function's name.
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use codegen_lang::compile;
241    /// use ir_lang::{Builder, Type};
242    ///
243    /// let mut b = Builder::new("main", &[], Type::Unit);
244    /// b.ret(None);
245    /// assert_eq!(compile(&b.finish()).unwrap().name(), "main");
246    /// ```
247    #[must_use]
248    pub fn name(&self) -> &str {
249        &self.name
250    }
251
252    /// Returns the registers holding the function's parameters, in declaration order.
253    ///
254    /// These are the registers an interpreter writes the call arguments into before it
255    /// begins executing the [op stream](Program::ops).
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use codegen_lang::compile;
261    /// use ir_lang::{Builder, Type};
262    ///
263    /// let mut b = Builder::new("f", &[Type::Int, Type::Bool], Type::Unit);
264    /// b.ret(None);
265    /// let program = compile(&b.finish()).unwrap();
266    /// assert_eq!(program.params().len(), 2);
267    /// ```
268    #[must_use]
269    pub fn params(&self) -> &[Reg] {
270        &self.params
271    }
272
273    /// Returns the number of registers the program uses; valid register numbers are
274    /// `0..register_count`.
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use codegen_lang::compile;
280    /// use ir_lang::{Builder, Type};
281    ///
282    /// let mut b = Builder::new("f", &[Type::Int], Type::Int);
283    /// let x = b.block_params(b.entry())[0];
284    /// let one = b.iconst(1);
285    /// let r = b.bin(ir_lang::BinOp::Add, x, one);
286    /// b.ret(Some(r));
287    /// // x, the constant, and the sum.
288    /// assert_eq!(compile(&b.finish()).unwrap().register_count(), 3);
289    /// ```
290    #[must_use]
291    pub const fn register_count(&self) -> u32 {
292        self.registers
293    }
294
295    /// Returns the program's ops, in execution order.
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use codegen_lang::{compile, Op};
301    /// use ir_lang::{Builder, Type};
302    ///
303    /// let mut b = Builder::new("f", &[], Type::Unit);
304    /// b.ret(None);
305    /// let program = compile(&b.finish()).unwrap();
306    /// assert!(matches!(program.ops(), [Op::Return { value: None }]));
307    /// ```
308    #[must_use]
309    pub fn ops(&self) -> &[Op] {
310        &self.ops
311    }
312
313    /// Returns the number of ops in the program.
314    #[must_use]
315    pub fn len(&self) -> usize {
316        self.ops.len()
317    }
318
319    /// Returns `true` if the program has no ops. A program lowered from a valid function
320    /// is never empty: its entry block always ends in a terminator op.
321    #[must_use]
322    pub fn is_empty(&self) -> bool {
323        self.ops.is_empty()
324    }
325
326    /// Resolves a label to the index of the op it points at, or `None` if the label does
327    /// not belong to this program.
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use codegen_lang::compile;
333    /// use ir_lang::{Builder, Type};
334    ///
335    /// let mut b = Builder::new("f", &[], Type::Unit);
336    /// b.ret(None);
337    /// let program = compile(&b.finish()).unwrap();
338    /// // Execution starts at the entry label, which is the first op.
339    /// assert_eq!(program.label_offset(program.entry()), Some(0));
340    /// ```
341    #[must_use]
342    pub fn label_offset(&self, label: Label) -> Option<usize> {
343        self.labels
344            .get(label.0 as usize)
345            .map(|&offset| offset as usize)
346    }
347
348    /// Returns the entry label, where execution begins: always `L0`, the source
349    /// function's entry block, which lowers to the first op.
350    #[must_use]
351    pub const fn entry(&self) -> Label {
352        Label(0)
353    }
354}
355
356impl fmt::Display for Program {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        write!(f, "{}(", self.name)?;
359        for (i, param) in self.params.iter().enumerate() {
360            if i != 0 {
361                f.write_str(", ")?;
362            }
363            write!(f, "{param}")?;
364        }
365        writeln!(f, ") regs={}", self.registers)?;
366
367        for (index, op) in self.ops.iter().enumerate() {
368            // A label points at exactly one op offset and no two labels share one, so at
369            // most one label prints before each op.
370            for (id, &offset) in self.labels.iter().enumerate() {
371                if offset as usize == index {
372                    writeln!(f, "{}:", Label(id as u32))?;
373                }
374            }
375            writeln!(f, "    {op}")?;
376        }
377        Ok(())
378    }
379}
380
381#[cfg(test)]
382#[allow(
383    clippy::unwrap_used,
384    reason = "tests build known-valid functions, so compilation cannot fail"
385)]
386mod tests {
387    use super::{Const, Label, Op, Reg};
388    use crate::compile;
389    use ir_lang::{BinOp, Builder, Type};
390
391    #[test]
392    fn test_reg_and_label_display_use_short_prefixes() {
393        assert_eq!(Reg(0).to_string(), "r0");
394        assert_eq!(Reg(41).to_string(), "r41");
395        assert_eq!(Label(0).to_string(), "L0");
396    }
397
398    #[test]
399    fn test_const_display_matches_payload() {
400        assert_eq!(Const::Int(5).to_string(), "5");
401        assert_eq!(Const::Int(-5).to_string(), "-5");
402        assert_eq!(Const::Bool(false).to_string(), "false");
403    }
404
405    #[test]
406    fn test_op_display_renders_each_form() {
407        assert_eq!(
408            Op::Const {
409                dst: Reg(0),
410                value: Const::Int(3)
411            }
412            .to_string(),
413            "r0 = const 3"
414        );
415        assert_eq!(
416            Op::Bin {
417                op: BinOp::Add,
418                dst: Reg(2),
419                lhs: Reg(0),
420                rhs: Reg(1)
421            }
422            .to_string(),
423            "r2 = add r0, r1"
424        );
425        assert_eq!(
426            Op::Move {
427                dst: Reg(1),
428                src: Reg(0)
429            }
430            .to_string(),
431            "r1 = r0"
432        );
433        assert_eq!(Op::Jump { target: Label(1) }.to_string(), "jump L1");
434        assert_eq!(
435            Op::JumpUnless {
436                cond: Reg(0),
437                target: Label(2)
438            }
439            .to_string(),
440            "jump_unless r0, L2"
441        );
442        assert_eq!(Op::Return { value: None }.to_string(), "ret");
443    }
444
445    #[test]
446    fn test_program_disassembly_prints_header_label_and_ops() {
447        let mut b = Builder::new("double", &[Type::Int], Type::Int);
448        let x = b.block_params(b.entry())[0];
449        let sum = b.bin(BinOp::Add, x, x);
450        b.ret(Some(sum));
451        let text = compile(&b.finish()).unwrap().to_string();
452
453        assert!(text.starts_with("double(r0) regs=2"));
454        assert!(text.contains("L0:"));
455        assert!(text.contains("r1 = add r0, r0"));
456        assert!(text.contains("ret r1"));
457        // The exact disassembly, as documented in the API reference and release note.
458        assert_eq!(
459            text,
460            "double(r0) regs=2\nL0:\n    r1 = add r0, r0\n    ret r1\n"
461        );
462    }
463
464    #[test]
465    fn test_entry_label_resolves_to_first_op() {
466        let mut b = Builder::new("f", &[], Type::Unit);
467        b.ret(None);
468        let program = compile(&b.finish()).unwrap();
469        assert_eq!(program.entry(), Label(0));
470        assert_eq!(program.label_offset(program.entry()), Some(0));
471        assert_eq!(program.label_offset(Label(999)), None);
472    }
473}