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