Skip to main content

wave_emu/
decoder.rs

1// Copyright 2026 Ojima Abraham
2// SPDX-License-Identifier: Apache-2.0
3
4//! Binary instruction decoder for WBIN format. Wraps wave-decode and provides
5//!
6//! a flat `DecodedInstruction` structure for the executor to dispatch on.
7
8use crate::EmulatorError;
9
10pub use wave_decode::{
11    AtomicOp, BitOpType, CmpOp, ControlOp, CvtType, F16Op, F16PackedOp, F64DivSqrtOp, F64Op,
12    FUnaryOp, MemWidth, MiscOp, Opcode, Scope, SyncOp, WaveOpType, WaveReduceType,
13};
14
15pub use wave_decode::opcodes::{MISC_OP_FLAG, SYNC_OP_FLAG};
16
17/// Flat decoded instruction for executor dispatch
18#[derive(Debug, Clone)]
19pub struct DecodedInstruction {
20    pub opcode: Opcode,
21    pub rd: u8,
22    pub rs1: u8,
23    pub rs2: u8,
24    pub rs3: u8,
25    pub rs4: u8,
26    pub modifier: u8,
27    pub scope: u8,
28    pub pred_reg: u8,
29    pub pred_neg: bool,
30    pub flags: u8,
31    pub immediate: u32,
32    pub size: u32,
33}
34
35impl DecodedInstruction {
36    pub fn is_predicated(&self) -> bool {
37        self.pred_reg != 0 || self.pred_neg
38    }
39
40    pub fn is_sync_op(&self) -> bool {
41        self.opcode == Opcode::Control && (self.flags & SYNC_OP_FLAG) != 0
42    }
43
44    pub fn is_misc_op(&self) -> bool {
45        self.opcode == Opcode::Control && (self.flags & MISC_OP_FLAG) != 0
46    }
47
48    pub fn is_non_returning_atomic(&self) -> bool {
49        (self.opcode == Opcode::LocalAtomic || self.opcode == Opcode::DeviceAtomic) && self.rd == 0
50    }
51
52    pub fn is_wave_reduce(&self) -> bool {
53        self.opcode == Opcode::WaveOp && self.modifier >= 8
54    }
55}
56
57pub struct Decoder<'a> {
58    code: &'a [u8],
59}
60
61impl<'a> Decoder<'a> {
62    pub fn new(code: &'a [u8]) -> Self {
63        Self { code }
64    }
65
66    pub fn decode_at(&self, pc: u32) -> Result<DecodedInstruction, EmulatorError> {
67        let decoded = wave_decode::decode_at(self.code, pc).map_err(|e| {
68            EmulatorError::InvalidInstruction {
69                pc,
70                message: e.to_string(),
71            }
72        })?;
73
74        Ok(convert_instruction(&decoded))
75    }
76
77    pub fn disassemble(&self, inst: &DecodedInstruction) -> String {
78        let pred = if inst.is_predicated() {
79            let neg = if inst.pred_neg { "!" } else { "" };
80            format!("@{neg}p{} ", inst.pred_reg)
81        } else {
82            String::new()
83        };
84
85        let mnemonic = self.get_mnemonic(inst);
86        let operands = self.get_operands(inst);
87
88        if operands.is_empty() {
89            format!("{pred}{mnemonic}")
90        } else {
91            format!("{pred}{mnemonic} {operands}")
92        }
93    }
94
95    fn get_mnemonic(&self, inst: &DecodedInstruction) -> &'static str {
96        match inst.opcode {
97            Opcode::Iadd => "iadd",
98            Opcode::Isub => "isub",
99            Opcode::Imul => "imul",
100            Opcode::ImulHi => "imul_hi",
101            Opcode::Imad => "imad",
102            Opcode::Idiv => "idiv",
103            Opcode::Imod => "imod",
104            Opcode::Ineg => "ineg",
105            Opcode::Iabs => "iabs",
106            Opcode::Imin => "imin",
107            Opcode::Imax => "imax",
108            Opcode::Iclamp => "iclamp",
109            Opcode::Fadd => "fadd",
110            Opcode::Fsub => "fsub",
111            Opcode::Fmul => "fmul",
112            Opcode::Fma => "fma",
113            Opcode::Fdiv => "fdiv",
114            Opcode::Fneg => "fneg",
115            Opcode::Fabs => "fabs",
116            Opcode::Fmin => "fmin",
117            Opcode::Fmax => "fmax",
118            Opcode::Fclamp => "fclamp",
119            Opcode::Fsqrt => "fsqrt",
120            Opcode::FUnaryOps => {
121                FUnaryOp::from_u8(inst.modifier).map_or("f_unknown", |op| op.mnemonic())
122            }
123            Opcode::F16Ops => F16Op::from_u8(inst.modifier).map_or("h_unknown", |op| op.mnemonic()),
124            Opcode::F16PackedOps => {
125                F16PackedOp::from_u8(inst.modifier).map_or("h2_unknown", |op| op.mnemonic())
126            }
127            Opcode::F64Ops => F64Op::from_u8(inst.modifier).map_or("d_unknown", |op| op.mnemonic()),
128            Opcode::F64DivSqrt => {
129                F64DivSqrtOp::from_u8(inst.modifier).map_or("d_unknown", |op| op.mnemonic())
130            }
131            Opcode::And => "and",
132            Opcode::Or => "or",
133            Opcode::Xor => "xor",
134            Opcode::Not => "not",
135            Opcode::Shl => "shl",
136            Opcode::Shr => "shr",
137            Opcode::Sar => "sar",
138            Opcode::BitOps => {
139                BitOpType::from_u8(inst.modifier).map_or("bit_unknown", |op| op.mnemonic())
140            }
141            Opcode::Icmp => match inst.modifier {
142                0 => "icmp_eq",
143                1 => "icmp_ne",
144                2 => "icmp_lt",
145                3 => "icmp_le",
146                4 => "icmp_gt",
147                5 => "icmp_ge",
148                _ => "icmp_unknown",
149            },
150            Opcode::Ucmp => match inst.modifier {
151                2 => "ucmp_lt",
152                3 => "ucmp_le",
153                _ => "ucmp_unknown",
154            },
155            Opcode::Fcmp => match inst.modifier {
156                0 => "fcmp_eq",
157                1 => "fcmp_ne",
158                2 => "fcmp_lt",
159                3 => "fcmp_le",
160                4 => "fcmp_gt",
161                6 => "fcmp_ord",
162                7 => "fcmp_unord",
163                _ => "fcmp_unknown",
164            },
165            Opcode::Select => "select",
166            Opcode::Cvt => {
167                CvtType::from_u8(inst.modifier).map_or("cvt_unknown", |ct| ct.mnemonic())
168            }
169            Opcode::LocalLoad => match inst.modifier {
170                0 => "local_load_u8",
171                1 => "local_load_u16",
172                2 => "local_load_u32",
173                3 => "local_load_u64",
174                _ => "local_load_unknown",
175            },
176            Opcode::LocalStore => match inst.modifier {
177                0 => "local_store_u8",
178                1 => "local_store_u16",
179                2 => "local_store_u32",
180                3 => "local_store_u64",
181                _ => "local_store_unknown",
182            },
183            Opcode::DeviceLoad => match inst.modifier {
184                0 => "device_load_u8",
185                1 => "device_load_u16",
186                2 => "device_load_u32",
187                3 => "device_load_u64",
188                4 => "device_load_u128",
189                _ => "device_load_unknown",
190            },
191            Opcode::DeviceStore => match inst.modifier {
192                0 => "device_store_u8",
193                1 => "device_store_u16",
194                2 => "device_store_u32",
195                3 => "device_store_u64",
196                4 => "device_store_u128",
197                _ => "device_store_unknown",
198            },
199            Opcode::LocalAtomic => match inst.modifier {
200                0 => "local_atomic_add",
201                1 => "local_atomic_sub",
202                2 => "local_atomic_min",
203                3 => "local_atomic_max",
204                4 => "local_atomic_and",
205                5 => "local_atomic_or",
206                6 => "local_atomic_xor",
207                7 => "local_atomic_exchange",
208                _ => "local_atomic_cas",
209            },
210            Opcode::DeviceAtomic => match inst.modifier {
211                0 => "atomic_add",
212                1 => "atomic_sub",
213                2 => "atomic_min",
214                3 => "atomic_max",
215                4 => "atomic_and",
216                5 => "atomic_or",
217                6 => "atomic_xor",
218                7 => "atomic_exchange",
219                _ => "atomic_cas",
220            },
221            Opcode::WaveOp => {
222                if inst.is_wave_reduce() {
223                    WaveReduceType::from_u8(inst.modifier - 8)
224                        .map_or("wave_reduce_unknown", |op| op.mnemonic())
225                } else {
226                    WaveOpType::from_u8(inst.modifier).map_or("wave_unknown", |op| op.mnemonic())
227                }
228            }
229            Opcode::Control => {
230                if inst.is_sync_op() {
231                    SyncOp::from_u8(inst.modifier).map_or("sync_unknown", |op| op.mnemonic())
232                } else if inst.is_misc_op() {
233                    MiscOp::from_u8(inst.modifier).map_or("misc_unknown", |op| op.mnemonic())
234                } else {
235                    ControlOp::from_u8(inst.modifier).map_or("control_unknown", |op| op.mnemonic())
236                }
237            }
238        }
239    }
240
241    fn get_operands(&self, inst: &DecodedInstruction) -> String {
242        match inst.opcode {
243            Opcode::Iadd
244            | Opcode::Isub
245            | Opcode::Imul
246            | Opcode::ImulHi
247            | Opcode::Idiv
248            | Opcode::Imod
249            | Opcode::Imin
250            | Opcode::Imax
251            | Opcode::Fadd
252            | Opcode::Fsub
253            | Opcode::Fmul
254            | Opcode::Fdiv
255            | Opcode::Fmin
256            | Opcode::Fmax
257            | Opcode::And
258            | Opcode::Or
259            | Opcode::Xor
260            | Opcode::Shl
261            | Opcode::Shr
262            | Opcode::Sar => {
263                format!("r{}, r{}, r{}", inst.rd, inst.rs1, inst.rs2)
264            }
265            Opcode::Imad | Opcode::Iclamp | Opcode::Fma | Opcode::Fclamp => {
266                format!("r{}, r{}, r{}, r{}", inst.rd, inst.rs1, inst.rs2, inst.rs3)
267            }
268            Opcode::Ineg
269            | Opcode::Iabs
270            | Opcode::Fneg
271            | Opcode::Fabs
272            | Opcode::Fsqrt
273            | Opcode::Not => {
274                format!("r{}, r{}", inst.rd, inst.rs1)
275            }
276            Opcode::FUnaryOps => format!("r{}, r{}", inst.rd, inst.rs1),
277            Opcode::Icmp | Opcode::Ucmp | Opcode::Fcmp => {
278                format!("p{}, r{}, r{}", inst.rd, inst.rs1, inst.rs2)
279            }
280            Opcode::Select => {
281                format!(
282                    "r{}, p{}, r{}, r{}",
283                    inst.rd, inst.modifier, inst.rs1, inst.rs2
284                )
285            }
286            Opcode::Cvt => format!("r{}, r{}", inst.rd, inst.rs1),
287            Opcode::LocalLoad | Opcode::DeviceLoad => format!("r{}, r{}", inst.rd, inst.rs1),
288            Opcode::LocalStore | Opcode::DeviceStore => format!("r{}, r{}", inst.rs1, inst.rs2),
289            Opcode::LocalAtomic | Opcode::DeviceAtomic => {
290                if inst.is_non_returning_atomic() {
291                    format!("r{}, r{}", inst.rs1, inst.rs2)
292                } else {
293                    format!("r{}, r{}, r{}", inst.rd, inst.rs1, inst.rs2)
294                }
295            }
296            Opcode::WaveOp => {
297                let mnemonic = self.get_mnemonic(inst);
298                if mnemonic == "wave_ballot" {
299                    format!("r{}, p{}", inst.rd, inst.rs1)
300                } else if mnemonic == "wave_any" || mnemonic == "wave_all" {
301                    format!("p{}, p{}", inst.rd, inst.rs1)
302                } else if mnemonic.starts_with("wave_reduce") || mnemonic == "wave_prefix_sum" {
303                    format!("r{}, r{}", inst.rd, inst.rs1)
304                } else {
305                    format!("r{}, r{}, r{}", inst.rd, inst.rs1, inst.rs2)
306                }
307            }
308            Opcode::Control => {
309                if inst.is_misc_op() {
310                    match inst.modifier {
311                        0 => format!("r{}, r{}", inst.rd, inst.rs1),
312                        1 => format!("r{}, 0x{:08x}", inst.rd, inst.immediate),
313                        2 => format!("r{}, sr_{}", inst.rd, inst.rs1),
314                        _ => String::new(),
315                    }
316                } else if inst.is_sync_op() {
317                    String::new()
318                } else {
319                    match inst.modifier {
320                        0 | 4 | 5 => format!("p{}", inst.rs1),
321                        7 => format!("0x{:08x}", inst.immediate),
322                        _ => String::new(),
323                    }
324                }
325            }
326            _ => String::new(),
327        }
328    }
329}
330
331fn convert_instruction(decoded: &wave_decode::DecodedInstruction) -> DecodedInstruction {
332    use wave_decode::Operation;
333
334    let (opcode, rd, rs1, rs2, rs3, rs4, modifier, scope, flags, immediate) = match &decoded
335        .operation
336    {
337        Operation::Iadd { rd, rs1, rs2 } => (Opcode::Iadd, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
338        Operation::Isub { rd, rs1, rs2 } => (Opcode::Isub, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
339        Operation::Imul { rd, rs1, rs2 } => (Opcode::Imul, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
340        Operation::ImulHi { rd, rs1, rs2 } => (Opcode::ImulHi, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
341        Operation::Imad { rd, rs1, rs2, rs3 } => {
342            (Opcode::Imad, *rd, *rs1, *rs2, *rs3, 0, 0, 0, 0, 0)
343        }
344        Operation::Idiv { rd, rs1, rs2 } => (Opcode::Idiv, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
345        Operation::Imod { rd, rs1, rs2 } => (Opcode::Imod, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
346        Operation::Ineg { rd, rs1 } => (Opcode::Ineg, *rd, *rs1, 0, 0, 0, 0, 0, 0, 0),
347        Operation::Iabs { rd, rs1 } => (Opcode::Iabs, *rd, *rs1, 0, 0, 0, 0, 0, 0, 0),
348        Operation::Imin { rd, rs1, rs2 } => (Opcode::Imin, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
349        Operation::Imax { rd, rs1, rs2 } => (Opcode::Imax, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
350        Operation::Iclamp { rd, rs1, rs2, rs3 } => {
351            (Opcode::Iclamp, *rd, *rs1, *rs2, *rs3, 0, 0, 0, 0, 0)
352        }
353
354        Operation::Fadd { rd, rs1, rs2 } => (Opcode::Fadd, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
355        Operation::Fsub { rd, rs1, rs2 } => (Opcode::Fsub, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
356        Operation::Fmul { rd, rs1, rs2 } => (Opcode::Fmul, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
357        Operation::Fma { rd, rs1, rs2, rs3 } => (Opcode::Fma, *rd, *rs1, *rs2, *rs3, 0, 0, 0, 0, 0),
358        Operation::Fdiv { rd, rs1, rs2 } => (Opcode::Fdiv, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
359        Operation::Fneg { rd, rs1 } => (Opcode::Fneg, *rd, *rs1, 0, 0, 0, 0, 0, 0, 0),
360        Operation::Fabs { rd, rs1 } => (Opcode::Fabs, *rd, *rs1, 0, 0, 0, 0, 0, 0, 0),
361        Operation::Fmin { rd, rs1, rs2 } => (Opcode::Fmin, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
362        Operation::Fmax { rd, rs1, rs2 } => (Opcode::Fmax, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
363        Operation::Fclamp { rd, rs1, rs2, rs3 } => {
364            (Opcode::Fclamp, *rd, *rs1, *rs2, *rs3, 0, 0, 0, 0, 0)
365        }
366        Operation::Fsqrt { rd, rs1 } => (Opcode::Fsqrt, *rd, *rs1, 0, 0, 0, 0, 0, 0, 0),
367        Operation::FUnary { op, rd, rs1 } => {
368            (Opcode::FUnaryOps, *rd, *rs1, 0, 0, 0, *op as u8, 0, 0, 0)
369        }
370
371        Operation::F16 {
372            op,
373            rd,
374            rs1,
375            rs2,
376            rs3,
377        } => (
378            Opcode::F16Ops,
379            *rd,
380            *rs1,
381            *rs2,
382            rs3.unwrap_or(0),
383            0,
384            *op as u8,
385            0,
386            0,
387            0,
388        ),
389        Operation::F16Packed {
390            op,
391            rd,
392            rs1,
393            rs2,
394            rs3,
395        } => (
396            Opcode::F16PackedOps,
397            *rd,
398            *rs1,
399            *rs2,
400            rs3.unwrap_or(0),
401            0,
402            *op as u8,
403            0,
404            0,
405            0,
406        ),
407
408        Operation::F64 {
409            op,
410            rd,
411            rs1,
412            rs2,
413            rs3,
414        } => (
415            Opcode::F64Ops,
416            *rd,
417            *rs1,
418            *rs2,
419            rs3.unwrap_or(0),
420            0,
421            *op as u8,
422            0,
423            0,
424            0,
425        ),
426        Operation::F64DivSqrt { op, rd, rs1, rs2 } => (
427            Opcode::F64DivSqrt,
428            *rd,
429            *rs1,
430            rs2.unwrap_or(0),
431            0,
432            0,
433            *op as u8,
434            0,
435            0,
436            0,
437        ),
438
439        Operation::And { rd, rs1, rs2 } => (Opcode::And, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
440        Operation::Or { rd, rs1, rs2 } => (Opcode::Or, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
441        Operation::Xor { rd, rs1, rs2 } => (Opcode::Xor, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
442        Operation::Not { rd, rs1 } => (Opcode::Not, *rd, *rs1, 0, 0, 0, 0, 0, 0, 0),
443        Operation::Shl { rd, rs1, rs2 } => (Opcode::Shl, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
444        Operation::Shr { rd, rs1, rs2 } => (Opcode::Shr, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
445        Operation::Sar { rd, rs1, rs2 } => (Opcode::Sar, *rd, *rs1, *rs2, 0, 0, 0, 0, 0, 0),
446        Operation::BitOp {
447            op,
448            rd,
449            rs1,
450            rs2,
451            rs3,
452            rs4,
453        } => (
454            Opcode::BitOps,
455            *rd,
456            *rs1,
457            rs2.unwrap_or(0),
458            rs3.unwrap_or(0),
459            rs4.unwrap_or(0),
460            *op as u8,
461            0,
462            0,
463            0,
464        ),
465
466        Operation::Icmp { op, pd, rs1, rs2 } => {
467            (Opcode::Icmp, *pd, *rs1, *rs2, 0, 0, *op as u8, 0, 0, 0)
468        }
469        Operation::Ucmp { op, pd, rs1, rs2 } => {
470            (Opcode::Ucmp, *pd, *rs1, *rs2, 0, 0, *op as u8, 0, 0, 0)
471        }
472        Operation::Fcmp { op, pd, rs1, rs2 } => {
473            (Opcode::Fcmp, *pd, *rs1, *rs2, 0, 0, *op as u8, 0, 0, 0)
474        }
475
476        Operation::Select { rd, ps, rs1, rs2 } => {
477            (Opcode::Select, *rd, *rs1, *rs2, 0, 0, *ps, 0, 0, 0)
478        }
479        Operation::Cvt { cvt_type, rd, rs1 } => {
480            (Opcode::Cvt, *rd, *rs1, 0, 0, 0, *cvt_type as u8, 0, 0, 0)
481        }
482
483        Operation::LocalLoad { width, rd, addr } => (
484            Opcode::LocalLoad,
485            *rd,
486            *addr,
487            0,
488            0,
489            0,
490            *width as u8,
491            0,
492            0,
493            0,
494        ),
495        Operation::LocalStore { width, addr, value } => (
496            Opcode::LocalStore,
497            0,
498            *addr,
499            *value,
500            0,
501            0,
502            *width as u8,
503            0,
504            0,
505            0,
506        ),
507
508        Operation::DeviceLoad { width, rd, addr } => (
509            Opcode::DeviceLoad,
510            *rd,
511            *addr,
512            0,
513            0,
514            0,
515            *width as u8,
516            0,
517            0,
518            0,
519        ),
520        Operation::DeviceStore { width, addr, value } => (
521            Opcode::DeviceStore,
522            0,
523            *addr,
524            *value,
525            0,
526            0,
527            *width as u8,
528            0,
529            0,
530            0,
531        ),
532
533        Operation::LocalAtomic {
534            op,
535            rd,
536            addr,
537            value,
538        } => {
539            let rd_val = rd.unwrap_or(0);
540            (
541                Opcode::LocalAtomic,
542                rd_val,
543                *addr,
544                *value,
545                0,
546                0,
547                *op as u8,
548                0,
549                0,
550                0,
551            )
552        }
553        Operation::LocalAtomicCas {
554            rd,
555            addr,
556            expected,
557            desired,
558        } => {
559            let rd_val = rd.unwrap_or(0);
560            (
561                Opcode::LocalAtomic,
562                rd_val,
563                *addr,
564                *expected,
565                *desired,
566                0,
567                8,
568                0,
569                0,
570                0,
571            )
572        }
573        Operation::DeviceAtomic {
574            op,
575            rd,
576            addr,
577            value,
578            scope,
579        } => {
580            let rd_val = rd.unwrap_or(0);
581            (
582                Opcode::DeviceAtomic,
583                rd_val,
584                *addr,
585                *value,
586                0,
587                0,
588                *op as u8,
589                *scope as u8,
590                0,
591                0,
592            )
593        }
594        Operation::DeviceAtomicCas {
595            rd,
596            addr,
597            expected,
598            desired,
599            scope,
600        } => {
601            let rd_val = rd.unwrap_or(0);
602            (
603                Opcode::DeviceAtomic,
604                rd_val,
605                *addr,
606                *expected,
607                *desired,
608                0,
609                8,
610                *scope as u8,
611                0,
612                0,
613            )
614        }
615
616        Operation::WaveOp { op, rd, rs1, rs2 } => (
617            Opcode::WaveOp,
618            *rd,
619            *rs1,
620            rs2.unwrap_or(0),
621            0,
622            0,
623            *op as u8,
624            0,
625            0,
626            0,
627        ),
628        Operation::WaveReduce { op, rd, rs1 } => {
629            (Opcode::WaveOp, *rd, *rs1, 0, 0, 0, *op as u8 + 8, 0, 0, 0)
630        }
631        Operation::WaveBallot { rd, ps } => (
632            Opcode::WaveOp,
633            *rd,
634            *ps,
635            0,
636            0,
637            0,
638            WaveOpType::Ballot as u8,
639            0,
640            0,
641            0,
642        ),
643        Operation::WaveVote { op, pd, ps } => {
644            (Opcode::WaveOp, *pd, *ps, 0, 0, 0, *op as u8, 0, 0, 0)
645        }
646
647        Operation::If { ps } => (
648            Opcode::Control,
649            0,
650            *ps,
651            0,
652            0,
653            0,
654            ControlOp::If as u8,
655            0,
656            0,
657            0,
658        ),
659        Operation::Else => (
660            Opcode::Control,
661            0,
662            0,
663            0,
664            0,
665            0,
666            ControlOp::Else as u8,
667            0,
668            0,
669            0,
670        ),
671        Operation::Endif => (
672            Opcode::Control,
673            0,
674            0,
675            0,
676            0,
677            0,
678            ControlOp::Endif as u8,
679            0,
680            0,
681            0,
682        ),
683        Operation::Loop => (
684            Opcode::Control,
685            0,
686            0,
687            0,
688            0,
689            0,
690            ControlOp::Loop as u8,
691            0,
692            0,
693            0,
694        ),
695        Operation::Break { ps } => (
696            Opcode::Control,
697            0,
698            *ps,
699            0,
700            0,
701            0,
702            ControlOp::Break as u8,
703            0,
704            0,
705            0,
706        ),
707        Operation::Continue { ps } => (
708            Opcode::Control,
709            0,
710            *ps,
711            0,
712            0,
713            0,
714            ControlOp::Continue as u8,
715            0,
716            0,
717            0,
718        ),
719        Operation::Endloop => (
720            Opcode::Control,
721            0,
722            0,
723            0,
724            0,
725            0,
726            ControlOp::Endloop as u8,
727            0,
728            0,
729            0,
730        ),
731        Operation::Call { target } => (
732            Opcode::Control,
733            0,
734            0,
735            0,
736            0,
737            0,
738            ControlOp::Call as u8,
739            0,
740            0,
741            *target,
742        ),
743
744        Operation::Return => (
745            Opcode::Control,
746            0,
747            0,
748            0,
749            0,
750            0,
751            SyncOp::Return as u8,
752            0,
753            SYNC_OP_FLAG,
754            0,
755        ),
756        Operation::Halt => (
757            Opcode::Control,
758            0,
759            0,
760            0,
761            0,
762            0,
763            SyncOp::Halt as u8,
764            0,
765            SYNC_OP_FLAG,
766            0,
767        ),
768        Operation::Barrier => (
769            Opcode::Control,
770            0,
771            0,
772            0,
773            0,
774            0,
775            SyncOp::Barrier as u8,
776            0,
777            SYNC_OP_FLAG,
778            0,
779        ),
780        Operation::FenceAcquire { scope } => (
781            Opcode::Control,
782            0,
783            0,
784            0,
785            0,
786            0,
787            SyncOp::FenceAcquire as u8,
788            *scope as u8,
789            SYNC_OP_FLAG,
790            0,
791        ),
792        Operation::FenceRelease { scope } => (
793            Opcode::Control,
794            0,
795            0,
796            0,
797            0,
798            0,
799            SyncOp::FenceRelease as u8,
800            *scope as u8,
801            SYNC_OP_FLAG,
802            0,
803        ),
804        Operation::FenceAcqRel { scope } => (
805            Opcode::Control,
806            0,
807            0,
808            0,
809            0,
810            0,
811            SyncOp::FenceAcqRel as u8,
812            *scope as u8,
813            SYNC_OP_FLAG,
814            0,
815        ),
816        Operation::Wait => (
817            Opcode::Control,
818            0,
819            0,
820            0,
821            0,
822            0,
823            SyncOp::Wait as u8,
824            0,
825            SYNC_OP_FLAG,
826            0,
827        ),
828        Operation::Nop => (
829            Opcode::Control,
830            0,
831            0,
832            0,
833            0,
834            0,
835            SyncOp::Nop as u8,
836            0,
837            SYNC_OP_FLAG,
838            0,
839        ),
840
841        Operation::Mov { rd, rs1 } => (
842            Opcode::Control,
843            *rd,
844            *rs1,
845            0,
846            0,
847            0,
848            MiscOp::Mov as u8,
849            0,
850            MISC_OP_FLAG,
851            0,
852        ),
853        Operation::MovImm { rd, imm } => (
854            Opcode::Control,
855            *rd,
856            0,
857            0,
858            0,
859            0,
860            MiscOp::MovImm as u8,
861            0,
862            MISC_OP_FLAG,
863            *imm,
864        ),
865        Operation::MovSr { rd, sr_index } => (
866            Opcode::Control,
867            *rd,
868            *sr_index,
869            0,
870            0,
871            0,
872            MiscOp::MovSr as u8,
873            0,
874            MISC_OP_FLAG,
875            0,
876        ),
877
878        Operation::Unknown {
879            opcode,
880            word0,
881            word1: _,
882        } => {
883            let opc = Opcode::from_u8(*opcode).unwrap_or(Opcode::Control);
884            (opc, 0, 0, 0, 0, 0, 0, 0, 0, *word0)
885        }
886    };
887
888    DecodedInstruction {
889        opcode,
890        rd,
891        rs1,
892        rs2,
893        rs3,
894        rs4,
895        modifier,
896        scope,
897        pred_reg: decoded.predicate,
898        pred_neg: decoded.predicate_negated,
899        flags,
900        immediate,
901        size: u32::from(decoded.size),
902    }
903}
904
905#[cfg(test)]
906mod tests {
907    use super::*;
908
909    fn encode_base(opcode: u8, rd: u8, rs1: u8, rs2: u8, modifier: u8, flags: u8) -> Vec<u8> {
910        let word = ((u32::from(opcode) & 0x3F) << 26)
911            | ((u32::from(rd) & 0x1F) << 21)
912            | ((u32::from(rs1) & 0x1F) << 16)
913            | ((u32::from(rs2) & 0x1F) << 11)
914            | ((u32::from(modifier) & 0x0F) << 7)
915            | (u32::from(flags) & 0x03);
916        word.to_le_bytes().to_vec()
917    }
918
919    #[test]
920    fn test_decoder_iadd() {
921        let code = encode_base(0x00, 1, 2, 3, 0, 0);
922        let decoder = Decoder::new(&code);
923        let inst = decoder.decode_at(0).unwrap();
924
925        assert_eq!(inst.opcode, Opcode::Iadd);
926        assert_eq!(inst.rd, 1);
927        assert_eq!(inst.rs1, 2);
928        assert_eq!(inst.rs2, 3);
929        assert_eq!(inst.size, 4);
930    }
931
932    #[test]
933    fn test_decoder_halt() {
934        let code = encode_base(0x3F, 0, 0, 0, 1, SYNC_OP_FLAG);
935        let decoder = Decoder::new(&code);
936        let inst = decoder.decode_at(0).unwrap();
937
938        assert_eq!(inst.opcode, Opcode::Control);
939        assert!(inst.is_sync_op());
940        assert_eq!(inst.modifier, 1);
941    }
942
943    #[test]
944    fn test_decoder_mov_imm() {
945        let word0 = encode_base(0x3F, 5, 0, 0, 1, MISC_OP_FLAG);
946        let word1 = 0x12345678u32;
947
948        let mut code = word0;
949        code.extend_from_slice(&word1.to_le_bytes());
950
951        let decoder = Decoder::new(&code);
952        let inst = decoder.decode_at(0).unwrap();
953
954        assert_eq!(inst.opcode, Opcode::Control);
955        assert!(inst.is_misc_op());
956        assert_eq!(inst.rd, 5);
957        assert_eq!(inst.immediate, 0x12345678);
958        assert_eq!(inst.size, 8);
959    }
960
961    #[test]
962    fn test_decoder_disassemble() {
963        let code = encode_base(0x00, 1, 2, 3, 0, 0);
964        let decoder = Decoder::new(&code);
965        let inst = decoder.decode_at(0).unwrap();
966        let disasm = decoder.disassemble(&inst);
967
968        assert_eq!(disasm, "iadd r1, r2, r3");
969    }
970
971    #[test]
972    fn test_decoder_out_of_bounds() {
973        let code = vec![0u8; 2];
974        let decoder = Decoder::new(&code);
975        assert!(decoder.decode_at(0).is_err());
976    }
977}