Skip to main content

rucc_ir/
inst.rs

1//! What one instruction is, and what a value is.
2//!
3//! Design: `spec/08-ir.md` sections 8.1 and 8.3.
4//!
5//! An instruction is an [`Opcode`], a set of [`Flags`], a run of value operands, and whatever
6//! else that opcode needs, which is [`Extra`]. Everything that fits in eight bytes is in the
7//! [`Extra`] itself and everything larger is an index into a side table, so the instruction
8//! stays small enough that walking a function is walking one dense array.
9//!
10//! A value is the result of an instruction or a parameter of a block, and it is nothing else.
11//! There is no constant operand kind: a constant is an [`Opcode::IConst`] with a result like
12//! any other instruction. That is what makes the dominance rule in the verifier a single rule
13//! with no exceptions, and it costs nothing, because a constant with no uses is deleted by the
14//! same pass that deletes anything else with no uses.
15
16use rucc_base::{Idx, IdxRange, Symbol};
17
18use crate::{ExtraKind, Flags, FloatPred, IntPred, MemOrder, Opcode, RmwOp, Type};
19
20/// One value: the result of an instruction, or a parameter of a block.
21pub type Value = Idx<ValueData>;
22/// One instruction, in the function that owns it.
23pub type Inst = Idx<InstData>;
24/// One basic block, in the function that owns it.
25pub type Block = Idx<BlockData>;
26
27/// The table of references to values, which is what an operand list is a run of.
28#[derive(Debug)]
29pub struct ValueRef;
30/// A run of value operands.
31pub type ValueList = IdxRange<ValueRef>;
32/// A run of branch targets, which is what a terminator's successors are.
33pub type BlockCallList = IdxRange<BlockCall>;
34/// A run of immediates, which is what a `switch` holds its case values in.
35pub type ImmList = IdxRange<Imm>;
36
37/// A constant, in the immediate table.
38///
39/// The bits and nothing else. An integer is stored two's complement in as many of the low bits
40/// as its type is wide, and a floating point value is stored as its bit pattern, so the same
41/// table holds both and the type on the result says how to read it. That keeps a bit-preserving
42/// answer for a NaN payload, which a value of a Rust floating type would not.
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub struct Imm(u128);
45
46impl Imm {
47    /// The bits, as they are stored.
48    #[must_use]
49    pub const fn bits(self) -> u128 {
50        self.0
51    }
52
53    /// An immediate holding these bits.
54    #[must_use]
55    pub const fn from_bits(bits: u128) -> Self {
56        Self(bits)
57    }
58
59    /// An integer, with the bits above `ty` cleared.
60    ///
61    /// A value is stored in exactly the width its type has, so two immediates are equal when
62    /// they are the same value, which is what lets an equality on the table stand in for an
63    /// equality on the numbers.
64    ///
65    /// # Panics
66    ///
67    /// Panics if `ty` is not an integer type.
68    #[must_use]
69    pub fn int(value: i128, ty: Type) -> Self {
70        assert!(ty.is_int(), "an integer immediate needs an integer type");
71        Self(value as u128 & mask(ty.bits()))
72    }
73
74    /// The value read as unsigned.
75    #[must_use]
76    pub const fn unsigned(self) -> u128 {
77        self.0
78    }
79
80    /// The value read as signed, with the sign bit of `ty` extended.
81    ///
82    /// # Panics
83    ///
84    /// Panics if `ty` is not an integer type.
85    #[must_use]
86    pub fn signed(self, ty: Type) -> i128 {
87        assert!(ty.is_int(), "an integer immediate needs an integer type");
88        let spare = 128 - ty.bits();
89        // Shifting left and then arithmetic right is the branch-free way to sign extend from
90        // an arbitrary width, and it is correct for a width of 128 because the shift is zero.
91        ((self.0 << spare) as i128) >> spare
92    }
93}
94
95/// The low `bits` bits set, and a width of 128 meaning all of them.
96fn mask(bits: u32) -> u128 {
97    if bits >= 128 { u128::MAX } else { (1u128 << bits) - 1 }
98}
99
100/// A branch target, and the values passed to it.
101///
102/// This is the whole reason there are no phi nodes. The arguments are here, in the branch,
103/// beside the block they go to, so removing a predecessor is one edit in one place and there
104/// is no second list anywhere that has to be kept in step with this one.
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub struct BlockCall {
107    /// Where control goes.
108    pub block: Block,
109    /// What is passed, one for each of the block's parameters.
110    pub args: ValueList,
111}
112
113/// What defines a value.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub enum Def {
116    /// The result of an instruction, at this position among its results.
117    Result {
118        /// The instruction.
119        inst: Inst,
120        /// Which of its results this is.
121        index: u8,
122    },
123    /// A parameter of a block, at this position among its parameters.
124    Param {
125        /// The block.
126        block: Block,
127        /// Which of its parameters this is.
128        index: u32,
129    },
130}
131
132/// One value.
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
134pub struct ValueData {
135    /// Its type.
136    pub ty: Type,
137    /// Where it comes from.
138    pub def: Def,
139}
140
141/// What an access does beyond naming an address.
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub struct MemInfo {
144    /// How many bytes the access covers, for the ones whose size is not their result type.
145    ///
146    /// A `load` takes its size from the type it produces. An `alloca` and a `memset` do not,
147    /// and this is where theirs is.
148    pub size: u64,
149    /// The alignment the access is known to have, in bytes.
150    pub align: u32,
151    /// How strongly it is ordered, with [`MemOrder::NotAtomic`] for an ordinary access.
152    pub order: MemOrder,
153    /// The type-based aliasing node, if the front end knew one.
154    pub tbaa: Option<Meta>,
155}
156
157/// A metadata node, in the module's table.
158pub type Meta = Idx<MetaNode>;
159
160/// A node of the metadata graph, which for now is only what aliasing needs.
161///
162/// The tree this forms is checked by the verifier, since a cycle in it would make the aliasing
163/// query that walks it not terminate, and the place to find that out is here and not there.
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub struct MetaNode {
166    /// What this node is called, which is what the printer writes and the parser reads.
167    pub name: Symbol,
168    /// The node one level up, with the root having none.
169    pub parent: Option<Meta>,
170    /// The offset within the parent, for a member of a struct type.
171    pub offset: u64,
172}
173
174/// What a call needs beyond its arguments.
175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
176pub struct CallInfo {
177    /// The name, for a direct call. `None` for a call through an address, where the address is
178    /// the first operand.
179    pub callee: Option<Symbol>,
180    /// The signature it is called with, which is where the ABI attributes are.
181    pub signature: Sig,
182}
183
184/// A signature, in the function's table.
185pub type Sig = Idx<Signature>;
186
187/// What a `switch` needs beyond the value it switches on.
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189pub struct SwitchInfo {
190    /// The targets, with the default first and one for each case after it.
191    pub targets: BlockCallList,
192    /// The case values, one for each target after the default.
193    pub cases: ImmList,
194}
195
196/// What inline assembly needs.
197///
198/// The semantics belong to the inline assembly document. What is here is the shape: a
199/// template, the constraints, the clobbers, and the successors that make `asm goto` the one
200/// instruction whose being a terminator is a property of the instruction and not the opcode.
201#[derive(Clone, Copy, Debug, PartialEq, Eq)]
202pub struct AsmInfo {
203    /// The template string, as written.
204    pub template: Symbol,
205    /// The constraint list, as written.
206    pub constraints: Symbol,
207    /// The clobber list, as written.
208    pub clobbers: Symbol,
209    /// The labels, which are empty for everything except `asm goto`.
210    pub targets: BlockCallList,
211}
212
213/// Everything an instruction carries that is not a value operand.
214///
215/// Anything that fits in eight bytes is here and anything larger is an index into a side
216/// table, so that the common instructions, which are the arithmetic ones carrying nothing at
217/// all, do not pay for the rare ones.
218#[derive(Clone, Copy, Debug, PartialEq, Eq)]
219pub enum Extra {
220    /// Nothing, which is most instructions.
221    None,
222    /// A constant, for `iconst`, `fconst` and `splat`.
223    Imm(Idx<Imm>),
224    /// A name, for `global_addr` and for a target-specific intrinsic.
225    Symbol(Symbol),
226    /// Which comparison, for `icmp`.
227    IntPred(IntPred),
228    /// Which comparison, for `fcmp`.
229    FloatPred(FloatPred),
230    /// An access, for the loads, the stores, the copies and `alloca`.
231    Mem(Idx<MemInfo>),
232    /// An atomic read-modify-write, which is an access and which operation.
233    Rmw(RmwOp, Idx<MemInfo>),
234    /// A barrier's ordering, for `fence`.
235    Order(MemOrder),
236    /// The targets of a branch, with the default first for a `switch`.
237    Targets(BlockCallList),
238    /// A call.
239    Call(Idx<CallInfo>),
240    /// A `switch`, which is targets and the values that select them.
241    Switch(Idx<SwitchInfo>),
242    /// Inline assembly.
243    Asm(Idx<AsmInfo>),
244}
245
246impl Extra {
247    /// Which shape this is, without the payload.
248    ///
249    /// The verifier compares this with [`Opcode::extra_kind`], because an instruction carrying
250    /// the payload of some other opcode prints as text the parser cannot read back.
251    #[must_use]
252    pub const fn kind(self) -> ExtraKind {
253        match self {
254            Self::None => ExtraKind::None,
255            Self::Imm(_) => ExtraKind::Imm,
256            Self::Symbol(_) => ExtraKind::Symbol,
257            Self::IntPred(_) => ExtraKind::IntPred,
258            Self::FloatPred(_) => ExtraKind::FloatPred,
259            Self::Mem(_) => ExtraKind::Mem,
260            Self::Rmw(..) => ExtraKind::Rmw,
261            Self::Order(_) => ExtraKind::Order,
262            Self::Targets(_) => ExtraKind::Targets,
263            Self::Call(_) => ExtraKind::Call,
264            Self::Switch(_) => ExtraKind::Switch,
265            Self::Asm(_) => ExtraKind::Asm,
266        }
267    }
268}
269
270/// One instruction.
271///
272/// There is no result type here. Each result is a value in the function's value table and the
273/// type is on the value, which means a reader asking what an instruction produces asks the
274/// same question about `add` as about `call`, and there is no second copy of the type to
275/// disagree with the first.
276#[derive(Clone, Copy, Debug, PartialEq, Eq)]
277pub struct InstData {
278    /// Which instruction this is.
279    pub opcode: Opcode,
280    /// What the optimizer is licensed to assume about it.
281    pub flags: Flags,
282    /// How many values it produces.
283    pub results: u8,
284    /// The first of them, with the rest following it in the value table.
285    pub first_result: Option<Value>,
286    /// Its value operands.
287    pub args: ValueList,
288    /// Everything else it carries.
289    pub extra: Extra,
290}
291
292impl InstData {
293    /// An instruction with no operands, no flags, no results and nothing extra.
294    #[must_use]
295    pub const fn new(opcode: Opcode) -> Self {
296        Self {
297            opcode,
298            flags: Flags::NONE,
299            results: 0,
300            first_result: None,
301            args: ValueList::EMPTY,
302            extra: Extra::None,
303        }
304    }
305
306    /// The values it produces, in order.
307    pub fn results(&self) -> impl Iterator<Item = Value> + use<> {
308        let first = self.first_result.map_or(0, Idx::raw);
309        (0..u32::from(self.results)).map(move |offset| Value::new(first + offset))
310    }
311
312    /// The run of targets it branches to, which is empty when it does not branch.
313    ///
314    /// A `switch` keeps its targets in a side table, so this reads `Extra::Targets` only and
315    /// the function is what answers for the rest.
316    #[must_use]
317    pub fn targets(&self) -> BlockCallList {
318        match self.extra {
319            Extra::Targets(targets) => targets,
320            _ => BlockCallList::EMPTY,
321        }
322    }
323}
324
325/// How one parameter or one return value travels, beyond what its type says.
326///
327/// The IR's types are the machine's and not C's, so a `ptr` parameter says nothing about
328/// whether the pointer is the argument or whether the object it points at is, and an `i8` says
329/// nothing about which half of the register above it the callee may read. Both are the ABI's
330/// answer rather than the type's, which is why they are here and not on [`Type`].
331///
332/// A signature carrying one of these has already had the ABI applied to it. What the walk to
333/// the IR builds first is the C-level form, where every parameter is [`Abi::Plain`], and the
334/// classification in `rucc-target` is what turns one into the other.
335#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
336pub enum Abi {
337    /// The value itself, in the type it is written as.
338    #[default]
339    Plain,
340    /// An integer narrower than a register, with the bits above it its own sign.
341    ///
342    /// Which of these an ABI asks for is not a property of the value: `unsigned char` is
343    /// [`Abi::Sext`] on the Darwin ABIs and [`Abi::Zext`] elsewhere, and on SysV neither the
344    /// caller nor the callee may assume anything about those bits at all.
345    Sext,
346    /// An integer narrower than a register, with zeroes above it.
347    Zext,
348    /// The bytes of the object the pointer points at, in the argument area, with no address
349    /// travelling anywhere.
350    ///
351    /// The caller makes the copy the callee is free to write to, which is what makes this a C
352    /// call by value rather than a pointer the callee must not keep.
353    ByVal {
354        /// How many bytes travel.
355        size: u64,
356        /// What the copy is aligned to, which is the C alignment of the type and not the
357        /// pointer's.
358        align: u32,
359    },
360    /// Somewhere for the return value to go, whose address the caller passes as the first
361    /// argument because the value does not fit in the registers a return comes back in.
362    Sret {
363        /// How many bytes the callee writes.
364        size: u64,
365        /// What the space is aligned to.
366        align: u32,
367    },
368}
369
370impl Abi {
371    /// Whether this describes an object behind a pointer rather than the value in hand.
372    #[must_use]
373    pub const fn indirect(self) -> bool {
374        matches!(self, Self::ByVal { .. } | Self::Sret { .. })
375    }
376
377    /// The size and alignment of that object, for the two that have one.
378    #[must_use]
379    pub const fn object(self) -> Option<(u64, u32)> {
380        match self {
381            Self::ByVal { size, align } | Self::Sret { size, align } => Some((size, align)),
382            _ => None,
383        }
384    }
385}
386
387/// One parameter, or one return value: a type and how it travels.
388#[derive(Clone, Copy, Debug, PartialEq, Eq)]
389pub struct Param {
390    /// The type the IR sees, which for the indirect forms is `ptr`.
391    pub ty: Type,
392    /// What the ABI asks of it.
393    pub abi: Abi,
394}
395
396impl Param {
397    /// A parameter of this type, in its C-level form.
398    #[must_use]
399    pub const fn new(ty: Type) -> Self {
400        Self { ty, abi: Abi::Plain }
401    }
402
403    /// A parameter of this type travelling this way.
404    #[must_use]
405    pub const fn with_abi(ty: Type, abi: Abi) -> Self {
406        Self { ty, abi }
407    }
408}
409
410/// What a function takes and returns.
411///
412/// A signature is not a type. Nothing in the IR has a function type, because a `ptr` has no
413/// pointee and there is nothing else a function type could sit on. A `call_indirect` names the
414/// signature it is called with, and that is where the ABI attributes are read from.
415#[derive(Clone, Debug, PartialEq, Eq, Default)]
416pub struct Signature {
417    /// What it takes, in their C-level form until the ABI has been applied.
418    pub params: Vec<Param>,
419    /// What it returns, which is empty for a `void` function and for one whose return value
420    /// comes back through an [`Abi::Sret`] parameter.
421    pub returns: Vec<Param>,
422    /// Whether it takes arguments beyond the ones named.
423    pub variadic: bool,
424}
425
426impl Signature {
427    /// A signature taking and returning nothing.
428    #[must_use]
429    pub fn new() -> Self {
430        Self::default()
431    }
432
433    /// The same signature with these parameters, each in its C-level form.
434    #[must_use]
435    pub fn with_params(mut self, params: &[Type]) -> Self {
436        self.params = params.iter().copied().map(Param::new).collect();
437        self
438    }
439
440    /// The same signature returning these, each in its C-level form.
441    #[must_use]
442    pub fn with_returns(mut self, returns: &[Type]) -> Self {
443        self.returns = returns.iter().copied().map(Param::new).collect();
444        self
445    }
446
447    /// The same signature with one more parameter, travelling the way the ABI said.
448    #[must_use]
449    pub fn and_param(mut self, param: Param) -> Self {
450        self.params.push(param);
451        self
452    }
453
454    /// The same signature with one more return value, travelling the way the ABI said.
455    #[must_use]
456    pub fn and_return(mut self, param: Param) -> Self {
457        self.returns.push(param);
458        self
459    }
460
461    /// The types it takes, without what the ABI asks of them.
462    pub fn param_types(&self) -> impl Iterator<Item = Type> + use<'_> {
463        self.params.iter().map(|param| param.ty)
464    }
465
466    /// The types it returns.
467    pub fn return_types(&self) -> impl Iterator<Item = Type> + use<'_> {
468        self.returns.iter().map(|param| param.ty)
469    }
470
471    /// The same signature, variadic.
472    #[must_use]
473    pub fn variadic(mut self) -> Self {
474        self.variadic = true;
475        self
476    }
477}
478
479/// One basic block: parameters, then instructions, then exactly one terminator.
480///
481/// The instructions are a doubly linked list rather than a vector, so that inserting one in
482/// the middle of a block does not move the ones after it. An optimizer does that constantly,
483/// and a move would invalidate every [`Inst`] anybody was holding.
484#[derive(Clone, Debug, Default, PartialEq, Eq)]
485pub struct BlockData {
486    /// The values arriving here, which is what other IRs spell as phi nodes.
487    ///
488    /// A `Vec` and not a run in a pool, because SSA construction adds a parameter to a loop
489    /// header long after the blocks that come after it have been built, and a run in a pool
490    /// cannot grow in the middle.
491    pub params: Vec<Value>,
492    /// The first instruction, or `None` for a block nothing has been put in yet.
493    pub first: Option<Inst>,
494    /// The last instruction, which is the terminator once the block is finished.
495    pub last: Option<Inst>,
496    /// The block before this one in layout order.
497    pub prev: Option<Block>,
498    /// The block after it.
499    pub next: Option<Block>,
500}
501
502/// Where one instruction sits.
503#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
504pub struct InstLayout {
505    /// The block it is in, or `None` if it has been made and not yet inserted.
506    pub block: Option<Block>,
507    /// The instruction before it in that block.
508    pub prev: Option<Inst>,
509    /// The instruction after it.
510    pub next: Option<Inst>,
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516
517    #[test]
518    fn an_immediate_keeps_only_the_bits_its_type_has() {
519        let byte = Type::int(8);
520        assert_eq!(Imm::int(-1, byte).unsigned(), 0xff);
521        assert_eq!(Imm::int(-1, byte).signed(byte), -1);
522        assert_eq!(Imm::int(255, byte), Imm::int(-1, byte));
523        assert_eq!(Imm::int(127, byte).signed(byte), 127);
524        assert_eq!(Imm::int(128, byte).signed(byte), -128);
525    }
526
527    #[test]
528    fn a_widest_immediate_is_not_truncated() {
529        let word = Type::int(128);
530        assert_eq!(Imm::int(i128::MIN, word).signed(word), i128::MIN);
531        assert_eq!(Imm::int(i128::MAX, word).signed(word), i128::MAX);
532        assert_eq!(Imm::int(-1, word).unsigned(), u128::MAX);
533    }
534
535    #[test]
536    fn a_one_bit_immediate_is_a_bit() {
537        let bit = Type::I1;
538        assert_eq!(Imm::int(1, bit).unsigned(), 1);
539        assert_eq!(Imm::int(3, bit).unsigned(), 1);
540        assert_eq!(Imm::int(2, bit).unsigned(), 0);
541        // The one bit is the sign bit, so the only two values are zero and minus one.
542        assert_eq!(Imm::int(1, bit).signed(bit), -1);
543    }
544
545    #[test]
546    fn a_floating_immediate_keeps_its_bits() {
547        let bits = f64::NAN.to_bits() | 0x7;
548        assert_eq!(Imm::from_bits(u128::from(bits)).bits(), u128::from(bits));
549    }
550
551    #[test]
552    fn an_instruction_with_no_results_yields_none() {
553        let inst = InstData::new(Opcode::Store);
554        assert_eq!(inst.results().count(), 0);
555    }
556
557    #[test]
558    fn results_follow_the_first_one() {
559        let mut inst = InstData::new(Opcode::SAddOverflow);
560        inst.first_result = Some(Value::new(4));
561        inst.results = 2;
562        let got: Vec<u32> = inst.results().map(Idx::raw).collect();
563        assert_eq!(got, [4, 5]);
564    }
565
566    #[test]
567    fn a_jump_says_where_it_goes() {
568        let mut inst = InstData::new(Opcode::Jump);
569        inst.extra = Extra::Targets(BlockCallList::new(Idx::new(0), Idx::new(1)));
570        assert_eq!(inst.targets().len(), 1);
571    }
572
573    #[test]
574    fn a_signature_is_built_by_saying_what_it_takes_and_returns() {
575        let sig = Signature::new()
576            .with_params(&[Type::int(32), Type::PTR])
577            .with_returns(&[Type::int(32)])
578            .variadic();
579        assert_eq!(sig.param_types().collect::<Vec<_>>(), [Type::int(32), Type::PTR]);
580        assert_eq!(sig.return_types().collect::<Vec<_>>(), [Type::int(32)]);
581        assert!(sig.variadic);
582        assert_eq!(Signature::new(), Signature::default());
583    }
584
585    #[test]
586    fn a_parameter_says_how_it_travels_and_not_only_what_it_is() {
587        let object = Abi::ByVal { size: 24, align: 8 };
588        let sig = Signature::new()
589            .and_param(Param::with_abi(Type::PTR, Abi::Sret { size: 32, align: 16 }))
590            .and_param(Param::with_abi(Type::PTR, object))
591            .and_param(Param::with_abi(Type::int(8), Abi::Zext));
592        // The types alone say `ptr, ptr, i8`, which is three of the calls in any C program and
593        // none of them the same call.
594        assert_eq!(sig.param_types().collect::<Vec<_>>(), [Type::PTR, Type::PTR, Type::int(8)]);
595        assert_eq!(sig.params[1].abi.object(), Some((24, 8)));
596        assert!(sig.params[0].abi.indirect() && !sig.params[2].abi.indirect());
597        assert_eq!(Param::new(Type::PTR).abi, Abi::Plain);
598        assert_eq!(Abi::Plain.object(), None);
599    }
600
601    #[test]
602    fn an_instruction_stays_small() {
603        // Not a promise, a tripwire. Every function in the program is a run of these, and a
604        // change that doubles this should be a change somebody decided to make.
605        assert!(size_of::<InstData>() <= 32, "{}", size_of::<InstData>());
606        assert_eq!(size_of::<ValueData>(), 16);
607        assert_eq!(size_of::<Extra>(), 12);
608    }
609}