1use vihaco::Instruction;
11
12pub const INSTRUCTION_WIDTH: u32 = 17;
26
27#[derive(Debug, Clone, PartialEq, Instruction, vihaco_parser::Parse)]
41#[instruction(width = 17)]
42pub enum Instruction {
43 #[token = "pop"]
45 Pop,
46 #[token = "swap"]
47 Swap,
48 #[token = "return"]
49 Return,
50
51 #[token = "const_loc"]
53 #[delimiters(open = "", close = "", separator = "")]
54 ConstLoc(#[parse_with = "crate::isa::parse_helpers::hex_u64"] u64),
55
56 #[token = "const_lane"]
57 #[delimiters(open = "", close = "", separator = "")]
58 ConstLane(#[parse_with = "crate::isa::parse_helpers::hex_u64"] u64),
59
60 #[token = "const_zone"]
61 #[delimiters(open = "", close = "", separator = "")]
62 ConstZone(#[parse_with = "crate::isa::parse_helpers::hex_u32"] u32),
63
64 #[token = "initial_fill"]
66 #[delimiters(open = "", close = "", separator = "")]
67 InitialFill(u32),
68 #[token = "fill"]
69 #[delimiters(open = "", close = "", separator = "")]
70 Fill(u32),
71 #[token = "move"]
72 #[delimiters(open = "", close = "", separator = "")]
73 Move(u32),
74
75 #[token = "local_rz"]
77 #[delimiters(open = "", close = "", separator = "")]
78 LocalRz(u32),
79 #[token = "local_r"]
80 #[delimiters(open = "", close = "", separator = "")]
81 LocalR(u32),
82 #[token = "global_rz"]
83 GlobalRz,
84 #[token = "global_r"]
85 GlobalR,
86 #[token = "cz"]
87 Cz,
88
89 #[token = "measure"]
91 #[delimiters(open = "", close = "", separator = "")]
92 Measure(u32),
93 #[token = "await_measure"]
94 AwaitMeasure,
95
96 #[token = "new_array"]
100 #[delimiters(open = "", close = "", separator = " ")]
101 NewArray(u32, u32, u32),
102 #[token = "get_item"]
103 #[delimiters(open = "", close = "", separator = "")]
104 GetItem(u32),
105
106 #[token = "set_detector"]
108 SetDetector,
109 #[token = "set_observable"]
110 SetObservable,
111
112 #[delegate]
116 Cpu(vihaco_cpu::Instruction),
117}
118
119impl Instruction {
120 pub fn op_name(&self) -> &'static str {
129 match self {
130 Instruction::Pop => "pop",
131 Instruction::Swap => "swap",
132 Instruction::Return => "return",
133 Instruction::ConstLoc(_) => "const_loc",
134 Instruction::ConstLane(_) => "const_lane",
135 Instruction::ConstZone(_) => "const_zone",
136 Instruction::InitialFill(_) => "initial_fill",
137 Instruction::Fill(_) => "fill",
138 Instruction::Move(_) => "move",
139 Instruction::LocalRz(_) => "local_rz",
140 Instruction::LocalR(_) => "local_r",
141 Instruction::GlobalRz => "global_rz",
142 Instruction::GlobalR => "global_r",
143 Instruction::Cz => "cz",
144 Instruction::Measure(_) => "measure",
145 Instruction::AwaitMeasure => "await_measure",
146 Instruction::NewArray(..) => "new_array",
147 Instruction::GetItem(_) => "get_item",
148 Instruction::SetDetector => "set_detector",
149 Instruction::SetObservable => "set_observable",
150 Instruction::Cpu(cpu) => cpu_op_name(cpu),
151 }
152 }
153}
154
155fn cpu_op_name(cpu: &vihaco_cpu::Instruction) -> &'static str {
158 use vihaco::value::Value;
159 use vihaco_cpu::Instruction as Cpu;
160 match cpu {
161 Cpu::Const(Value::F64(_)) => "const_float",
162 Cpu::Const(Value::I64(_)) => "const_int",
163 Cpu::Const(_) => "const",
164 Cpu::Dup => "dup",
165 Cpu::Halt => "halt",
166 _ => "cpu",
167 }
168}
169
170impl std::fmt::Display for Instruction {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 Instruction::Pop => f.write_str("pop"),
174 Instruction::Swap => f.write_str("swap"),
175 Instruction::Return => f.write_str("return"),
176 Instruction::ConstLoc(v) => write!(f, "const_loc 0x{v:016x}"),
177 Instruction::ConstLane(v) => write!(f, "const_lane 0x{v:016x}"),
178 Instruction::ConstZone(v) => write!(f, "const_zone 0x{v:08x}"),
179 Instruction::InitialFill(a) => write!(f, "initial_fill {a}"),
180 Instruction::Fill(a) => write!(f, "fill {a}"),
181 Instruction::Move(a) => write!(f, "move {a}"),
182 Instruction::LocalRz(a) => write!(f, "local_rz {a}"),
183 Instruction::LocalR(a) => write!(f, "local_r {a}"),
184 Instruction::GlobalRz => f.write_str("global_rz"),
185 Instruction::GlobalR => f.write_str("global_r"),
186 Instruction::Cz => f.write_str("cz"),
187 Instruction::Measure(a) => write!(f, "measure {a}"),
188 Instruction::AwaitMeasure => f.write_str("await_measure"),
189 Instruction::NewArray(t, d0, d1) => write!(f, "new_array {t} {d0} {d1}"),
190 Instruction::GetItem(n) => write!(f, "get_item {n}"),
191 Instruction::SetDetector => f.write_str("set_detector"),
192 Instruction::SetObservable => f.write_str("set_observable"),
193 Instruction::Cpu(cpu) => write!(f, "{cpu}"),
195 }
196 }
197}
198
199#[cfg(test)]
200#[allow(clippy::approx_constant)] mod tests {
202 use super::*;
203 use chumsky::Parser as _;
204 use vihaco::instruction::{FromBytes, OpCode, WriteBytes};
205 use vihaco::value::Value;
206 use vihaco_cpu::Instruction as Cpu;
207 use vihaco_parser_core::Parse;
208
209 fn parse(input: &str) -> Instruction {
210 Instruction::parser()
211 .parse(input)
212 .into_result()
213 .unwrap_or_else(|e| panic!("parse({input:?}) failed: {e:?}"))
214 }
215
216 fn sample_program() -> Vec<Instruction> {
219 vec![
220 Instruction::Cpu(Cpu::Const(Value::F64(3.14159))),
221 Instruction::Cpu(Cpu::Const(Value::I64(-42))),
222 Instruction::Cpu(Cpu::Dup),
223 Instruction::ConstLoc(0x0000_0000_0100_0000),
224 Instruction::ConstLane(0x0000_0000_0000_0001),
225 Instruction::ConstZone(0x0000_0007),
226 Instruction::InitialFill(2),
227 Instruction::Fill(1),
228 Instruction::Move(2),
229 Instruction::LocalRz(1),
230 Instruction::LocalR(3),
231 Instruction::GlobalRz,
232 Instruction::GlobalR,
233 Instruction::Cz,
234 Instruction::Measure(1),
235 Instruction::AwaitMeasure,
236 Instruction::NewArray(1, 3, 0),
237 Instruction::GetItem(1),
238 Instruction::SetDetector,
239 Instruction::SetObservable,
240 Instruction::Pop,
241 Instruction::Swap,
242 Instruction::Cpu(Cpu::Halt),
243 Instruction::Return,
244 ]
245 }
246
247 #[test]
248 fn every_instruction_encodes_to_fixed_width() {
249 assert_eq!(Instruction::width(), INSTRUCTION_WIDTH);
250 for inst in sample_program() {
251 let mut buf = Vec::new();
252 inst.write_bytes(&mut buf).unwrap();
253 assert_eq!(
254 buf.len(),
255 INSTRUCTION_WIDTH as usize,
256 "{inst:?} did not encode to a full {INSTRUCTION_WIDTH}-byte word"
257 );
258 }
259 }
260
261 #[test]
262 fn binary_round_trips_a_flat_program() {
263 let program = sample_program();
264
265 let mut bytes = Vec::new();
267 for inst in &program {
268 inst.write_bytes(&mut bytes).unwrap();
269 }
270 assert_eq!(bytes.len(), program.len() * INSTRUCTION_WIDTH as usize);
271
272 let mut cursor = std::io::Cursor::new(bytes);
275 let mut decoded = Vec::new();
276 for _ in 0..program.len() {
277 decoded.push(Instruction::from_bytes(&mut cursor).unwrap());
278 }
279 assert_eq!(decoded, program);
280 }
281
282 #[test]
283 fn text_parses_each_shape() {
284 assert_eq!(
285 parse("const_loc 0x0000000001000000"),
286 Instruction::ConstLoc(0x0000_0000_0100_0000)
287 );
288 assert_eq!(
289 parse("const_lane 0x0000000000000001"),
290 Instruction::ConstLane(1)
291 );
292 assert_eq!(parse("const_zone 0x00000007"), Instruction::ConstZone(7));
293 assert_eq!(parse("initial_fill 2"), Instruction::InitialFill(2));
294 assert_eq!(parse("move 2"), Instruction::Move(2));
295 assert_eq!(parse("new_array 1 3 0"), Instruction::NewArray(1, 3, 0));
296 assert_eq!(parse("get_item 1"), Instruction::GetItem(1));
297
298 for (text, inst) in [
299 ("pop", Instruction::Pop),
300 ("swap", Instruction::Swap),
301 ("return", Instruction::Return),
302 ("global_rz", Instruction::GlobalRz),
303 ("global_r", Instruction::GlobalR),
304 ("cz", Instruction::Cz),
305 ("await_measure", Instruction::AwaitMeasure),
306 ("set_detector", Instruction::SetDetector),
307 ("set_observable", Instruction::SetObservable),
308 ] {
309 assert_eq!(parse(text), inst, "text {text:?}");
310 }
311 }
312
313 #[test]
314 fn cpu_ops_delegate_to_vihaco_cpu() {
315 assert_eq!(
318 parse("const.i64 42"),
319 Instruction::Cpu(Cpu::Const(Value::I64(42)))
320 );
321 assert_eq!(
322 parse("const.f64 1.5"),
323 Instruction::Cpu(Cpu::Const(Value::F64(1.5)))
324 );
325 assert_eq!(parse("dup"), Instruction::Cpu(Cpu::Dup));
326 assert_eq!(parse("halt"), Instruction::Cpu(Cpu::Halt));
327 }
328
329 #[test]
330 fn device_token_wins_over_delegated_cpu() {
331 assert_eq!(parse("get_item 2"), Instruction::GetItem(2));
334 }
335
336 #[test]
337 fn prefix_tokens_disambiguate() {
338 assert_eq!(parse("local_rz 1"), Instruction::LocalRz(1));
340 assert_eq!(parse("local_r 3"), Instruction::LocalR(3));
341 assert_eq!(parse("global_rz"), Instruction::GlobalRz);
342 assert_eq!(parse("global_r"), Instruction::GlobalR);
343 }
344
345 #[test]
346 fn text_then_binary_agree() {
347 let from_text = parse("const_loc 0x0000000001000000");
348 let mut bytes = Vec::new();
349 from_text.write_bytes(&mut bytes).unwrap();
350 let decoded = Instruction::from_bytes(&mut std::io::Cursor::new(bytes)).unwrap();
351 assert_eq!(from_text, decoded);
352 }
353
354 #[test]
355 fn op_name_matches_display_leading_token_for_native_ops() {
356 let native = [
360 Instruction::Pop,
361 Instruction::Swap,
362 Instruction::Return,
363 Instruction::ConstLoc(0),
364 Instruction::ConstLane(0),
365 Instruction::ConstZone(0),
366 Instruction::InitialFill(1),
367 Instruction::Fill(1),
368 Instruction::Move(1),
369 Instruction::LocalRz(1),
370 Instruction::LocalR(1),
371 Instruction::GlobalRz,
372 Instruction::GlobalR,
373 Instruction::Cz,
374 Instruction::Measure(1),
375 Instruction::AwaitMeasure,
376 Instruction::NewArray(1, 1, 0),
377 Instruction::GetItem(1),
378 Instruction::SetDetector,
379 Instruction::SetObservable,
380 ];
381 for inst in native {
382 let display_head = inst.to_string();
383 let head = display_head.split_whitespace().next().unwrap();
384 assert_eq!(inst.op_name(), head, "op_name/Display drift for {inst:?}");
385 }
386 }
387
388 #[test]
389 fn cpu_op_name_uses_decode_handler_names() {
390 assert_eq!(
391 Instruction::Cpu(Cpu::Const(Value::F64(0.0))).op_name(),
392 "const_float"
393 );
394 assert_eq!(
395 Instruction::Cpu(Cpu::Const(Value::I64(0))).op_name(),
396 "const_int"
397 );
398 assert_eq!(Instruction::Cpu(Cpu::Dup).op_name(), "dup");
399 assert_eq!(Instruction::Cpu(Cpu::Halt).op_name(), "halt");
400 }
401
402 #[test]
403 fn cpu_op_name_falls_back_for_other_variants() {
404 assert_eq!(
407 Instruction::Cpu(Cpu::Const(Value::Bool(true))).op_name(),
408 "const"
409 );
410 assert_eq!(Instruction::Cpu(Cpu::Print).op_name(), "cpu");
412 }
413
414 #[test]
415 fn display_matches_parser_tokens() {
416 use std::string::ToString;
417 let samples = [
419 Instruction::ConstLoc(0x0100_0000),
420 Instruction::Move(2),
421 Instruction::LocalRz(1),
422 Instruction::GlobalR,
423 Instruction::NewArray(1, 3, 0),
424 Instruction::Pop,
425 Instruction::Return,
426 Instruction::Cpu(Cpu::Const(Value::F64(1.5))),
427 Instruction::Cpu(Cpu::Halt),
428 ];
429 for inst in samples {
430 let text = inst.to_string();
431 assert_eq!(parse(&text), inst, "Display/parse mismatch for {text:?}");
432 }
433 }
434}