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