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}