bloqade_lanes_bytecode_core/bytecode/
encode.rs

1use std::fmt;
2
3use super::instruction::{
4    ArrayInstruction, AtomArrangementInstruction, CpuInstruction, DetectorObservableInstruction,
5    Instruction, LaneConstInstruction, MeasurementInstruction, QuantumGateInstruction,
6};
7use super::opcode::{OpCodeCategory, decode_opcode};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum DecodeError {
11    UnknownOpcode(u16),
12    UnknownDeviceCode(u8),
13    InvalidOperand { opcode: u16, message: String },
14}
15
16impl fmt::Display for DecodeError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            DecodeError::UnknownOpcode(op) => write!(f, "unknown opcode: 0x{:04x}", op),
20            DecodeError::UnknownDeviceCode(dc) => {
21                write!(f, "unknown device code: 0x{:02x}", dc)
22            }
23            DecodeError::InvalidOperand { opcode, message } => {
24                write!(
25                    f,
26                    "invalid operand for opcode 0x{:04x}: {}",
27                    opcode, message
28                )
29            }
30        }
31    }
32}
33
34impl std::error::Error for DecodeError {}
35
36impl Instruction {
37    /// Encode instruction into (opcode, data0, data1, data2).
38    ///
39    /// Opcode word layout: `[unused:16][instruction_code:8][device_code:8]`
40    pub fn encode(&self) -> (u32, u32, u32, u32) {
41        let opcode = self.opcode() as u32;
42
43        match self {
44            // ConstFloat: f64 LE across data0 (low) and data1 (high)
45            Instruction::Cpu(CpuInstruction::ConstFloat(f)) => {
46                let bits = f.to_bits();
47                let data0 = bits as u32;
48                let data1 = (bits >> 32) as u32;
49                (opcode, data0, data1, 0)
50            }
51            // ConstInt: i64 LE across data0 (low) and data1 (high)
52            Instruction::Cpu(CpuInstruction::ConstInt(v)) => {
53                let bits = *v as u64;
54                let data0 = bits as u32;
55                let data1 = (bits >> 32) as u32;
56                (opcode, data0, data1, 0)
57            }
58            // ConstLoc: LocationAddr in data0
59            Instruction::LaneConst(LaneConstInstruction::ConstLoc(v)) => (opcode, *v, 0, 0),
60            // ConstLane: two u32 words (data0, data1)
61            Instruction::LaneConst(LaneConstInstruction::ConstLane(d0, d1)) => {
62                (opcode, *d0, *d1, 0)
63            }
64            // ConstZone: ZoneAddr in data0
65            Instruction::LaneConst(LaneConstInstruction::ConstZone(v)) => (opcode, *v, 0, 0),
66
67            // Simple CPU ops: no data
68            Instruction::Cpu(CpuInstruction::Pop)
69            | Instruction::Cpu(CpuInstruction::Dup)
70            | Instruction::Cpu(CpuInstruction::Swap)
71            | Instruction::Cpu(CpuInstruction::Return)
72            | Instruction::Cpu(CpuInstruction::Halt) => (opcode, 0, 0, 0),
73
74            // NewArray: data0 = [tag:8][pad:8][dim0:16], data1 = [pad:16][dim1:16]
75            Instruction::Array(ArrayInstruction::NewArray {
76                type_tag,
77                dim0,
78                dim1,
79            }) => {
80                let data0 = ((*type_tag as u32) << 24) | (*dim0 as u32);
81                let data1 = *dim1 as u32;
82                (opcode, data0, data1, 0)
83            }
84
85            // GetItem: data0 = ndims
86            Instruction::Array(ArrayInstruction::GetItem { ndims }) => {
87                (opcode, *ndims as u32, 0, 0)
88            }
89
90            // Device ops with arity: data0 = arity
91            Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity })
92            | Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity })
93            | Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity })
94            | Instruction::QuantumGate(QuantumGateInstruction::LocalR { arity })
95            | Instruction::QuantumGate(QuantumGateInstruction::LocalRz { arity })
96            | Instruction::Measurement(MeasurementInstruction::Measure { arity }) => {
97                (opcode, *arity, 0, 0)
98            }
99
100            // Simple device ops: no data
101            Instruction::QuantumGate(QuantumGateInstruction::GlobalR)
102            | Instruction::QuantumGate(QuantumGateInstruction::GlobalRz)
103            | Instruction::QuantumGate(QuantumGateInstruction::CZ)
104            | Instruction::Measurement(MeasurementInstruction::AwaitMeasure)
105            | Instruction::DetectorObservable(DetectorObservableInstruction::SetDetector)
106            | Instruction::DetectorObservable(DetectorObservableInstruction::SetObservable) => {
107                (opcode, 0, 0, 0)
108            }
109        }
110    }
111
112    /// Decode an instruction from (opcode_word, data0, data1, data2).
113    pub fn decode(word: u32, data0: u32, data1: u32, _data2: u32) -> Result<Self, DecodeError> {
114        let category = decode_opcode(word)?;
115
116        match category {
117            OpCodeCategory::Cpu(cpu_op) => {
118                use super::opcode::CpuInstCode::*;
119                match cpu_op {
120                    ConstFloat => {
121                        let bits = (data0 as u64) | ((data1 as u64) << 32);
122                        Ok(Instruction::Cpu(CpuInstruction::ConstFloat(
123                            f64::from_bits(bits),
124                        )))
125                    }
126                    ConstInt => {
127                        let bits = (data0 as u64) | ((data1 as u64) << 32);
128                        Ok(Instruction::Cpu(CpuInstruction::ConstInt(bits as i64)))
129                    }
130                    Pop => Ok(Instruction::Cpu(CpuInstruction::Pop)),
131                    Dup => Ok(Instruction::Cpu(CpuInstruction::Dup)),
132                    Swap => Ok(Instruction::Cpu(CpuInstruction::Swap)),
133                    Return => Ok(Instruction::Cpu(CpuInstruction::Return)),
134                    Halt => Ok(Instruction::Cpu(CpuInstruction::Halt)),
135                }
136            }
137            OpCodeCategory::LaneConstants(lc_op) => {
138                use super::opcode::LaneConstInstCode::*;
139                match lc_op {
140                    ConstLoc => Ok(Instruction::LaneConst(LaneConstInstruction::ConstLoc(
141                        data0,
142                    ))),
143                    ConstLane => Ok(Instruction::LaneConst(LaneConstInstruction::ConstLane(
144                        data0, data1,
145                    ))),
146                    ConstZone => Ok(Instruction::LaneConst(LaneConstInstruction::ConstZone(
147                        data0,
148                    ))),
149                }
150            }
151            OpCodeCategory::AtomArrangement(aa_op) => {
152                use super::opcode::AtomArrangementInstCode::*;
153                let arity = data0;
154                match aa_op {
155                    InitialFill => Ok(Instruction::AtomArrangement(
156                        AtomArrangementInstruction::InitialFill { arity },
157                    )),
158                    Fill => Ok(Instruction::AtomArrangement(
159                        AtomArrangementInstruction::Fill { arity },
160                    )),
161                    Move => Ok(Instruction::AtomArrangement(
162                        AtomArrangementInstruction::Move { arity },
163                    )),
164                }
165            }
166            OpCodeCategory::QuantumGate(qg_op) => {
167                use super::opcode::QuantumGateInstCode::*;
168                match qg_op {
169                    LocalR => Ok(Instruction::QuantumGate(QuantumGateInstruction::LocalR {
170                        arity: data0,
171                    })),
172                    LocalRz => Ok(Instruction::QuantumGate(QuantumGateInstruction::LocalRz {
173                        arity: data0,
174                    })),
175                    GlobalR => Ok(Instruction::QuantumGate(QuantumGateInstruction::GlobalR)),
176                    GlobalRz => Ok(Instruction::QuantumGate(QuantumGateInstruction::GlobalRz)),
177                    CZ => Ok(Instruction::QuantumGate(QuantumGateInstruction::CZ)),
178                }
179            }
180            OpCodeCategory::Measurement(m_op) => {
181                use super::opcode::MeasurementInstCode::*;
182                match m_op {
183                    Measure => Ok(Instruction::Measurement(MeasurementInstruction::Measure {
184                        arity: data0,
185                    })),
186                    AwaitMeasure => Ok(Instruction::Measurement(
187                        MeasurementInstruction::AwaitMeasure,
188                    )),
189                }
190            }
191            OpCodeCategory::Array(arr_op) => {
192                use super::opcode::ArrayInstCode::*;
193                match arr_op {
194                    NewArray => {
195                        let type_tag = ((data0 >> 24) & 0xFF) as u8;
196                        let dim0 = (data0 & 0xFFFF) as u16;
197                        let dim1 = (data1 & 0xFFFF) as u16;
198                        Ok(Instruction::Array(ArrayInstruction::NewArray {
199                            type_tag,
200                            dim0,
201                            dim1,
202                        }))
203                    }
204                    GetItem => Ok(Instruction::Array(ArrayInstruction::GetItem {
205                        ndims: data0 as u16,
206                    })),
207                }
208            }
209            OpCodeCategory::DetectorObservable(dob_op) => {
210                use super::opcode::DetectorObservableInstCode::*;
211                match dob_op {
212                    SetDetector => Ok(Instruction::DetectorObservable(
213                        DetectorObservableInstruction::SetDetector,
214                    )),
215                    SetObservable => Ok(Instruction::DetectorObservable(
216                        DetectorObservableInstruction::SetObservable,
217                    )),
218                }
219            }
220        }
221    }
222
223    /// Encode to 16 little-endian bytes.
224    pub fn to_bytes(&self) -> [u8; 16] {
225        let (word, data0, data1, data2) = self.encode();
226        let mut bytes = [0u8; 16];
227        bytes[0..4].copy_from_slice(&word.to_le_bytes());
228        bytes[4..8].copy_from_slice(&data0.to_le_bytes());
229        bytes[8..12].copy_from_slice(&data1.to_le_bytes());
230        bytes[12..16].copy_from_slice(&data2.to_le_bytes());
231        bytes
232    }
233
234    /// Decode from 16 little-endian bytes.
235    pub fn from_bytes(bytes: &[u8; 16]) -> Result<Self, DecodeError> {
236        let word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
237        let data0 = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
238        let data1 = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
239        let data2 = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
240        Self::decode(word, data0, data1, data2)
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    fn round_trip(instr: Instruction) {
249        let (word, data0, data1, data2) = instr.encode();
250        let decoded = Instruction::decode(word, data0, data1, data2).unwrap();
251        assert_eq!(instr, decoded, "round-trip failed for {:?}", instr);
252    }
253
254    fn round_trip_bytes(instr: Instruction) {
255        let bytes = instr.to_bytes();
256        let decoded = Instruction::from_bytes(&bytes).unwrap();
257        assert_eq!(instr, decoded, "bytes round-trip failed for {:?}", instr);
258    }
259
260    #[test]
261    fn test_round_trip_all_instructions() {
262        let instructions = vec![
263            Instruction::Cpu(CpuInstruction::ConstFloat(1.5)),
264            Instruction::Cpu(CpuInstruction::ConstInt(-42)),
265            Instruction::Cpu(CpuInstruction::Pop),
266            Instruction::Cpu(CpuInstruction::Dup),
267            Instruction::Cpu(CpuInstruction::Swap),
268            Instruction::Cpu(CpuInstruction::Return),
269            Instruction::Cpu(CpuInstruction::Halt),
270            Instruction::LaneConst(LaneConstInstruction::ConstLoc(0x1234)),
271            Instruction::LaneConst(LaneConstInstruction::ConstLane(0x00125678, 0xC0009ABC)),
272            Instruction::LaneConst(LaneConstInstruction::ConstZone(0x9ABC)),
273            Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 4 }),
274            Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity: 3 }),
275            Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 2 }),
276            Instruction::QuantumGate(QuantumGateInstruction::LocalR { arity: 1 }),
277            Instruction::QuantumGate(QuantumGateInstruction::LocalRz { arity: 5 }),
278            Instruction::QuantumGate(QuantumGateInstruction::GlobalR),
279            Instruction::QuantumGate(QuantumGateInstruction::GlobalRz),
280            Instruction::QuantumGate(QuantumGateInstruction::CZ),
281            Instruction::Measurement(MeasurementInstruction::Measure { arity: 10 }),
282            Instruction::Measurement(MeasurementInstruction::AwaitMeasure),
283            Instruction::Array(ArrayInstruction::NewArray {
284                type_tag: 0x2,
285                dim0: 100,
286                dim1: 200,
287            }),
288            Instruction::Array(ArrayInstruction::GetItem { ndims: 2 }),
289            Instruction::DetectorObservable(DetectorObservableInstruction::SetDetector),
290            Instruction::DetectorObservable(DetectorObservableInstruction::SetObservable),
291        ];
292
293        for instr in &instructions {
294            round_trip(*instr);
295            round_trip_bytes(*instr);
296        }
297    }
298
299    #[test]
300    fn test_const_float_bit_pattern() {
301        let instr = Instruction::Cpu(CpuInstruction::ConstFloat(1.0));
302        let (word, data0, data1, _data2) = instr.encode();
303        assert_eq!(word, 0x0300); // opcode: device 0x00, inst 0x03
304        let bits = 1.0_f64.to_bits();
305        assert_eq!(data0, bits as u32);
306        assert_eq!(data1, (bits >> 32) as u32);
307    }
308
309    #[test]
310    fn test_const_int_bit_pattern() {
311        let instr = Instruction::Cpu(CpuInstruction::ConstInt(42));
312        let (word, data0, data1, _data2) = instr.encode();
313        assert_eq!(word, 0x0200); // opcode: device 0x00, inst 0x02
314        assert_eq!(data0, 42);
315        assert_eq!(data1, 0);
316    }
317
318    #[test]
319    fn test_const_int_negative() {
320        let instr = Instruction::Cpu(CpuInstruction::ConstInt(-1));
321        let (word, data0, data1, _data2) = instr.encode();
322        assert_eq!(word, 0x0200);
323        assert_eq!(data0, 0xFFFF_FFFF); // low 32 bits of -1i64
324        assert_eq!(data1, 0xFFFF_FFFF); // high 32 bits of -1i64
325        let decoded = Instruction::decode(word, data0, data1, 0).unwrap();
326        assert_eq!(decoded, instr);
327    }
328
329    #[test]
330    fn test_new_array_bit_pattern() {
331        let instr = Instruction::Array(ArrayInstruction::NewArray {
332            type_tag: 0x2,
333            dim0: 10,
334            dim1: 20,
335        });
336        let (word, data0, data1, _data2) = instr.encode();
337        assert_eq!(word, 0x0013); // opcode: device 0x13, inst 0x00
338        assert_eq!((data0 >> 24) & 0xFF, 0x2); // type_tag
339        assert_eq!(data0 & 0xFFFF, 10); // dim0
340        assert_eq!(data1 & 0xFFFF, 20); // dim1
341    }
342
343    #[test]
344    fn test_unknown_opcode_error() {
345        // device 0x01 is reserved/unknown
346        let result = Instruction::decode(0x0001, 0, 0, 0);
347        assert!(matches!(result, Err(DecodeError::UnknownDeviceCode(0x01))));
348    }
349
350    #[test]
351    fn test_to_bytes_little_endian() {
352        let instr = Instruction::Cpu(CpuInstruction::Halt);
353        let bytes = instr.to_bytes();
354        assert_eq!(bytes.len(), 16);
355        // opcode 0xFF00 in LE: byte[0]=0x00 (device), byte[1]=0xFF (inst)
356        assert_eq!(bytes[0], 0x00); // device code (Cpu)
357        assert_eq!(bytes[1], 0xFF); // instruction code (Halt)
358        assert_eq!(bytes[2], 0);
359        assert_eq!(bytes[3], 0);
360        assert_eq!(&bytes[4..], &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
361    }
362
363    #[test]
364    fn test_to_bytes_device_instruction() {
365        let instr =
366            Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 3 });
367        let bytes = instr.to_bytes();
368        assert_eq!(bytes.len(), 16);
369        // opcode 0x0010 in LE: byte[0]=0x10 (device), byte[1]=0x00 (inst)
370        assert_eq!(bytes[0], 0x10); // device code (AtomArrangement)
371        assert_eq!(bytes[1], 0x00); // instruction code (InitialFill)
372        assert_eq!(bytes[2], 0);
373        assert_eq!(bytes[3], 0);
374        // data0: arity 3 in LE
375        assert_eq!(bytes[4], 3);
376        assert_eq!(bytes[5], 0);
377    }
378}