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}