Skip to main content

ir_lang/
inst.rs

1//! Instructions and terminators: the operations a block is made of.
2
3use alloc::vec::Vec;
4use core::fmt;
5
6use crate::entity::{Block, Value};
7
8/// A binary operation.
9///
10/// The arithmetic operations ([`Add`](BinOp::Add) through [`Div`](BinOp::Div))
11/// take two operands of the same numeric type and produce that type. The comparison
12/// operations ([`Eq`](BinOp::Eq) through [`Ge`](BinOp::Ge)) take two operands of the
13/// same type and produce a [`Bool`](crate::Type::Bool). The logical operations
14/// ([`And`](BinOp::And), [`Or`](BinOp::Or)) take two `Bool`s and produce a `Bool`.
15/// The validator enforces these operand rules; the result type is determined by the
16/// operation, so the builder never has to be told it.
17///
18/// # Examples
19///
20/// ```
21/// use ir_lang::BinOp;
22///
23/// assert_eq!(BinOp::Add.to_string(), "add");
24/// assert!(BinOp::Lt.is_comparison());
25/// assert!(!BinOp::Add.is_comparison());
26/// ```
27#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum BinOp {
30    /// Addition of two numeric operands.
31    Add,
32    /// Subtraction of the second numeric operand from the first.
33    Sub,
34    /// Multiplication of two numeric operands.
35    Mul,
36    /// Division of the first numeric operand by the second.
37    Div,
38    /// Equality comparison; result is `Bool`.
39    Eq,
40    /// Inequality comparison; result is `Bool`.
41    Ne,
42    /// Less-than comparison; result is `Bool`.
43    Lt,
44    /// Less-than-or-equal comparison; result is `Bool`.
45    Le,
46    /// Greater-than comparison; result is `Bool`.
47    Gt,
48    /// Greater-than-or-equal comparison; result is `Bool`.
49    Ge,
50    /// Logical conjunction of two `Bool` operands; result is `Bool`.
51    And,
52    /// Logical disjunction of two `Bool` operands; result is `Bool`.
53    Or,
54}
55
56impl BinOp {
57    /// Returns `true` for the comparison operations, whose result is a
58    /// [`Bool`](crate::Type::Bool) regardless of the operand type.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// use ir_lang::BinOp;
64    ///
65    /// assert!(BinOp::Eq.is_comparison());
66    /// assert!(BinOp::Ge.is_comparison());
67    /// assert!(!BinOp::Mul.is_comparison());
68    /// ```
69    #[must_use]
70    pub const fn is_comparison(self) -> bool {
71        matches!(
72            self,
73            BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge
74        )
75    }
76
77    /// Returns `true` for the logical operations, which take and produce
78    /// [`Bool`](crate::Type::Bool).
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use ir_lang::BinOp;
84    ///
85    /// assert!(BinOp::And.is_logical());
86    /// assert!(!BinOp::Add.is_logical());
87    /// ```
88    #[must_use]
89    pub const fn is_logical(self) -> bool {
90        matches!(self, BinOp::And | BinOp::Or)
91    }
92}
93
94impl fmt::Display for BinOp {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        let name = match self {
97            BinOp::Add => "add",
98            BinOp::Sub => "sub",
99            BinOp::Mul => "mul",
100            BinOp::Div => "div",
101            BinOp::Eq => "eq",
102            BinOp::Ne => "ne",
103            BinOp::Lt => "lt",
104            BinOp::Le => "le",
105            BinOp::Gt => "gt",
106            BinOp::Ge => "ge",
107            BinOp::And => "and",
108            BinOp::Or => "or",
109        };
110        f.write_str(name)
111    }
112}
113
114/// A unary operation.
115///
116/// [`Neg`](UnOp::Neg) negates a numeric operand and produces the same numeric type.
117/// [`Not`](UnOp::Not) inverts a [`Bool`](crate::Type::Bool) and produces a `Bool`.
118///
119/// # Examples
120///
121/// ```
122/// use ir_lang::UnOp;
123///
124/// assert_eq!(UnOp::Neg.to_string(), "neg");
125/// assert_eq!(UnOp::Not.to_string(), "not");
126/// ```
127#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129pub enum UnOp {
130    /// Arithmetic negation of a numeric operand.
131    Neg,
132    /// Logical negation of a `Bool` operand.
133    Not,
134}
135
136impl fmt::Display for UnOp {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        let name = match self {
139            UnOp::Neg => "neg",
140            UnOp::Not => "not",
141        };
142        f.write_str(name)
143    }
144}
145
146/// A value-producing instruction.
147///
148/// Every variant defines exactly one [`Value`], whose type is recorded by the
149/// [`Function`](crate::Function) and can be read with
150/// [`Function::value_type`](crate::Function::value_type). Instructions reference
151/// their operands by `Value` handle, never by nesting a sub-expression, so the IR
152/// stays flat and walkable in program order. The terminator that ends a block is a
153/// separate type, [`Terminator`].
154///
155/// You do not build an `Inst` directly; the [`Builder`](crate::Builder) emits one
156/// per call and hands back the value it defines. This type is what you read back
157/// when inspecting a function, for example with
158/// [`Function::inst`](crate::Function::inst).
159///
160/// # Examples
161///
162/// ```
163/// use ir_lang::{Builder, BinOp, Inst};
164///
165/// let mut b = Builder::new("k", &[], ir_lang::Type::Int);
166/// let one = b.iconst(1);
167/// let sum = b.bin(BinOp::Add, one, one);
168/// b.ret(Some(sum));
169/// let func = b.finish();
170///
171/// // The instruction that defined `sum` is the add.
172/// assert!(matches!(func.inst(sum), Some(Inst::Bin(BinOp::Add, _, _))));
173/// // A block parameter is not produced by an instruction.
174/// assert!(func.inst(one).is_some());
175/// ```
176#[derive(Clone, PartialEq, Debug)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub enum Inst {
179    /// An integer constant. Result type is [`Int`](crate::Type::Int).
180    Iconst(i64),
181    /// A floating-point constant. Result type is [`Float`](crate::Type::Float).
182    Fconst(f64),
183    /// A boolean constant. Result type is [`Bool`](crate::Type::Bool).
184    Bconst(bool),
185    /// A binary operation over two values. Result type follows the operation.
186    Bin(BinOp, Value, Value),
187    /// A unary operation over one value. Result type follows the operation.
188    Un(UnOp, Value),
189}
190
191/// The single instruction that ends a basic block and transfers control.
192///
193/// Exactly one terminator ends every block. A [`Jump`](Terminator::Jump) or the two
194/// arms of a [`Branch`](Terminator::Branch) carry an argument per parameter of the
195/// target block — that is how a value is threaded across a control-flow join in SSA
196/// form, in place of a phi node. A [`Return`](Terminator::Return) leaves the
197/// function.
198///
199/// # Examples
200///
201/// ```
202/// use ir_lang::{Builder, Type, Terminator};
203///
204/// let mut b = Builder::new("f", &[], Type::Unit);
205/// b.ret(None);
206/// let func = b.finish();
207/// assert!(matches!(func.terminator(func.entry()), Some(Terminator::Return(None))));
208/// ```
209#[derive(Clone, PartialEq, Debug)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211pub enum Terminator {
212    /// Return from the function, optionally with a value. `Some(v)` returns `v`,
213    /// whose type must match the function's return type; `None` returns from a
214    /// function whose return type is [`Unit`](crate::Type::Unit).
215    Return(Option<Value>),
216    /// Jump unconditionally to a block, passing one argument per target parameter.
217    Jump(Block, Vec<Value>),
218    /// Branch on a [`Bool`](crate::Type::Bool) condition: take the first block (and
219    /// its arguments) when the condition is true, the second otherwise. Each block's
220    /// arguments are matched against that block's parameters.
221    Branch {
222        /// The boolean condition selecting which arm runs.
223        cond: Value,
224        /// The block taken when `cond` is true.
225        then_block: Block,
226        /// Arguments passed to `then_block`'s parameters.
227        then_args: Vec<Value>,
228        /// The block taken when `cond` is false.
229        else_block: Block,
230        /// Arguments passed to `else_block`'s parameters.
231        else_args: Vec<Value>,
232    },
233}
234
235impl Terminator {
236    /// Calls `f` once for each block this terminator can transfer control to, in
237    /// order. A [`Return`](Terminator::Return) calls `f` zero times.
238    ///
239    /// This is how the control-flow graph is read: the successors of a block are the
240    /// targets of its terminator.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// use ir_lang::{Builder, Type};
246    ///
247    /// let mut b = Builder::new("f", &[], Type::Unit);
248    /// let exit = b.create_block(&[]);
249    /// b.jump(exit, &[]);
250    /// b.switch_to(exit);
251    /// b.ret(None);
252    /// let func = b.finish();
253    ///
254    /// let mut succs = Vec::new();
255    /// if let Some(term) = func.terminator(func.entry()) {
256    ///     term.each_successor(|blk| succs.push(blk));
257    /// }
258    /// assert_eq!(succs, vec![exit]);
259    /// ```
260    pub fn each_successor(&self, mut f: impl FnMut(Block)) {
261        match self {
262            Terminator::Return(_) => {}
263            Terminator::Jump(target, _) => f(*target),
264            Terminator::Branch {
265                then_block,
266                else_block,
267                ..
268            } => {
269                f(*then_block);
270                f(*else_block);
271            }
272        }
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use alloc::vec;
280
281    #[test]
282    fn test_binop_classification_partitions_operations() {
283        for op in [BinOp::Add, BinOp::Sub, BinOp::Mul, BinOp::Div] {
284            assert!(!op.is_comparison() && !op.is_logical());
285        }
286        for op in [
287            BinOp::Eq,
288            BinOp::Ne,
289            BinOp::Lt,
290            BinOp::Le,
291            BinOp::Gt,
292            BinOp::Ge,
293        ] {
294            assert!(op.is_comparison() && !op.is_logical());
295        }
296        for op in [BinOp::And, BinOp::Or] {
297            assert!(op.is_logical() && !op.is_comparison());
298        }
299    }
300
301    #[test]
302    fn test_each_successor_reports_targets_in_order() {
303        let mut got = Vec::new();
304        Terminator::Return(None).each_successor(|b| got.push(b));
305        assert!(got.is_empty());
306
307        let mut got = Vec::new();
308        Terminator::Jump(Block::from_raw(2), vec![]).each_successor(|b| got.push(b));
309        assert_eq!(got, vec![Block::from_raw(2)]);
310
311        let mut got = Vec::new();
312        Terminator::Branch {
313            cond: Value::from_raw(0),
314            then_block: Block::from_raw(1),
315            then_args: vec![],
316            else_block: Block::from_raw(2),
317            else_args: vec![],
318        }
319        .each_successor(|b| got.push(b));
320        assert_eq!(got, vec![Block::from_raw(1), Block::from_raw(2)]);
321    }
322}