bloqade_lanes_bytecode_core/isa/
program.rs1use std::io::Cursor;
23
24use vihaco::instruction::{FromBytes, WriteBytes};
25use vihaco::module::Module;
26use vihaco::value::{Type, Value};
27
28use super::{INSTRUCTION_WIDTH, Instruction};
29use crate::version::Version;
30
31pub const MAGIC: &[u8; 5] = b"LANES";
34
35const HEADER_LEN: usize = MAGIC.len() + 4;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct LanesInfo {
41 pub version: Version,
42}
43
44impl Default for LanesInfo {
45 fn default() -> Self {
46 Self {
47 version: Version::new(0, 0),
48 }
49 }
50}
51
52impl std::fmt::Display for LanesInfo {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(f, "version {}", self.version)
55 }
56}
57
58pub type Program = Module<Instruction, Value, Type, LanesInfo>;
61
62#[allow(clippy::field_reassign_with_default)] pub fn from_code(version: Version, code: Vec<Instruction>) -> Program {
67 let mut m = Program::default();
68 m.code = code;
69 m.extra = LanesInfo { version };
70 m
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum BinaryError {
76 BadMagic,
78 Truncated { expected: usize, got: usize },
80 UnalignedCode { len: usize },
82 Decode { pc: usize, message: String },
84}
85
86impl std::fmt::Display for BinaryError {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 match self {
89 BinaryError::BadMagic => write!(f, "bad magic bytes (expected LANES)"),
90 BinaryError::Truncated { expected, got } => {
91 write!(f, "truncated: expected {expected} bytes, got {got}")
92 }
93 BinaryError::UnalignedCode { len } => write!(
94 f,
95 "code length {len} is not a multiple of {INSTRUCTION_WIDTH}"
96 ),
97 BinaryError::Decode { pc, message } => {
98 write!(f, "decode error at instruction {pc}: {message}")
99 }
100 }
101 }
102}
103
104impl std::error::Error for BinaryError {}
105
106pub fn to_binary(program: &Program) -> Vec<u8> {
108 let mut buf = Vec::with_capacity(HEADER_LEN + program.code.len() * INSTRUCTION_WIDTH as usize);
109 buf.extend_from_slice(MAGIC);
110 let packed: u32 = program.extra.version.into();
111 buf.extend_from_slice(&packed.to_le_bytes());
112 for inst in &program.code {
113 inst.write_bytes(&mut buf)
115 .expect("writing instruction bytes to a Vec cannot fail");
116 }
117 buf
118}
119
120pub fn from_binary(bytes: &[u8]) -> Result<Program, BinaryError> {
122 if bytes.len() < HEADER_LEN {
123 return Err(BinaryError::Truncated {
124 expected: HEADER_LEN,
125 got: bytes.len(),
126 });
127 }
128 if &bytes[0..MAGIC.len()] != MAGIC {
129 return Err(BinaryError::BadMagic);
130 }
131 let packed = u32::from_le_bytes([
132 bytes[MAGIC.len()],
133 bytes[MAGIC.len() + 1],
134 bytes[MAGIC.len() + 2],
135 bytes[MAGIC.len() + 3],
136 ]);
137 let version = Version::from(packed);
138
139 let code = &bytes[HEADER_LEN..];
140 let width = INSTRUCTION_WIDTH as usize;
141 if !code.len().is_multiple_of(width) {
142 return Err(BinaryError::UnalignedCode { len: code.len() });
143 }
144
145 let count = code.len() / width;
146 let mut instructions = Vec::with_capacity(count);
147 let mut cursor = Cursor::new(code);
148 for pc in 0..count {
149 let inst = Instruction::from_bytes(&mut cursor).map_err(|e| BinaryError::Decode {
150 pc,
151 message: e.to_string(),
152 })?;
153 instructions.push(inst);
154 }
155
156 Ok(from_code(version, instructions))
157}
158
159#[cfg(test)]
160#[allow(clippy::approx_constant)] mod tests {
162 use super::*;
163
164 fn sample() -> Program {
165 use vihaco::value::Value;
166 use vihaco_cpu::Instruction as Cpu;
167 from_code(
168 Version::new(1, 2),
169 vec![
170 Instruction::Cpu(Cpu::Const(Value::F64(1.5))),
171 Instruction::Cpu(Cpu::Const(Value::I64(-42))),
172 Instruction::Cpu(Cpu::Dup),
173 Instruction::ConstLoc(0x0000_0000_0100_0000),
174 Instruction::ConstLane(0x0000_0000_0000_0001),
175 Instruction::ConstZone(0x0000_0003),
176 Instruction::InitialFill(2),
177 Instruction::Move(1),
178 Instruction::LocalRz(1),
179 Instruction::LocalR(3),
180 Instruction::GlobalRz,
181 Instruction::Cz,
182 Instruction::Measure(1),
183 Instruction::AwaitMeasure,
184 Instruction::NewArray(2, 10, 20),
185 Instruction::GetItem(2),
186 Instruction::SetDetector,
187 Instruction::Cpu(Cpu::Halt),
188 Instruction::Return,
189 ],
190 )
191 }
192
193 #[test]
194 fn binary_round_trips() {
195 let program = sample();
196 let bytes = to_binary(&program);
197 assert_eq!(&bytes[0..MAGIC.len()], MAGIC);
198 assert_eq!(
199 bytes.len(),
200 HEADER_LEN + program.code.len() * INSTRUCTION_WIDTH as usize
201 );
202 assert_eq!(from_binary(&bytes).unwrap(), program);
203 }
204
205 #[test]
206 fn binary_preserves_version() {
207 let bytes = to_binary(&sample());
208 assert_eq!(
209 from_binary(&bytes).unwrap().extra.version,
210 Version::new(1, 2)
211 );
212 }
213
214 #[test]
215 fn empty_program_round_trips() {
216 let program = from_code(Version::new(1, 0), vec![]);
217 let bytes = to_binary(&program);
218 assert_eq!(bytes.len(), HEADER_LEN);
219 assert_eq!(from_binary(&bytes).unwrap(), program);
220 }
221
222 #[test]
223 fn bad_magic_rejected() {
224 let mut bytes = to_binary(&sample());
225 bytes[0] = b'X';
226 assert_eq!(from_binary(&bytes), Err(BinaryError::BadMagic));
227 }
228
229 #[test]
230 fn short_buffer_rejected() {
231 assert_eq!(
232 from_binary(b"LAN"),
233 Err(BinaryError::Truncated {
234 expected: HEADER_LEN,
235 got: 3
236 })
237 );
238 }
239
240 #[test]
241 fn unaligned_code_rejected() {
242 let mut bytes = to_binary(&sample());
243 bytes.push(0); assert!(matches!(
245 from_binary(&bytes),
246 Err(BinaryError::UnalignedCode { .. })
247 ));
248 }
249
250 #[test]
251 fn decode_error_on_bad_opcode() {
252 let mut bytes = Vec::new();
256 bytes.extend_from_slice(MAGIC);
257 let packed: u32 = Version::new(1, 0).into();
258 bytes.extend_from_slice(&packed.to_le_bytes());
259 bytes.extend_from_slice(&[0xFF; INSTRUCTION_WIDTH as usize]);
260 assert!(
261 matches!(from_binary(&bytes), Err(BinaryError::Decode { pc: 0, .. })),
262 "got {:?}",
263 from_binary(&bytes)
264 );
265 }
266
267 #[test]
268 fn binary_error_display_strings() {
269 assert_eq!(
270 BinaryError::BadMagic.to_string(),
271 "bad magic bytes (expected LANES)"
272 );
273 assert_eq!(
274 BinaryError::Truncated {
275 expected: 9,
276 got: 3
277 }
278 .to_string(),
279 "truncated: expected 9 bytes, got 3"
280 );
281 assert_eq!(
282 BinaryError::UnalignedCode { len: 5 }.to_string(),
283 format!("code length 5 is not a multiple of {INSTRUCTION_WIDTH}")
284 );
285 assert_eq!(
286 BinaryError::Decode {
287 pc: 2,
288 message: "boom".into()
289 }
290 .to_string(),
291 "decode error at instruction 2: boom"
292 );
293 }
294
295 #[test]
296 fn lanes_info_display() {
297 let info = LanesInfo {
298 version: Version::new(1, 4),
299 };
300 assert_eq!(info.to_string(), "version 1.4");
301 }
302}