Skip to main content

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