bloqade_lanes_bytecode_core/bytecode/
program.rs

1use std::fmt;
2
3use super::encode::DecodeError;
4use super::instruction::Instruction;
5use crate::version::Version;
6
7const MAGIC: &[u8; 4] = b"BLQD";
8const SECTION_TYPE_METADATA: u32 = 0;
9const SECTION_TYPE_CODE: u32 = 1;
10
11/// A bytecode program consisting of a version and instruction sequence.
12///
13/// Programs can be serialized to/from BLQD binary format (via
14/// [`to_binary`](Program::to_binary) / [`from_binary`](Program::from_binary))
15/// or SST text assembly (via [`bytecode::text`](super::text)).
16#[derive(Debug, Clone, PartialEq)]
17pub struct Program {
18    /// Program version.
19    pub version: Version,
20    /// Instructions in execution order.
21    pub instructions: Vec<Instruction>,
22}
23
24/// Error type for BLQD binary format parsing.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum ProgramError {
27    BadMagic,
28    Truncated { expected: usize, got: usize },
29    UnknownSectionType(u32),
30    InvalidCodeSectionLength(usize),
31    MissingMetadataSection,
32    MissingCodeSection,
33    Decode(DecodeError),
34}
35
36impl fmt::Display for ProgramError {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            ProgramError::BadMagic => write!(f, "bad magic bytes (expected BLQD)"),
40            ProgramError::Truncated { expected, got } => {
41                write!(f, "truncated: expected {} bytes, got {}", expected, got)
42            }
43            ProgramError::UnknownSectionType(t) => write!(f, "unknown section type: {}", t),
44            ProgramError::InvalidCodeSectionLength(len) => {
45                write!(f, "code section length {} is not a multiple of 16", len)
46            }
47            ProgramError::MissingMetadataSection => write!(f, "missing metadata section"),
48            ProgramError::MissingCodeSection => write!(f, "missing code section"),
49            ProgramError::Decode(e) => write!(f, "decode error: {}", e),
50        }
51    }
52}
53
54impl std::error::Error for ProgramError {}
55
56impl From<DecodeError> for ProgramError {
57    fn from(e: DecodeError) -> Self {
58        ProgramError::Decode(e)
59    }
60}
61
62impl Program {
63    /// Serialize the program to the BLQD binary format.
64    pub fn to_binary(&self) -> Vec<u8> {
65        let code_payload_len = self.instructions.len() * 16;
66        // Header (8) + metadata section header (8) + metadata payload (4)
67        // + code section header (8) + code payload
68        let total = 8 + 8 + 4 + 8 + code_payload_len;
69        let mut buf = Vec::with_capacity(total);
70
71        // Header: magic + section_count
72        buf.extend_from_slice(MAGIC);
73        buf.extend_from_slice(&2u32.to_le_bytes());
74
75        // Metadata section: type=0, length=4, version
76        buf.extend_from_slice(&SECTION_TYPE_METADATA.to_le_bytes());
77        buf.extend_from_slice(&4u32.to_le_bytes());
78        let version_u32: u32 = self.version.into();
79        buf.extend_from_slice(&version_u32.to_le_bytes());
80
81        // Code section: type=1, length, instructions
82        buf.extend_from_slice(&SECTION_TYPE_CODE.to_le_bytes());
83        buf.extend_from_slice(&(code_payload_len as u32).to_le_bytes());
84        for instr in &self.instructions {
85            buf.extend_from_slice(&instr.to_bytes());
86        }
87
88        buf
89    }
90
91    /// Deserialize a program from the BLQD binary format.
92    pub fn from_binary(bytes: &[u8]) -> Result<Self, ProgramError> {
93        if bytes.len() < 8 {
94            return Err(ProgramError::Truncated {
95                expected: 8,
96                got: bytes.len(),
97            });
98        }
99
100        // Verify magic
101        if &bytes[0..4] != MAGIC {
102            return Err(ProgramError::BadMagic);
103        }
104
105        let section_count = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
106
107        let mut offset = 8usize;
108        let mut version: Option<Version> = None;
109        let mut instructions: Option<Vec<Instruction>> = None;
110
111        for _ in 0..section_count {
112            // Need at least 8 bytes for section header
113            if offset + 8 > bytes.len() {
114                return Err(ProgramError::Truncated {
115                    expected: offset + 8,
116                    got: bytes.len(),
117                });
118            }
119
120            let section_type = u32::from_le_bytes([
121                bytes[offset],
122                bytes[offset + 1],
123                bytes[offset + 2],
124                bytes[offset + 3],
125            ]);
126            let section_length = u32::from_le_bytes([
127                bytes[offset + 4],
128                bytes[offset + 5],
129                bytes[offset + 6],
130                bytes[offset + 7],
131            ]) as usize;
132            offset += 8;
133
134            if offset + section_length > bytes.len() {
135                return Err(ProgramError::Truncated {
136                    expected: offset + section_length,
137                    got: bytes.len(),
138                });
139            }
140
141            match section_type {
142                SECTION_TYPE_METADATA => {
143                    if section_length < 4 {
144                        return Err(ProgramError::Truncated {
145                            expected: 4,
146                            got: section_length,
147                        });
148                    }
149                    let v = u32::from_le_bytes([
150                        bytes[offset],
151                        bytes[offset + 1],
152                        bytes[offset + 2],
153                        bytes[offset + 3],
154                    ]);
155                    version = Some(Version::from(v));
156                }
157                SECTION_TYPE_CODE => {
158                    if !section_length.is_multiple_of(16) {
159                        return Err(ProgramError::InvalidCodeSectionLength(section_length));
160                    }
161                    let count = section_length / 16;
162                    let mut instrs = Vec::with_capacity(count);
163                    for i in 0..count {
164                        let start = offset + i * 16;
165                        let chunk: &[u8] = &bytes[start..start + 16];
166                        let arr: [u8; 16] = chunk.try_into().unwrap();
167                        instrs.push(Instruction::from_bytes(&arr)?);
168                    }
169                    instructions = Some(instrs);
170                }
171                other => return Err(ProgramError::UnknownSectionType(other)),
172            }
173
174            offset += section_length;
175        }
176
177        let version = version.ok_or(ProgramError::MissingMetadataSection)?;
178        let instructions = instructions.ok_or(ProgramError::MissingCodeSection)?;
179
180        Ok(Program {
181            version,
182            instructions,
183        })
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::bytecode::instruction::{
191        ArrayInstruction, AtomArrangementInstruction, CpuInstruction, LaneConstInstruction,
192        QuantumGateInstruction,
193    };
194    use crate::version::Version;
195
196    #[test]
197    fn test_round_trip_empty_program() {
198        let program = Program {
199            version: Version::new(1, 0),
200            instructions: vec![],
201        };
202        let binary = program.to_binary();
203        let decoded = Program::from_binary(&binary).unwrap();
204        assert_eq!(program, decoded);
205    }
206
207    #[test]
208    fn test_round_trip_with_instructions() {
209        let program = Program {
210            version: Version::new(2, 0),
211            instructions: vec![
212                Instruction::Cpu(CpuInstruction::ConstFloat(1.5)),
213                Instruction::Cpu(CpuInstruction::ConstInt(42)),
214                Instruction::Cpu(CpuInstruction::Dup),
215                Instruction::Array(ArrayInstruction::NewArray {
216                    type_tag: 1,
217                    dim0: 10,
218                    dim1: 20,
219                }),
220                Instruction::Array(ArrayInstruction::GetItem { ndims: 2 }),
221                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0x1234)),
222                Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity: 5 }),
223                Instruction::QuantumGate(QuantumGateInstruction::GlobalR),
224                Instruction::Cpu(CpuInstruction::Halt),
225            ],
226        };
227        let binary = program.to_binary();
228        let decoded = Program::from_binary(&binary).unwrap();
229        assert_eq!(program, decoded);
230    }
231
232    #[test]
233    fn test_bad_magic() {
234        let mut binary = Program {
235            version: Version::new(1, 0),
236            instructions: vec![],
237        }
238        .to_binary();
239        binary[0] = b'X';
240        assert_eq!(Program::from_binary(&binary), Err(ProgramError::BadMagic));
241    }
242
243    #[test]
244    fn test_truncated_header() {
245        let bytes = b"BLQD";
246        assert_eq!(
247            Program::from_binary(bytes),
248            Err(ProgramError::Truncated {
249                expected: 8,
250                got: 4,
251            })
252        );
253    }
254
255    #[test]
256    fn test_truncated_empty() {
257        let bytes = b"BL";
258        assert_eq!(
259            Program::from_binary(bytes),
260            Err(ProgramError::Truncated {
261                expected: 8,
262                got: 2,
263            })
264        );
265    }
266
267    #[test]
268    fn test_invalid_code_section_length() {
269        // Build a binary with a code section whose length is not a multiple of 16
270        let mut buf = Vec::new();
271        buf.extend_from_slice(b"BLQD");
272        buf.extend_from_slice(&2u32.to_le_bytes()); // section_count = 2
273
274        // Metadata section
275        buf.extend_from_slice(&0u32.to_le_bytes()); // type = 0
276        buf.extend_from_slice(&4u32.to_le_bytes()); // length = 4
277        buf.extend_from_slice(&1u32.to_le_bytes()); // version = 1
278
279        // Code section with bad length (5 bytes, not multiple of 16)
280        buf.extend_from_slice(&1u32.to_le_bytes()); // type = 1
281        buf.extend_from_slice(&5u32.to_le_bytes()); // length = 5
282        buf.extend_from_slice(&[0u8; 5]); // payload
283
284        assert_eq!(
285            Program::from_binary(&buf),
286            Err(ProgramError::InvalidCodeSectionLength(5))
287        );
288    }
289
290    #[test]
291    fn test_missing_code_section() {
292        // Binary with only a metadata section
293        let mut buf = Vec::new();
294        buf.extend_from_slice(b"BLQD");
295        buf.extend_from_slice(&1u32.to_le_bytes()); // section_count = 1
296
297        // Metadata section only
298        buf.extend_from_slice(&0u32.to_le_bytes());
299        buf.extend_from_slice(&4u32.to_le_bytes());
300        buf.extend_from_slice(&1u32.to_le_bytes());
301
302        assert_eq!(
303            Program::from_binary(&buf),
304            Err(ProgramError::MissingCodeSection)
305        );
306    }
307
308    #[test]
309    fn test_missing_metadata_section() {
310        // Binary with only a code section
311        let mut buf = Vec::new();
312        buf.extend_from_slice(b"BLQD");
313        buf.extend_from_slice(&1u32.to_le_bytes()); // section_count = 1
314
315        // Code section only (empty)
316        buf.extend_from_slice(&1u32.to_le_bytes());
317        buf.extend_from_slice(&0u32.to_le_bytes());
318
319        assert_eq!(
320            Program::from_binary(&buf),
321            Err(ProgramError::MissingMetadataSection)
322        );
323    }
324
325    #[test]
326    fn test_unknown_section_type() {
327        let mut buf = Vec::new();
328        buf.extend_from_slice(b"BLQD");
329        buf.extend_from_slice(&1u32.to_le_bytes()); // section_count = 1
330
331        // Unknown section type = 99
332        buf.extend_from_slice(&99u32.to_le_bytes());
333        buf.extend_from_slice(&0u32.to_le_bytes());
334
335        assert_eq!(
336            Program::from_binary(&buf),
337            Err(ProgramError::UnknownSectionType(99))
338        );
339    }
340
341    #[test]
342    fn test_binary_header_structure() {
343        let version = Version::new(1, 2);
344        let program = Program {
345            version,
346            instructions: vec![Instruction::Cpu(CpuInstruction::Halt)],
347        };
348        let binary = program.to_binary();
349
350        // Check magic
351        assert_eq!(&binary[0..4], b"BLQD");
352        // Check section count = 2
353        assert_eq!(u32::from_le_bytes(binary[4..8].try_into().unwrap()), 2);
354        // Metadata section type = 0
355        assert_eq!(u32::from_le_bytes(binary[8..12].try_into().unwrap()), 0);
356        // Metadata section length = 4
357        assert_eq!(u32::from_le_bytes(binary[12..16].try_into().unwrap()), 4);
358        // Version = packed u32
359        let expected_packed: u32 = version.into();
360        assert_eq!(
361            u32::from_le_bytes(binary[16..20].try_into().unwrap()),
362            expected_packed
363        );
364        // Code section type = 1
365        assert_eq!(u32::from_le_bytes(binary[20..24].try_into().unwrap()), 1);
366        // Code section length = 16 (one instruction)
367        assert_eq!(u32::from_le_bytes(binary[24..28].try_into().unwrap()), 16);
368    }
369}