Skip to main content

cove_ir/bytecode/
mod.rs

1//! The fixed-width encoded form of the instructions.
2//!
3//! [ADR 0041](../../../../docs/adr/0041-a-slot-number-fits-in-sixteen-bits.md)
4//! decides everything here: sixteen bytes, one opcode byte, one reserved
5//! `flags` byte that must be zero, three sixteen-bit fields that are always
6//! frame slots, and one sixty-four-bit payload that is everything else.
7//!
8//! ```text
9//! byte:  0        1        2   3     4   5     6   7     8 .. 15
10//!        opcode   flags    a         b         c         payload
11//!        u8       u8       u16       u16       u16       u64
12//! ```
13//!
14//! # What this is for
15//!
16//! [`Inst`](crate::Inst) stays the compiler's representation: it is what the
17//! lowering builds, what a listing prints, what a test asserts on, and what
18//! the debugger shows as *lowered IR*. This is the other half of issue #245's
19//! split — a form that is **verified once and then trusted**, so that a
20//! dispatch loop can read an operand without asking whether it is in range.
21//!
22//! Nothing executes one yet. This module is the encoder, the decoder, the
23//! verifier and the disassembly; running them is issue #245's Phase 3.
24//!
25//! # The layout is written and read by hand
26//!
27//! An [`EncodedInst`] is `[u8; 16]` and every field goes in and comes out
28//! through the little-endian accessors below. There is no `#[repr(C)]`
29//! struct, no `transmute` and no `bytemuck`: issue #245 asks for the width
30//! and the byte order to be the format's own promise rather than something
31//! inherited from what the Rust compiler happens to do with a declaration.
32//!
33//! # It is 1:1 with the readable IR
34//!
35//! Every `Inst` encodes to exactly one `EncodedInst` and back, so **bytecode
36//! pc is IR pc**. That is what keeps [`Function::spans`](crate::Function),
37//! [`Local`](crate::Local)'s pc ranges and [`Table::targets`](crate::Table)
38//! meaning what they meant with no remapping, and it is why the disassembler
39//! is [`decode()`] plus [`crate::print`] rather than a second renderer. See
40//! [`disasm`].
41//!
42//! # Not a compatibility promise
43//!
44//! ADR 0041 is explicit: this is an internal executable representation. There
45//! is no stable on-disk bytecode, no cross-version compatibility, no public
46//! ABI, and **no opcode-number stability** — the numbers below are positions
47//! in a generated table and move when the table does. [`verify()`] is
48//! nevertheless safe against arbitrary bytes, because a verifier that is only
49//! safe against its own encoder is not a verifier.
50
51pub mod decode;
52pub mod disasm;
53pub mod encode;
54pub mod op;
55pub mod verify;
56
57pub use decode::{decode, Malformed};
58pub use disasm::listing;
59pub use encode::{encode, encode_function, encode_program, Encoded, TooWide};
60pub use op::{Half, Op, Operand, Payload};
61pub use verify::{verify, Fault};
62
63use crate::inst::Pc;
64
65/// The most words one function's frame may hold.
66///
67/// A slot operand is sixteen bits, so slots 0 through 65,535 are nameable and
68/// a frame of exactly 65,536 words is exactly encodable. ADR 0041 adopts this
69/// as a compiler limit and `crate::lower` is where a frame over it is refused,
70/// with a diagnostic at the declaration — never a truncation and never a wrap.
71///
72/// It is **not** the run's stack budget. `SEGMENT_WORDS` bounds one whole
73/// task's stack at `1 << 20` words and is answered at run time by
74/// `Memory::push_frame`; this bounds one *function*, at compile time, and is
75/// one sixteenth of that.
76pub const MAX_FRAME_WORDS: usize = 65_536;
77
78/// One encoded instruction: sixteen bytes, little-endian.
79///
80/// Built by [`encode()`] and read back by [`decode()`]. The accessors are the
81/// whole of the format — an [`EncodedInst`] means nothing except what they
82/// say it means.
83#[derive(Clone, Copy, PartialEq, Eq, Hash)]
84pub struct EncodedInst([u8; EncodedInst::BYTES]);
85
86impl EncodedInst {
87    /// How wide one instruction is. Two words, four to a cache line, and the
88    /// byte offset of instruction `pc` is `pc << 4`.
89    pub const BYTES: usize = 16;
90
91    /// An instruction from its fields, which is the only way the encoder
92    /// builds one.
93    pub fn new(opcode: u8, a: u16, b: u16, c: u16, payload: u64) -> EncodedInst {
94        let mut bytes = [0u8; EncodedInst::BYTES];
95        bytes[0] = opcode;
96        // Byte 1 is `flags`, and it stays zero: ADR 0041 reserves it for a
97        // fact that does not exist yet and requires the verifier to reject a
98        // nonzero one. There is deliberately no way to set it here.
99        bytes[2..4].copy_from_slice(&a.to_le_bytes());
100        bytes[4..6].copy_from_slice(&b.to_le_bytes());
101        bytes[6..8].copy_from_slice(&c.to_le_bytes());
102        bytes[8..16].copy_from_slice(&payload.to_le_bytes());
103        EncodedInst(bytes)
104    }
105
106    /// An instruction from bytes nothing here produced.
107    ///
108    /// This is how a verifier test, a debugger and a future loader get one,
109    /// and it is why [`verify()`] may not assume anything about the contents.
110    pub fn from_bytes(bytes: [u8; EncodedInst::BYTES]) -> EncodedInst {
111        EncodedInst(bytes)
112    }
113
114    /// The sixteen bytes, as they are stored.
115    pub fn bytes(&self) -> &[u8; EncodedInst::BYTES] {
116        &self.0
117    }
118
119    /// Byte 0: which operation this is. See [`Op`].
120    pub fn opcode(&self) -> u8 {
121        self.0[0]
122    }
123
124    /// Byte 1: reserved, and required to be zero.
125    pub fn flags(&self) -> u8 {
126        self.0[1]
127    }
128
129    /// Bytes 2–3: the first slot field.
130    pub fn a(&self) -> u16 {
131        u16::from_le_bytes([self.0[2], self.0[3]])
132    }
133
134    /// Bytes 4–5: the second slot field.
135    pub fn b(&self) -> u16 {
136        u16::from_le_bytes([self.0[4], self.0[5]])
137    }
138
139    /// Bytes 6–7: the third slot field.
140    pub fn c(&self) -> u16 {
141        u16::from_le_bytes([self.0[6], self.0[7]])
142    }
143
144    /// Bytes 8–15: everything that is not a slot.
145    pub fn payload(&self) -> u64 {
146        u64::from_le_bytes([
147            self.0[8], self.0[9], self.0[10], self.0[11], self.0[12], self.0[13], self.0[14],
148            self.0[15],
149        ])
150    }
151
152    /// The payload's low half, which is where a single id goes.
153    pub fn lo(&self) -> u32 {
154        self.payload() as u32
155    }
156
157    /// The payload's high half, which is where a second id goes.
158    pub fn hi(&self) -> u32 {
159        (self.payload() >> 32) as u32
160    }
161
162    /// The byte offset of instruction `pc`, which a fixed width makes a
163    /// shift.
164    pub fn offset_of(pc: Pc) -> usize {
165        (pc as usize) << 4
166    }
167}
168
169/// Sixteen bytes in hex, so that a failing test says which byte.
170impl std::fmt::Debug for EncodedInst {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.write_str("EncodedInst(")?;
173        for byte in &self.0 {
174            write!(f, "{byte:02x}")?;
175        }
176        f.write_str(")")
177    }
178}
179
180/// A byte sequence that is not a whole number of instructions.
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182pub struct Truncated {
183    /// How many bytes there were.
184    pub bytes: usize,
185    /// How many of them are left over after the last whole instruction.
186    pub over: usize,
187}
188
189impl std::fmt::Display for Truncated {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        write!(
192            f,
193            "{} bytes is {} instructions and {} bytes over, and an instruction is {}",
194            self.bytes,
195            self.bytes / EncodedInst::BYTES,
196            self.over,
197            EncodedInst::BYTES
198        )
199    }
200}
201
202/// Reads a run of bytes as instructions, refusing a partial one.
203///
204/// The one place truncation is a question a fixed width can be asked: an
205/// [`EncodedInst`] is always sixteen bytes, so a short *instruction* cannot
206/// exist, but a short *stream* can — a file cut off, a `Uint8Array` handed
207/// across the Wasm boundary with the wrong length. This is where that is
208/// refused, before anything indexes into it.
209pub fn instructions(bytes: &[u8]) -> Result<Vec<EncodedInst>, Truncated> {
210    let over = bytes.len() % EncodedInst::BYTES;
211    if over != 0 {
212        return Err(Truncated {
213            bytes: bytes.len(),
214            over,
215        });
216    }
217    // `as_chunks` rather than `chunks_exact`, and the difference is not
218    // only that a newer clippy asks for it. A `chunks_exact` over a constant
219    // width answers slices the caller has to copy back into an array of the
220    // width it already knew; `as_chunks` answers the arrays. The copy and the
221    // scratch buffer that made it go away with it.
222    let (chunks, rest) = bytes.as_chunks::<{ EncodedInst::BYTES }>();
223    debug_assert!(rest.is_empty(), "the remainder was refused above");
224    Ok(chunks
225        .iter()
226        .copied()
227        .map(EncodedInst::from_bytes)
228        .collect())
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    /// Every field goes in at the byte offset ADR 0041's diagram gives it,
236    /// little-endian, and `flags` is zero because nothing can set it.
237    #[test]
238    fn the_sixteen_bytes_are_the_layout_the_adr_draws() {
239        let inst = EncodedInst::new(0x2a, 0x0102, 0x0304, 0x0506, 0x0807_0605_0403_0201);
240        assert_eq!(
241            inst.bytes(),
242            &[
243                0x2a, 0x00, // opcode, flags
244                0x02, 0x01, // a
245                0x04, 0x03, // b
246                0x06, 0x05, // c
247                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // payload
248            ]
249        );
250        assert_eq!(inst.opcode(), 0x2a);
251        assert_eq!(inst.flags(), 0);
252        assert_eq!(inst.a(), 0x0102);
253        assert_eq!(inst.b(), 0x0304);
254        assert_eq!(inst.c(), 0x0506);
255        assert_eq!(inst.payload(), 0x0807_0605_0403_0201);
256        assert_eq!(inst.lo(), 0x0403_0201);
257        assert_eq!(inst.hi(), 0x0807_0605);
258    }
259
260    /// The byte offset of an instruction is a shift, which is the whole
261    /// arithmetic argument for sixteen rather than twenty-four.
262    #[test]
263    fn the_byte_offset_of_a_pc_is_that_pc_times_sixteen() {
264        assert_eq!(EncodedInst::offset_of(0), 0);
265        assert_eq!(EncodedInst::offset_of(1), 16);
266        assert_eq!(EncodedInst::offset_of(1_000), 16_000);
267    }
268
269    /// A stream that is not a whole number of instructions is refused rather
270    /// than rounded down.
271    #[test]
272    fn a_stream_that_stops_mid_instruction_is_refused() {
273        assert_eq!(instructions(&[]), Ok(Vec::new()));
274        assert_eq!(instructions(&[0u8; 32]).map(|held| held.len()), Ok(2));
275        assert_eq!(
276            instructions(&[0u8; 17]),
277            Err(Truncated { bytes: 17, over: 1 })
278        );
279        assert_eq!(
280            instructions(&[0u8; 15]),
281            Err(Truncated {
282                bytes: 15,
283                over: 15
284            })
285        );
286    }
287
288    /// The limit is exactly what a `u16` slot can name, one past the largest
289    /// slot number.
290    #[test]
291    fn the_frame_limit_is_one_past_the_largest_slot_a_u16_names() {
292        assert_eq!(MAX_FRAME_WORDS, u16::MAX as usize + 1);
293    }
294}