Skip to main content

javm_exec/
instruction.rs

1//! RV64+C+custom-0 instruction decoder for PVM2.
2//!
3//! Decodes a 16-bit-aligned cursor into `(Inst, byte_length)`.
4//! Compressed (RVC) instructions are **decompressed at decode time**
5//! into their 32-bit equivalents so the rest of the pipeline sees
6//! uniform `Inst` values; the returned `byte_length` (2 or 4) is
7//! the wire length, used to advance PC.
8//!
9//! ISA coverage matches the PVM2 spec
10//! (`~/docs/pvm-isa/05-pvm2-rv-diff.md` and `06-pvm2-pvm-diff.md`):
11//!
12//!   RV64I base  +  M  +  C  +  Zbb  +  Zba  +  Zbs  +  Zicond  +  Zicclsm
13//!   + custom-0 ops:  trap  /  ecall.jar  /  ecalli
14//!
15//! Forbidden encodings (per PVM2-Base divergences):
16//!
17//! - Standard ECALL / EBREAK (RV `SYSTEM` major) — decoder returns
18//!   `Reserved`. PVM2's ecall lives in custom-0 instead.
19//! - CSR ops, atomics, FP, vector — `Reserved`.
20//! - Any reg field of x16..x31 — `Reserved`; x3/x4 are valid RV64E
21//!   registers and route through the spilled-register path.
22
23#![allow(dead_code)]
24
25/// Decoded RV instruction in named-variant form.
26///
27/// One variant per RV operation we accept. Field layout is per the
28/// RV unprivileged spec. Immediate fields are pre-sign-extended to
29/// `i32`.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Inst {
32    // -------- Loads (I-type, major LOAD=0000011) --------
33    Lb {
34        rd: u8,
35        rs1: u8,
36        imm: i32,
37    },
38    Lh {
39        rd: u8,
40        rs1: u8,
41        imm: i32,
42    },
43    Lw {
44        rd: u8,
45        rs1: u8,
46        imm: i32,
47    },
48    Ld {
49        rd: u8,
50        rs1: u8,
51        imm: i32,
52    },
53    Lbu {
54        rd: u8,
55        rs1: u8,
56        imm: i32,
57    },
58    Lhu {
59        rd: u8,
60        rs1: u8,
61        imm: i32,
62    },
63    Lwu {
64        rd: u8,
65        rs1: u8,
66        imm: i32,
67    },
68
69    // -------- Stores (S-type, major STORE=0100011) --------
70    Sb {
71        rs1: u8,
72        rs2: u8,
73        imm: i32,
74    },
75    Sh {
76        rs1: u8,
77        rs2: u8,
78        imm: i32,
79    },
80    Sw {
81        rs1: u8,
82        rs2: u8,
83        imm: i32,
84    },
85    Sd {
86        rs1: u8,
87        rs2: u8,
88        imm: i32,
89    },
90
91    // -------- ALU with immediate (I-type) --------
92    // 64-bit (major OP-IMM = 0010011)
93    Addi {
94        rd: u8,
95        rs1: u8,
96        imm: i32,
97    },
98    Slti {
99        rd: u8,
100        rs1: u8,
101        imm: i32,
102    },
103    Sltiu {
104        rd: u8,
105        rs1: u8,
106        imm: i32,
107    },
108    Andi {
109        rd: u8,
110        rs1: u8,
111        imm: i32,
112    },
113    Ori {
114        rd: u8,
115        rs1: u8,
116        imm: i32,
117    },
118    Xori {
119        rd: u8,
120        rs1: u8,
121        imm: i32,
122    },
123    Slli {
124        rd: u8,
125        rs1: u8,
126        shamt: u8,
127    },
128    Srli {
129        rd: u8,
130        rs1: u8,
131        shamt: u8,
132    },
133    Srai {
134        rd: u8,
135        rs1: u8,
136        shamt: u8,
137    },
138    // 32-bit (major OP-IMM-32 = 0011011)
139    Addiw {
140        rd: u8,
141        rs1: u8,
142        imm: i32,
143    },
144    Slliw {
145        rd: u8,
146        rs1: u8,
147        shamt: u8,
148    },
149    Srliw {
150        rd: u8,
151        rs1: u8,
152        shamt: u8,
153    },
154    Sraiw {
155        rd: u8,
156        rs1: u8,
157        shamt: u8,
158    },
159
160    // -------- ALU register-register (R-type) --------
161    // 64-bit (major OP = 0110011)
162    Add {
163        rd: u8,
164        rs1: u8,
165        rs2: u8,
166    },
167    Sub {
168        rd: u8,
169        rs1: u8,
170        rs2: u8,
171    },
172    Sll {
173        rd: u8,
174        rs1: u8,
175        rs2: u8,
176    },
177    Srl {
178        rd: u8,
179        rs1: u8,
180        rs2: u8,
181    },
182    Sra {
183        rd: u8,
184        rs1: u8,
185        rs2: u8,
186    },
187    Slt {
188        rd: u8,
189        rs1: u8,
190        rs2: u8,
191    },
192    Sltu {
193        rd: u8,
194        rs1: u8,
195        rs2: u8,
196    },
197    Xor {
198        rd: u8,
199        rs1: u8,
200        rs2: u8,
201    },
202    Or {
203        rd: u8,
204        rs1: u8,
205        rs2: u8,
206    },
207    And {
208        rd: u8,
209        rs1: u8,
210        rs2: u8,
211    },
212    // 32-bit (major OP-32 = 0111011)
213    Addw {
214        rd: u8,
215        rs1: u8,
216        rs2: u8,
217    },
218    Subw {
219        rd: u8,
220        rs1: u8,
221        rs2: u8,
222    },
223    Sllw {
224        rd: u8,
225        rs1: u8,
226        rs2: u8,
227    },
228    Srlw {
229        rd: u8,
230        rs1: u8,
231        rs2: u8,
232    },
233    Sraw {
234        rd: u8,
235        rs1: u8,
236        rs2: u8,
237    },
238
239    // -------- M extension --------
240    Mul {
241        rd: u8,
242        rs1: u8,
243        rs2: u8,
244    },
245    Mulh {
246        rd: u8,
247        rs1: u8,
248        rs2: u8,
249    },
250    Mulhsu {
251        rd: u8,
252        rs1: u8,
253        rs2: u8,
254    },
255    Mulhu {
256        rd: u8,
257        rs1: u8,
258        rs2: u8,
259    },
260    Div {
261        rd: u8,
262        rs1: u8,
263        rs2: u8,
264    },
265    Divu {
266        rd: u8,
267        rs1: u8,
268        rs2: u8,
269    },
270    Rem {
271        rd: u8,
272        rs1: u8,
273        rs2: u8,
274    },
275    Remu {
276        rd: u8,
277        rs1: u8,
278        rs2: u8,
279    },
280    Mulw {
281        rd: u8,
282        rs1: u8,
283        rs2: u8,
284    },
285    Divw {
286        rd: u8,
287        rs1: u8,
288        rs2: u8,
289    },
290    Divuw {
291        rd: u8,
292        rs1: u8,
293        rs2: u8,
294    },
295    Remw {
296        rd: u8,
297        rs1: u8,
298        rs2: u8,
299    },
300    Remuw {
301        rd: u8,
302        rs1: u8,
303        rs2: u8,
304    },
305
306    // -------- Zbb (basic bit manipulation) --------
307    Clz {
308        rd: u8,
309        rs1: u8,
310    },
311    Clzw {
312        rd: u8,
313        rs1: u8,
314    },
315    Ctz {
316        rd: u8,
317        rs1: u8,
318    },
319    Ctzw {
320        rd: u8,
321        rs1: u8,
322    },
323    Cpop {
324        rd: u8,
325        rs1: u8,
326    },
327    Cpopw {
328        rd: u8,
329        rs1: u8,
330    },
331    SextB {
332        rd: u8,
333        rs1: u8,
334    },
335    SextH {
336        rd: u8,
337        rs1: u8,
338    },
339    ZextH {
340        rd: u8,
341        rs1: u8,
342    }, // canonical encoding: pack rd, rs1, x0
343    Rev8 {
344        rd: u8,
345        rs1: u8,
346    },
347    OrcB {
348        rd: u8,
349        rs1: u8,
350    },
351    Min {
352        rd: u8,
353        rs1: u8,
354        rs2: u8,
355    },
356    Minu {
357        rd: u8,
358        rs1: u8,
359        rs2: u8,
360    },
361    Max {
362        rd: u8,
363        rs1: u8,
364        rs2: u8,
365    },
366    Maxu {
367        rd: u8,
368        rs1: u8,
369        rs2: u8,
370    },
371    Andn {
372        rd: u8,
373        rs1: u8,
374        rs2: u8,
375    },
376    Orn {
377        rd: u8,
378        rs1: u8,
379        rs2: u8,
380    },
381    Xnor {
382        rd: u8,
383        rs1: u8,
384        rs2: u8,
385    },
386    Rol {
387        rd: u8,
388        rs1: u8,
389        rs2: u8,
390    },
391    Ror {
392        rd: u8,
393        rs1: u8,
394        rs2: u8,
395    },
396    Rolw {
397        rd: u8,
398        rs1: u8,
399        rs2: u8,
400    },
401    Rorw {
402        rd: u8,
403        rs1: u8,
404        rs2: u8,
405    },
406    Rori {
407        rd: u8,
408        rs1: u8,
409        shamt: u8,
410    },
411    Roriw {
412        rd: u8,
413        rs1: u8,
414        shamt: u8,
415    },
416
417    // -------- Zba (shift-add) --------
418    Sh1add {
419        rd: u8,
420        rs1: u8,
421        rs2: u8,
422    },
423    Sh2add {
424        rd: u8,
425        rs1: u8,
426        rs2: u8,
427    },
428    Sh3add {
429        rd: u8,
430        rs1: u8,
431        rs2: u8,
432    },
433    Sh1adduw {
434        rd: u8,
435        rs1: u8,
436        rs2: u8,
437    },
438    Sh2adduw {
439        rd: u8,
440        rs1: u8,
441        rs2: u8,
442    },
443    Sh3adduw {
444        rd: u8,
445        rs1: u8,
446        rs2: u8,
447    },
448    Adduw {
449        rd: u8,
450        rs1: u8,
451        rs2: u8,
452    },
453    Slliuw {
454        rd: u8,
455        rs1: u8,
456        shamt: u8,
457    },
458
459    // -------- Zbs (single-bit) --------
460    Bclr {
461        rd: u8,
462        rs1: u8,
463        rs2: u8,
464    },
465    Bset {
466        rd: u8,
467        rs1: u8,
468        rs2: u8,
469    },
470    Binv {
471        rd: u8,
472        rs1: u8,
473        rs2: u8,
474    },
475    Bext {
476        rd: u8,
477        rs1: u8,
478        rs2: u8,
479    },
480    Bclri {
481        rd: u8,
482        rs1: u8,
483        shamt: u8,
484    },
485    Bseti {
486        rd: u8,
487        rs1: u8,
488        shamt: u8,
489    },
490    Binvi {
491        rd: u8,
492        rs1: u8,
493        shamt: u8,
494    },
495    Bexti {
496        rd: u8,
497        rs1: u8,
498        shamt: u8,
499    },
500
501    // -------- Zicond --------
502    CzeroEqz {
503        rd: u8,
504        rs1: u8,
505        rs2: u8,
506    },
507    CzeroNez {
508        rd: u8,
509        rs1: u8,
510        rs2: u8,
511    },
512
513    // -------- Upper immediate --------
514    Lui {
515        rd: u8,
516        imm: i32,
517    },
518    /// `auipc rd, imm` — `rd = pc + (imm << 12)`. With code mapped at
519    /// CODE_BASE, `pc` is the guest VA, so the recompiler folds this
520    /// to a compile-time constant. `imm` holds the already-shifted
521    /// upper-20 value (same shape as `Lui`).
522    Auipc {
523        rd: u8,
524        imm: i32,
525    },
526
527    // -------- Control flow --------
528    /// `jal rd, off` — `rd = pc + sizeof(jal)`; `pc = pc + off`. Used
529    /// for static jumps (`rd = x0`, = `c.j`) and direct calls
530    /// (`rd = ra`, saving the return address natively).
531    Jal {
532        rd: u8,
533        imm: i32,
534    },
535    /// `jalr rd, rs1, imm` — `rd = pc + sizeof`;
536    /// `target_va = (rs1 + imm) & 0xFFFF_FFFF` (32-bit wrap). The
537    /// runtime validates the target is a basic-block start (else Panic)
538    /// and dispatches. Used for returns (`jalr x0, ra, 0`) and
539    /// indirect calls.
540    Jalr {
541        rd: u8,
542        rs1: u8,
543        imm: i32,
544    },
545    Beq {
546        rs1: u8,
547        rs2: u8,
548        imm: i32,
549    },
550    Bne {
551        rs1: u8,
552        rs2: u8,
553        imm: i32,
554    },
555    Blt {
556        rs1: u8,
557        rs2: u8,
558        imm: i32,
559    },
560    Bge {
561        rs1: u8,
562        rs2: u8,
563        imm: i32,
564    },
565    Bltu {
566        rs1: u8,
567        rs2: u8,
568        imm: i32,
569    },
570    Bgeu {
571        rs1: u8,
572        rs2: u8,
573        imm: i32,
574    },
575
576    // -------- System (RV-defined, all no-ops or reserved in PVM2) --------
577    /// `fence` — decoded but treated as no-op (PVM2 is single-threaded).
578    Fence,
579    /// `fence.i` — same.
580    FenceI,
581
582    // -------- Custom-0 (PVM2-jar host ops) --------
583    /// `custom-0` funct3=000: unconditional execution abort.
584    Trap,
585    /// `custom-0` funct3=001: jar management op / dynamic CALL.
586    EcallJar,
587    /// `custom-0` funct3=010: host-call with 20-bit signed immediate.
588    Ecalli {
589        imm: i32,
590    },
591    /// `custom-0` funct3=100: terminator no-op. Acts as a basic-block
592    /// start at the next byte. Linker injects this before branch / call
593    /// targets that aren't naturally post-terminator.
594    Fallthrough,
595
596    // -------- Sentinel for forbidden encodings --------
597    /// Decoder accepted the wire bits but the encoding is reserved
598    /// by PVM2 (AUIPC, standard ECALL/EBREAK, CSR, atomics, FP, …).
599    /// Programs containing this are rejected at deblob.
600    Reserved {
601        raw: u32,
602    },
603}
604
605impl Inst {
606    /// True iff this instruction names a reserved register in any of its
607    /// register fields. The valid PVM2 registers are `x0, x1..x15` (RV64E's
608    /// full file, including `x3`/`x4`); reserved are only `x16..x31`, which
609    /// do not exist in RV64E — a 16-register base — so naming one is an
610    /// illegal, standard-RV encoding. Such an instruction decodes as
611    /// `Reserved` and panics if executed (lazy). Immediate bits that happen
612    /// to alias a reserved register are **not** registers and are ignored —
613    /// each variant binds exactly its real register fields. This match is
614    /// the single source of truth both consensus engines route through (the
615    /// interpreter via [`decode`], the recompiler via
616    /// [`word_uses_reserved_reg`]); the lack of a wildcard arm means a
617    /// future `Inst` variant won't compile until classified.
618    pub fn uses_reserved_reg(&self) -> bool {
619        use crate::regs::reg_is_reserved as r;
620        use Inst::*;
621        match *self {
622            // Register-register (rd, rs1, rs2).
623            Add { rd, rs1, rs2 }
624            | Sub { rd, rs1, rs2 }
625            | Sll { rd, rs1, rs2 }
626            | Srl { rd, rs1, rs2 }
627            | Sra { rd, rs1, rs2 }
628            | Slt { rd, rs1, rs2 }
629            | Sltu { rd, rs1, rs2 }
630            | Xor { rd, rs1, rs2 }
631            | Or { rd, rs1, rs2 }
632            | And { rd, rs1, rs2 }
633            | Addw { rd, rs1, rs2 }
634            | Subw { rd, rs1, rs2 }
635            | Sllw { rd, rs1, rs2 }
636            | Srlw { rd, rs1, rs2 }
637            | Sraw { rd, rs1, rs2 }
638            | Mul { rd, rs1, rs2 }
639            | Mulh { rd, rs1, rs2 }
640            | Mulhsu { rd, rs1, rs2 }
641            | Mulhu { rd, rs1, rs2 }
642            | Div { rd, rs1, rs2 }
643            | Divu { rd, rs1, rs2 }
644            | Rem { rd, rs1, rs2 }
645            | Remu { rd, rs1, rs2 }
646            | Mulw { rd, rs1, rs2 }
647            | Divw { rd, rs1, rs2 }
648            | Divuw { rd, rs1, rs2 }
649            | Remw { rd, rs1, rs2 }
650            | Remuw { rd, rs1, rs2 }
651            | Min { rd, rs1, rs2 }
652            | Minu { rd, rs1, rs2 }
653            | Max { rd, rs1, rs2 }
654            | Maxu { rd, rs1, rs2 }
655            | Andn { rd, rs1, rs2 }
656            | Orn { rd, rs1, rs2 }
657            | Xnor { rd, rs1, rs2 }
658            | Rol { rd, rs1, rs2 }
659            | Ror { rd, rs1, rs2 }
660            | Rolw { rd, rs1, rs2 }
661            | Rorw { rd, rs1, rs2 }
662            | Sh1add { rd, rs1, rs2 }
663            | Sh2add { rd, rs1, rs2 }
664            | Sh3add { rd, rs1, rs2 }
665            | Sh1adduw { rd, rs1, rs2 }
666            | Sh2adduw { rd, rs1, rs2 }
667            | Sh3adduw { rd, rs1, rs2 }
668            | Adduw { rd, rs1, rs2 }
669            | Bclr { rd, rs1, rs2 }
670            | Bset { rd, rs1, rs2 }
671            | Binv { rd, rs1, rs2 }
672            | Bext { rd, rs1, rs2 }
673            | CzeroEqz { rd, rs1, rs2 }
674            | CzeroNez { rd, rs1, rs2 } => r(rd) || r(rs1) || r(rs2),
675
676            // rd + rs1 (loads, I-type ALU, shifts, unary Zbb, jalr).
677            Lb { rd, rs1, .. }
678            | Lh { rd, rs1, .. }
679            | Lw { rd, rs1, .. }
680            | Ld { rd, rs1, .. }
681            | Lbu { rd, rs1, .. }
682            | Lhu { rd, rs1, .. }
683            | Lwu { rd, rs1, .. }
684            | Addi { rd, rs1, .. }
685            | Slti { rd, rs1, .. }
686            | Sltiu { rd, rs1, .. }
687            | Andi { rd, rs1, .. }
688            | Ori { rd, rs1, .. }
689            | Xori { rd, rs1, .. }
690            | Slli { rd, rs1, .. }
691            | Srli { rd, rs1, .. }
692            | Srai { rd, rs1, .. }
693            | Addiw { rd, rs1, .. }
694            | Slliw { rd, rs1, .. }
695            | Srliw { rd, rs1, .. }
696            | Sraiw { rd, rs1, .. }
697            | Clz { rd, rs1 }
698            | Clzw { rd, rs1 }
699            | Ctz { rd, rs1 }
700            | Ctzw { rd, rs1 }
701            | Cpop { rd, rs1 }
702            | Cpopw { rd, rs1 }
703            | SextB { rd, rs1 }
704            | SextH { rd, rs1 }
705            | ZextH { rd, rs1 }
706            | Rev8 { rd, rs1 }
707            | OrcB { rd, rs1 }
708            | Rori { rd, rs1, .. }
709            | Roriw { rd, rs1, .. }
710            | Slliuw { rd, rs1, .. }
711            | Bclri { rd, rs1, .. }
712            | Bseti { rd, rs1, .. }
713            | Binvi { rd, rs1, .. }
714            | Bexti { rd, rs1, .. }
715            | Jalr { rd, rs1, .. } => r(rd) || r(rs1),
716
717            // rs1 + rs2 (stores, branches).
718            Sb { rs1, rs2, .. }
719            | Sh { rs1, rs2, .. }
720            | Sw { rs1, rs2, .. }
721            | Sd { rs1, rs2, .. }
722            | Beq { rs1, rs2, .. }
723            | Bne { rs1, rs2, .. }
724            | Blt { rs1, rs2, .. }
725            | Bge { rs1, rs2, .. }
726            | Bltu { rs1, rs2, .. }
727            | Bgeu { rs1, rs2, .. } => r(rs1) || r(rs2),
728
729            // rd only (upper-immediate, jal).
730            Lui { rd, .. } | Auipc { rd, .. } | Jal { rd, .. } => r(rd),
731
732            // No register fields.
733            Fence | FenceI | Trap | EcallJar | Ecalli { .. } | Fallthrough | Reserved { .. } => {
734                false
735            }
736        }
737    }
738}
739
740/// Apply a per-register-index predicate `pred` to every field a raw 4-byte
741/// word uses *as a register* (per its format), ignoring immediate bits that
742/// happen to alias a register field. The shared core behind
743/// [`word_uses_reserved_reg`] and [`word_uses_spilled_reg`] — keeping the
744/// format → register-field mapping in **one** place so the two predicates
745/// can't disagree on which positions are registers.
746///
747/// `#[inline]` so it folds into the recompiler's `compile_rv4` across the
748/// crate boundary (the field extractions then CSE against the ones
749/// `compile_rv4` already does).
750#[inline]
751fn word_reg_class_hits(w: u32, pred: fn(u8) -> bool) -> bool {
752    let r = |x: u32| pred(x as u8);
753    let rd = (w >> 7) & 0x1F;
754    let rs1 = (w >> 15) & 0x1F;
755    let rs2 = (w >> 20) & 0x1F;
756    match w & 0x7F {
757        // R-type (OP, OP-32): rd, rs1, rs2.
758        0b011_0011 | 0b011_1011 => r(rd) || r(rs1) || r(rs2),
759        // I-type (LOAD, OP-IMM, OP-IMM-32, JALR): rd, rs1.
760        0b000_0011 | 0b001_0011 | 0b001_1011 | 0b110_0111 => r(rd) || r(rs1),
761        // S-type (STORE), B-type (BRANCH): rs1, rs2.
762        0b010_0011 | 0b110_0011 => r(rs1) || r(rs2),
763        // U-type (LUI, AUIPC), J-type (JAL): rd.
764        0b011_0111 | 0b001_0111 | 0b110_1111 => r(rd),
765        // SYSTEM, MISC-MEM, custom-0/1, atomics, FP, … — no PVM2 register
766        // field (reserved or no-reg); they panic via the normal path.
767        _ => false,
768    }
769}
770
771/// Reserved-register predicate on a raw 4-byte word, for the recompiler
772/// (which dispatches on raw words rather than building an [`Inst`], and
773/// runs on the cold-compile hot path — so this must not re-decode).
774///
775/// True iff the word names `x16..x31` (which do not exist in RV64E) in a
776/// real register field. For every word this equals
777/// [`Inst::uses_reserved_reg`] (pinned by
778/// `word_predicate_matches_inst_predicate`), so the interpreter and
779/// recompiler agree on which encodings are illegal and panic.
780#[inline]
781pub fn word_uses_reserved_reg(w: u32) -> bool {
782    word_reg_class_hits(w, crate::regs::reg_is_reserved)
783}
784
785/// Spilled-register predicate on a raw 4-byte word, for the recompiler.
786///
787/// True iff the word names `x3` or `x4` (the two host-spilled GPRs) in a
788/// real register field. The recompiler tests this *after*
789/// [`word_uses_reserved_reg`] to route an `x3`/`x4` instruction to its cold
790/// spill path; the interpreter needs no equivalent (it executes `x3`/`x4`
791/// as ordinary slot-13/14 GPRs). For every conformant word the toolchain
792/// emits this is `false`, so the recompiler's fast path is untouched.
793#[inline]
794pub fn word_uses_spilled_reg(w: u32) -> bool {
795    word_reg_class_hits(w, crate::regs::reg_is_spilled)
796}
797
798/// Major opcode of a 32-bit RV instruction is bits [6:2]; bits [1:0]
799/// are always `11` for non-compressed.
800const OP_LOAD: u32 = 0b00_000;
801const OP_STORE: u32 = 0b01_000;
802const OP_MADD: u32 = 0b10_000; // reserved (FP)
803const OP_BRANCH: u32 = 0b11_000;
804
805const OP_LOAD_FP: u32 = 0b00_001; // reserved (FP)
806const OP_STORE_FP: u32 = 0b01_001; // reserved (FP)
807const OP_JALR: u32 = 0b11_001;
808
809const OP_CUSTOM_0: u32 = 0b00_010;
810const OP_CUSTOM_1: u32 = 0b01_010; // reserved
811const OP_CUSTOM_2_OR_RV128: u32 = 0b10_010; // reserved
812// Note bit 11000 is shared with BRANCH
813
814const OP_MISC_MEM: u32 = 0b00_011;
815const OP_AMO: u32 = 0b01_011; // reserved (A)
816const OP_JAL: u32 = 0b11_011;
817
818const OP_IMM: u32 = 0b00_100;
819const OP_OP: u32 = 0b01_100;
820const OP_OP_FP: u32 = 0b10_100; // reserved (FP)
821const OP_SYSTEM: u32 = 0b11_100;
822
823const OP_AUIPC: u32 = 0b00_101;
824const OP_LUI: u32 = 0b01_101;
825const OP_OP_IMM_32: u32 = 0b00_110;
826const OP_OP_32: u32 = 0b01_110;
827
828/// Decode a single instruction starting at `bytes[0]`.
829///
830/// Returns `(inst, byte_length)` where byte_length is 2 (compressed)
831/// or 4 (standard). Returns `None` if fewer than 2 bytes are
832/// available or the first 2 bytes are zero (which is `Reserved`
833/// shape — we treat as decode failure to give the caller a clean
834/// signal).
835pub fn decode(bytes: &[u8]) -> Option<(Inst, u8)> {
836    if bytes.len() < 2 {
837        return None;
838    }
839    let lo16 = u16::from_le_bytes([bytes[0], bytes[1]]);
840    // Length encoding: bits [1:0] of byte 0.
841    //   xx11  -> 32-bit (or longer; we don't support >32)
842    //   else  -> 16-bit compressed
843    if lo16 & 0b11 != 0b11 {
844        // An instruction naming a reserved register (x3/x4, or x16..x31
845        // which don't exist in RV64E) is a reserved encoding → `Reserved`,
846        // so it panics if executed (lazy), matching the recompiler.
847        let inst = decompress(lo16);
848        let inst = reserve_if_reserved_reg(inst, lo16 as u32);
849        return Some((inst, 2));
850    }
851    if bytes.len() < 4 {
852        return None;
853    }
854    let w = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
855    Some((reserve_if_reserved_reg(decode_32(w), w), 4))
856}
857
858/// Map an instruction that names a reserved register (`x3`/`x4` or the
859/// nonexistent `x16..x31`) to `Reserved` so it is refused lazily, like any
860/// other reserved encoding.
861#[inline]
862fn reserve_if_reserved_reg(inst: Inst, raw: u32) -> Inst {
863    if inst.uses_reserved_reg() {
864        Inst::Reserved { raw }
865    } else {
866        inst
867    }
868}
869
870// ============================================================================
871// 32-bit decode
872// ============================================================================
873
874fn decode_32(w: u32) -> Inst {
875    let major = (w >> 2) & 0x1F; // [6:2]
876    if w & 0b11 != 0b11 {
877        return Inst::Reserved { raw: w };
878    }
879    let rd = ((w >> 7) & 0x1F) as u8;
880    let rs1 = ((w >> 15) & 0x1F) as u8;
881    let rs2 = ((w >> 20) & 0x1F) as u8;
882    let funct3 = ((w >> 12) & 0x07) as u8;
883    let funct7 = ((w >> 25) & 0x7F) as u8;
884
885    match major {
886        OP_LOAD => decode_load(w, rd, rs1, funct3),
887        OP_STORE => decode_store(w, rs1, rs2, funct3),
888        OP_IMM => decode_op_imm(w, rd, rs1, funct3),
889        OP_OP_IMM_32 => decode_op_imm_32(w, rd, rs1, funct3),
890        OP_OP => decode_op(rd, rs1, rs2, funct3, funct7, w),
891        OP_OP_32 => decode_op_32(rd, rs1, rs2, funct3, funct7, w),
892        OP_LUI => Inst::Lui {
893            rd,
894            imm: (w & 0xFFFFF000) as i32,
895        },
896        OP_AUIPC => Inst::Auipc {
897            rd,
898            imm: (w & 0xFFFFF000) as i32,
899        },
900        OP_JAL => {
901            let imm = imm_j(w);
902            Inst::Jal { rd, imm }
903        }
904        // jalr is I-type with funct3=000; other funct3 are reserved.
905        OP_JALR if funct3 == 0 => Inst::Jalr {
906            rd,
907            rs1,
908            imm: imm_i(w),
909        },
910        OP_BRANCH => decode_branch(rs1, rs2, funct3, imm_b(w), w),
911        OP_MISC_MEM => decode_misc_mem(funct3),
912        OP_SYSTEM => Inst::Reserved { raw: w }, // standard ECALL/EBREAK/CSR reserved
913        OP_CUSTOM_0 => decode_custom_0(w, rd, rs1, funct3),
914        OP_CUSTOM_1 => Inst::Reserved { raw: w }, // custom-1 reserved (was callf, now removed)
915        _ => Inst::Reserved { raw: w },
916    }
917}
918
919fn decode_load(w: u32, rd: u8, rs1: u8, funct3: u8) -> Inst {
920    let imm = imm_i(w);
921    match funct3 {
922        0b000 => Inst::Lb { rd, rs1, imm },
923        0b001 => Inst::Lh { rd, rs1, imm },
924        0b010 => Inst::Lw { rd, rs1, imm },
925        0b011 => Inst::Ld { rd, rs1, imm },
926        0b100 => Inst::Lbu { rd, rs1, imm },
927        0b101 => Inst::Lhu { rd, rs1, imm },
928        0b110 => Inst::Lwu { rd, rs1, imm },
929        _ => Inst::Reserved { raw: w },
930    }
931}
932
933fn decode_store(w: u32, rs1: u8, rs2: u8, funct3: u8) -> Inst {
934    let imm = imm_s(w);
935    match funct3 {
936        0b000 => Inst::Sb { rs1, rs2, imm },
937        0b001 => Inst::Sh { rs1, rs2, imm },
938        0b010 => Inst::Sw { rs1, rs2, imm },
939        0b011 => Inst::Sd { rs1, rs2, imm },
940        _ => Inst::Reserved { raw: w },
941    }
942}
943
944fn decode_op_imm(w: u32, rd: u8, rs1: u8, funct3: u8) -> Inst {
945    let imm = imm_i(w);
946    let shamt = (w >> 20) & 0x3F; // 6-bit for RV64 shifts
947    let shtype = (w >> 26) & 0x3F; // upper 6 bits for shift type
948    match funct3 {
949        0b000 => Inst::Addi { rd, rs1, imm },
950        0b010 => Inst::Slti { rd, rs1, imm },
951        0b011 => Inst::Sltiu { rd, rs1, imm },
952        0b100 => Inst::Xori { rd, rs1, imm },
953        0b110 => Inst::Ori { rd, rs1, imm },
954        0b111 => Inst::Andi { rd, rs1, imm },
955        0b001 => match shtype {
956            0b000000 => Inst::Slli {
957                rd,
958                rs1,
959                shamt: shamt as u8,
960            },
961            // Zbs: bclri (funct7[6:1]=010010), bseti (001010), binvi (011010)
962            0b010010 => Inst::Bclri {
963                rd,
964                rs1,
965                shamt: shamt as u8,
966            },
967            0b001010 => Inst::Bseti {
968                rd,
969                rs1,
970                shamt: shamt as u8,
971            },
972            0b011010 => Inst::Binvi {
973                rd,
974                rs1,
975                shamt: shamt as u8,
976            },
977            // Zbb: clz/ctz/cpop/sext.b/sext.h (funct7=0110000), variant in rs2
978            0b011000 => match rs2_field(w) {
979                0b00000 => Inst::Clz { rd, rs1 },
980                0b00001 => Inst::Ctz { rd, rs1 },
981                0b00010 => Inst::Cpop { rd, rs1 },
982                0b00100 => Inst::SextB { rd, rs1 },
983                0b00101 => Inst::SextH { rd, rs1 },
984                _ => Inst::Reserved { raw: w },
985            },
986            _ => Inst::Reserved { raw: w },
987        },
988        0b101 => match shtype {
989            0b000000 => Inst::Srli {
990                rd,
991                rs1,
992                shamt: shamt as u8,
993            },
994            0b010000 => Inst::Srai {
995                rd,
996                rs1,
997                shamt: shamt as u8,
998            },
999            // Zbs: bexti (010010)
1000            0b010010 => Inst::Bexti {
1001                rd,
1002                rs1,
1003                shamt: shamt as u8,
1004            },
1005            // Zbb: rori (011000)
1006            0b011000 => Inst::Rori {
1007                rd,
1008                rs1,
1009                shamt: shamt as u8,
1010            },
1011            // Zbb: orc.b / rev8 — distinguished by rs2 field
1012            0b001010 => match rs2_field(w) {
1013                0b00111 => Inst::OrcB { rd, rs1 },
1014                _ => Inst::Reserved { raw: w },
1015            },
1016            0b011010 => match rs2_field(w) {
1017                0b11000 => Inst::Rev8 { rd, rs1 },
1018                _ => Inst::Reserved { raw: w },
1019            },
1020            _ => Inst::Reserved { raw: w },
1021        },
1022        _ => Inst::Reserved { raw: w },
1023    }
1024}
1025
1026fn decode_op_imm_32(w: u32, rd: u8, rs1: u8, funct3: u8) -> Inst {
1027    let imm = imm_i(w);
1028    let shamt5 = ((w >> 20) & 0x1F) as u8; // 5-bit for W shifts
1029    let funct7 = (w >> 25) & 0x7F;
1030    match funct3 {
1031        0b000 => Inst::Addiw { rd, rs1, imm },
1032        0b001 => match funct7 {
1033            0b0000000 => Inst::Slliw {
1034                rd,
1035                rs1,
1036                shamt: shamt5,
1037            },
1038            // Zba: slli.uw (funct7=0000100)
1039            0b0000100 => Inst::Slliuw {
1040                rd,
1041                rs1,
1042                shamt: ((w >> 20) & 0x3F) as u8,
1043            },
1044            // Zbb: clzw/ctzw/cpopw (funct7=0110000, rs2 varies)
1045            0b0110000 => match rs2_field(w) {
1046                0b00000 => Inst::Clzw { rd, rs1 },
1047                0b00001 => Inst::Ctzw { rd, rs1 },
1048                0b00010 => Inst::Cpopw { rd, rs1 },
1049                _ => Inst::Reserved { raw: w },
1050            },
1051            _ => Inst::Reserved { raw: w },
1052        },
1053        0b101 => match funct7 {
1054            0b0000000 => Inst::Srliw {
1055                rd,
1056                rs1,
1057                shamt: shamt5,
1058            },
1059            0b0100000 => Inst::Sraiw {
1060                rd,
1061                rs1,
1062                shamt: shamt5,
1063            },
1064            // Zbb: roriw (funct7=0110000)
1065            0b0110000 => Inst::Roriw {
1066                rd,
1067                rs1,
1068                shamt: shamt5,
1069            },
1070            _ => Inst::Reserved { raw: w },
1071        },
1072        _ => Inst::Reserved { raw: w },
1073    }
1074}
1075
1076fn decode_op(rd: u8, rs1: u8, rs2: u8, funct3: u8, funct7: u8, w: u32) -> Inst {
1077    match (funct7, funct3) {
1078        // Base 64-bit ALU
1079        (0b0000000, 0b000) => Inst::Add { rd, rs1, rs2 },
1080        (0b0100000, 0b000) => Inst::Sub { rd, rs1, rs2 },
1081        (0b0000000, 0b001) => Inst::Sll { rd, rs1, rs2 },
1082        (0b0000000, 0b010) => Inst::Slt { rd, rs1, rs2 },
1083        (0b0000000, 0b011) => Inst::Sltu { rd, rs1, rs2 },
1084        (0b0000000, 0b100) => Inst::Xor { rd, rs1, rs2 },
1085        (0b0000000, 0b101) => Inst::Srl { rd, rs1, rs2 },
1086        (0b0100000, 0b101) => Inst::Sra { rd, rs1, rs2 },
1087        (0b0000000, 0b110) => Inst::Or { rd, rs1, rs2 },
1088        (0b0000000, 0b111) => Inst::And { rd, rs1, rs2 },
1089        // M extension
1090        (0b0000001, 0b000) => Inst::Mul { rd, rs1, rs2 },
1091        (0b0000001, 0b001) => Inst::Mulh { rd, rs1, rs2 },
1092        (0b0000001, 0b010) => Inst::Mulhsu { rd, rs1, rs2 },
1093        (0b0000001, 0b011) => Inst::Mulhu { rd, rs1, rs2 },
1094        (0b0000001, 0b100) => Inst::Div { rd, rs1, rs2 },
1095        (0b0000001, 0b101) => Inst::Divu { rd, rs1, rs2 },
1096        (0b0000001, 0b110) => Inst::Rem { rd, rs1, rs2 },
1097        (0b0000001, 0b111) => Inst::Remu { rd, rs1, rs2 },
1098        // Zbb
1099        (0b0100000, 0b111) => Inst::Andn { rd, rs1, rs2 },
1100        (0b0100000, 0b110) => Inst::Orn { rd, rs1, rs2 },
1101        (0b0100000, 0b100) => Inst::Xnor { rd, rs1, rs2 },
1102        (0b0000101, 0b100) => Inst::Min { rd, rs1, rs2 },
1103        (0b0000101, 0b101) => Inst::Minu { rd, rs1, rs2 },
1104        (0b0000101, 0b110) => Inst::Max { rd, rs1, rs2 },
1105        (0b0000101, 0b111) => Inst::Maxu { rd, rs1, rs2 },
1106        (0b0110000, 0b001) => Inst::Rol { rd, rs1, rs2 },
1107        (0b0110000, 0b101) => Inst::Ror { rd, rs1, rs2 },
1108        // Zba
1109        (0b0010000, 0b010) => Inst::Sh1add { rd, rs1, rs2 },
1110        (0b0010000, 0b100) => Inst::Sh2add { rd, rs1, rs2 },
1111        (0b0010000, 0b110) => Inst::Sh3add { rd, rs1, rs2 },
1112        // Zbs
1113        (0b0100100, 0b001) => Inst::Bclr { rd, rs1, rs2 },
1114        (0b0010100, 0b001) => Inst::Bset { rd, rs1, rs2 },
1115        (0b0110100, 0b001) => Inst::Binv { rd, rs1, rs2 },
1116        (0b0100100, 0b101) => Inst::Bext { rd, rs1, rs2 },
1117        // Zicond
1118        (0b0000111, 0b101) => Inst::CzeroEqz { rd, rs1, rs2 },
1119        (0b0000111, 0b111) => Inst::CzeroNez { rd, rs1, rs2 },
1120        // Zbb zext.h via pack rd, rs1, x0 (funct7=0000100, funct3=100)
1121        (0b0000100, 0b100) if rs2 == 0 => Inst::ZextH { rd, rs1 },
1122        _ => Inst::Reserved { raw: w },
1123    }
1124}
1125
1126fn decode_op_32(rd: u8, rs1: u8, rs2: u8, funct3: u8, funct7: u8, w: u32) -> Inst {
1127    match (funct7, funct3) {
1128        (0b0000000, 0b000) => Inst::Addw { rd, rs1, rs2 },
1129        (0b0100000, 0b000) => Inst::Subw { rd, rs1, rs2 },
1130        (0b0000000, 0b001) => Inst::Sllw { rd, rs1, rs2 },
1131        (0b0000000, 0b101) => Inst::Srlw { rd, rs1, rs2 },
1132        (0b0100000, 0b101) => Inst::Sraw { rd, rs1, rs2 },
1133        // M-W
1134        (0b0000001, 0b000) => Inst::Mulw { rd, rs1, rs2 },
1135        (0b0000001, 0b100) => Inst::Divw { rd, rs1, rs2 },
1136        (0b0000001, 0b101) => Inst::Divuw { rd, rs1, rs2 },
1137        (0b0000001, 0b110) => Inst::Remw { rd, rs1, rs2 },
1138        (0b0000001, 0b111) => Inst::Remuw { rd, rs1, rs2 },
1139        // Zbb-W
1140        (0b0110000, 0b001) => Inst::Rolw { rd, rs1, rs2 },
1141        (0b0110000, 0b101) => Inst::Rorw { rd, rs1, rs2 },
1142        // Zba-W: add.uw (funct7=0000100, funct3=000), sh1add.uw (010), sh2add.uw (100), sh3add.uw (110)
1143        (0b0000100, 0b000) => Inst::Adduw { rd, rs1, rs2 },
1144        (0b0010000, 0b010) => Inst::Sh1adduw { rd, rs1, rs2 },
1145        (0b0010000, 0b100) => Inst::Sh2adduw { rd, rs1, rs2 },
1146        (0b0010000, 0b110) => Inst::Sh3adduw { rd, rs1, rs2 },
1147        _ => Inst::Reserved { raw: w },
1148    }
1149}
1150
1151fn decode_branch(rs1: u8, rs2: u8, funct3: u8, imm: i32, w: u32) -> Inst {
1152    match funct3 {
1153        0b000 => Inst::Beq { rs1, rs2, imm },
1154        0b001 => Inst::Bne { rs1, rs2, imm },
1155        0b100 => Inst::Blt { rs1, rs2, imm },
1156        0b101 => Inst::Bge { rs1, rs2, imm },
1157        0b110 => Inst::Bltu { rs1, rs2, imm },
1158        0b111 => Inst::Bgeu { rs1, rs2, imm },
1159        _ => Inst::Reserved { raw: w },
1160    }
1161}
1162
1163fn decode_misc_mem(funct3: u8) -> Inst {
1164    match funct3 {
1165        0b000 => Inst::Fence,
1166        0b001 => Inst::FenceI,
1167        _ => Inst::Reserved { raw: 0 },
1168    }
1169}
1170
1171fn decode_custom_0(w: u32, _rd: u8, _rs1: u8, funct3: u8) -> Inst {
1172    // Sub-op layout (I-type wire shape; funct3 is the sub-op selector):
1173    //   funct3 = 000 -> trap         (other fields ignored)
1174    //   funct3 = 001 -> ecall.jar    (other fields ignored)
1175    //   funct3 = 010 -> ecalli       (imm12 in bits [31:20], rs1/rd zero)
1176    //   funct3 = 100 -> fallthrough  (other fields ignored)
1177    //   funct3 = 011 (was br_table) -> reserved; PVM2 uses plain jalr.
1178    match funct3 {
1179        0b000 => Inst::Trap,
1180        0b001 => Inst::EcallJar,
1181        0b010 => Inst::Ecalli { imm: imm_i(w) },
1182        0b100 => Inst::Fallthrough,
1183        _ => Inst::Reserved { raw: w },
1184    }
1185}
1186
1187// ============================================================================
1188// Immediate extraction
1189// ============================================================================
1190
1191fn imm_i(w: u32) -> i32 {
1192    // bits [31:20], sign-extended
1193    (w as i32) >> 20
1194}
1195
1196fn imm_s(w: u32) -> i32 {
1197    let hi = (w >> 25) & 0x7F;
1198    let lo = (w >> 7) & 0x1F;
1199    let raw = ((hi << 5) | lo) as i32;
1200    // sign-extend 12-bit
1201    (raw << 20) >> 20
1202}
1203
1204fn imm_b(w: u32) -> i32 {
1205    let b12 = (w >> 31) & 1;
1206    let b11 = (w >> 7) & 1;
1207    let b10_5 = (w >> 25) & 0x3F;
1208    let b4_1 = (w >> 8) & 0xF;
1209    let raw = (b12 << 12) | (b11 << 11) | (b10_5 << 5) | (b4_1 << 1);
1210    // sign-extend 13-bit
1211    ((raw as i32) << 19) >> 19
1212}
1213
1214fn imm_j(w: u32) -> i32 {
1215    let b20 = (w >> 31) & 1;
1216    let b10_1 = (w >> 21) & 0x3FF;
1217    let b11 = (w >> 20) & 1;
1218    let b19_12 = (w >> 12) & 0xFF;
1219    let raw = (b20 << 20) | (b19_12 << 12) | (b11 << 11) | (b10_1 << 1);
1220    // sign-extend 21-bit
1221    ((raw as i32) << 11) >> 11
1222}
1223
1224fn rs2_field(w: u32) -> u32 {
1225    (w >> 20) & 0x1F
1226}
1227
1228// ============================================================================
1229// RVC (compressed) decompression
1230// ============================================================================
1231
1232/// Decompress a 16-bit RVC instruction into its 32-bit equivalent
1233/// `Inst`. Reserved encodings decode to `Reserved { raw: <halfword> }`.
1234///
1235/// References RV unprivileged spec §16 (Compressed). Quadrants are
1236/// distinguished by op[1:0]; within each quadrant by funct3 ([15:13]).
1237fn decompress(h: u16) -> Inst {
1238    let op = h & 0b11;
1239    let f3 = (h >> 13) & 0b111;
1240    match op {
1241        0b00 => decompress_q0(h, f3),
1242        0b01 => decompress_q1(h, f3),
1243        0b10 => decompress_q2(h, f3),
1244        _ => Inst::Reserved { raw: h as u32 }, // op=11 isn't compressed
1245    }
1246}
1247
1248// "Compressed register" subset x8..x15 — 3-bit field maps to x8+r.
1249fn creg(r: u16) -> u8 {
1250    (r + 8) as u8
1251}
1252
1253fn decompress_q0(h: u16, f3: u16) -> Inst {
1254    let rs1c = creg((h >> 7) & 0b111);
1255    let rdrs2c = creg((h >> 2) & 0b111);
1256    match f3 {
1257        0b000 => {
1258            // c.addi4spn -> addi rd', x2, nzuimm
1259            let imm = ((((h >> 7) & 0x30) << 2) // [11:10]<<5..<<6 -> 9:8 to 6 ? Let me reread.
1260                | (((h >> 5) & 0x3) << 6) // [6:5] -> 7:6
1261                | (((h >> 11) & 0x3) << 4) // wait this differs from spec; check spec
1262                | (((h >> 2) & 0x1) << 3)) // [5] -> 3
1263                & 0x3FF;
1264            // Spec for c.addi4spn (CIW):
1265            //   nzuimm[5:4|9:6|2|3] from h[12:11|10:7|6|5]
1266            // Easier to recompute explicitly.
1267            let n = (((h >> 11) & 0x3) << 4) // [12:11] -> [5:4]
1268                | (((h >> 7) & 0xF) << 6)    // [10:7]  -> [9:6]
1269                | (((h >> 6) & 0x1) << 2)    // [6]     -> [2]
1270                | (((h >> 5) & 0x1) << 3); // [5]     -> [3]
1271            let _ = imm; // sigh
1272            if n == 0 {
1273                Inst::Reserved { raw: h as u32 } // c.addi4spn nzuimm=0 reserved
1274            } else {
1275                Inst::Addi {
1276                    rd: rdrs2c,
1277                    rs1: 2,
1278                    imm: n as i32,
1279                }
1280            }
1281        }
1282        0b010 => {
1283            // c.lw -> lw rd', uimm(rs1')
1284            let imm = (((h >> 10) & 0x7) << 3) // [12:10] -> [5:3]
1285                | (((h >> 6) & 0x1) << 2)      // [6]     -> [2]
1286                | (((h >> 5) & 0x1) << 6); // [5]     -> [6]
1287            Inst::Lw {
1288                rd: rdrs2c,
1289                rs1: rs1c,
1290                imm: imm as i32,
1291            }
1292        }
1293        0b011 => {
1294            // c.ld -> ld rd', uimm(rs1')
1295            let imm = (((h >> 10) & 0x7) << 3) // [12:10] -> [5:3]
1296                | (((h >> 5) & 0x3) << 6); // [6:5]   -> [7:6]
1297            Inst::Ld {
1298                rd: rdrs2c,
1299                rs1: rs1c,
1300                imm: imm as i32,
1301            }
1302        }
1303        0b110 => {
1304            // c.sw
1305            let imm = (((h >> 10) & 0x7) << 3) | (((h >> 6) & 0x1) << 2) | (((h >> 5) & 0x1) << 6);
1306            Inst::Sw {
1307                rs1: rs1c,
1308                rs2: rdrs2c,
1309                imm: imm as i32,
1310            }
1311        }
1312        0b111 => {
1313            // c.sd
1314            let imm = (((h >> 10) & 0x7) << 3) | (((h >> 5) & 0x3) << 6);
1315            Inst::Sd {
1316                rs1: rs1c,
1317                rs2: rdrs2c,
1318                imm: imm as i32,
1319            }
1320        }
1321        _ => Inst::Reserved { raw: h as u32 },
1322    }
1323}
1324
1325fn decompress_q1(h: u16, f3: u16) -> Inst {
1326    match f3 {
1327        0b000 => {
1328            // c.nop / c.addi
1329            let rd = ((h >> 7) & 0x1F) as u8;
1330            let imm = decode_ci_imm6(h);
1331            if rd == 0 {
1332                // c.nop (imm should be 0 but we don't enforce)
1333                Inst::Addi {
1334                    rd: 0,
1335                    rs1: 0,
1336                    imm: 0,
1337                }
1338            } else {
1339                Inst::Addi { rd, rs1: rd, imm }
1340            }
1341        }
1342        0b001 => {
1343            // c.addiw (RV64) — rd != 0
1344            let rd = ((h >> 7) & 0x1F) as u8;
1345            if rd == 0 {
1346                return Inst::Reserved { raw: h as u32 };
1347            }
1348            let imm = decode_ci_imm6(h);
1349            Inst::Addiw { rd, rs1: rd, imm }
1350        }
1351        0b010 => {
1352            // c.li -> addi rd, x0, imm
1353            let rd = ((h >> 7) & 0x1F) as u8;
1354            if rd == 0 {
1355                return Inst::Reserved { raw: h as u32 };
1356            }
1357            let imm = decode_ci_imm6(h);
1358            Inst::Addi { rd, rs1: 0, imm }
1359        }
1360        0b011 => {
1361            // c.addi16sp / c.lui
1362            let rd = ((h >> 7) & 0x1F) as u8;
1363            if rd == 2 {
1364                // c.addi16sp
1365                let imm = (((h >> 12) & 1) << 9)
1366                    | (((h >> 6) & 1) << 4)
1367                    | (((h >> 5) & 1) << 6)
1368                    | (((h >> 3) & 0x3) << 7)
1369                    | (((h >> 2) & 1) << 5);
1370                let sx = ((imm as i32) << 22) >> 22; // sign-ext 10-bit
1371                if sx == 0 {
1372                    return Inst::Reserved { raw: h as u32 };
1373                }
1374                Inst::Addi {
1375                    rd: 2,
1376                    rs1: 2,
1377                    imm: sx,
1378                }
1379            } else if rd == 0 {
1380                Inst::Reserved { raw: h as u32 }
1381            } else {
1382                // c.lui — cast to u32 first because shifts go up to <<17
1383                let h = h as u32;
1384                let imm = (((h >> 12) & 1) << 17) | (((h >> 2) & 0x1F) << 12);
1385                let sx = ((imm as i32) << 14) >> 14; // sign-ext 18-bit
1386                if sx == 0 {
1387                    return Inst::Reserved { raw: (h & 0xFFFF) };
1388                }
1389                Inst::Lui { rd, imm: sx }
1390            }
1391        }
1392        0b100 => decompress_q1_misc_alu(h),
1393        0b101 => {
1394            // c.j -> jal x0, off
1395            let imm = decode_cj_imm(h);
1396            Inst::Jal { rd: 0, imm }
1397        }
1398        0b110 | 0b111 => {
1399            // c.beqz / c.bnez (rs1 = creg)
1400            let rs1 = creg((h >> 7) & 0b111);
1401            let imm = decode_cb_imm(h);
1402            if f3 == 0b110 {
1403                Inst::Beq { rs1, rs2: 0, imm }
1404            } else {
1405                Inst::Bne { rs1, rs2: 0, imm }
1406            }
1407        }
1408        _ => Inst::Reserved { raw: h as u32 },
1409    }
1410}
1411
1412fn decompress_q1_misc_alu(h: u16) -> Inst {
1413    let f6_10 = (h >> 10) & 0b11; // funct2 selecting shift / andi / sub-op
1414    let rdrs1c = creg((h >> 7) & 0b111);
1415    match f6_10 {
1416        0b00 | 0b01 => {
1417            // c.srli / c.srai (RV64 shamt: bit12||bits6:2)
1418            let shamt = ((((h >> 12) & 1) << 5) | ((h >> 2) & 0x1F)) as u8;
1419            if f6_10 == 0b00 {
1420                Inst::Srli {
1421                    rd: rdrs1c,
1422                    rs1: rdrs1c,
1423                    shamt,
1424                }
1425            } else {
1426                Inst::Srai {
1427                    rd: rdrs1c,
1428                    rs1: rdrs1c,
1429                    shamt,
1430                }
1431            }
1432        }
1433        0b10 => {
1434            // c.andi
1435            let imm = decode_ci_imm6(h);
1436            Inst::Andi {
1437                rd: rdrs1c,
1438                rs1: rdrs1c,
1439                imm,
1440            }
1441        }
1442        0b11 => {
1443            // c.sub/xor/or/and (bit12=0) or c.subw/c.addw (bit12=1)
1444            let rs2c = creg((h >> 2) & 0b111);
1445            let bit12 = (h >> 12) & 1;
1446            let f2 = (h >> 5) & 0b11;
1447            match (bit12, f2) {
1448                (0, 0b00) => Inst::Sub {
1449                    rd: rdrs1c,
1450                    rs1: rdrs1c,
1451                    rs2: rs2c,
1452                },
1453                (0, 0b01) => Inst::Xor {
1454                    rd: rdrs1c,
1455                    rs1: rdrs1c,
1456                    rs2: rs2c,
1457                },
1458                (0, 0b10) => Inst::Or {
1459                    rd: rdrs1c,
1460                    rs1: rdrs1c,
1461                    rs2: rs2c,
1462                },
1463                (0, 0b11) => Inst::And {
1464                    rd: rdrs1c,
1465                    rs1: rdrs1c,
1466                    rs2: rs2c,
1467                },
1468                (1, 0b00) => Inst::Subw {
1469                    rd: rdrs1c,
1470                    rs1: rdrs1c,
1471                    rs2: rs2c,
1472                },
1473                (1, 0b01) => Inst::Addw {
1474                    rd: rdrs1c,
1475                    rs1: rdrs1c,
1476                    rs2: rs2c,
1477                },
1478                _ => Inst::Reserved { raw: h as u32 },
1479            }
1480        }
1481        _ => Inst::Reserved { raw: h as u32 },
1482    }
1483}
1484
1485fn decompress_q2(h: u16, f3: u16) -> Inst {
1486    let rdrs1 = ((h >> 7) & 0x1F) as u8;
1487    let rs2 = ((h >> 2) & 0x1F) as u8;
1488    match f3 {
1489        0b000 => {
1490            // c.slli (RV64 shamt: bit12||bits6:2)
1491            if rdrs1 == 0 {
1492                return Inst::Reserved { raw: h as u32 };
1493            }
1494            let shamt = ((((h >> 12) & 1) << 5) | ((h >> 2) & 0x1F)) as u8;
1495            Inst::Slli {
1496                rd: rdrs1,
1497                rs1: rdrs1,
1498                shamt,
1499            }
1500        }
1501        0b010 => {
1502            // c.lwsp -> lw rd, uimm(x2)
1503            if rdrs1 == 0 {
1504                return Inst::Reserved { raw: h as u32 };
1505            }
1506            let imm = (((h >> 12) & 1) << 5) | (((h >> 4) & 0x7) << 2) | (((h >> 2) & 0x3) << 6);
1507            Inst::Lw {
1508                rd: rdrs1,
1509                rs1: 2,
1510                imm: imm as i32,
1511            }
1512        }
1513        0b011 => {
1514            // c.ldsp -> ld rd, uimm(x2)
1515            if rdrs1 == 0 {
1516                return Inst::Reserved { raw: h as u32 };
1517            }
1518            let imm = (((h >> 12) & 1) << 5) | (((h >> 5) & 0x3) << 3) | (((h >> 2) & 0x7) << 6);
1519            Inst::Ld {
1520                rd: rdrs1,
1521                rs1: 2,
1522                imm: imm as i32,
1523            }
1524        }
1525        0b100 => {
1526            // c.jr / c.mv / c.ebreak / c.jalr / c.add
1527            //
1528            // PVM2 re-enables JALR, so the compressed forms expand
1529            // naturally: `c.jr rs1` → `jalr x0, rs1, 0`; `c.jalr rs1`
1530            // → `jalr x1, rs1, 0`. `c.ebreak` stays reserved.
1531            let bit12 = (h >> 12) & 1;
1532            match (bit12, rdrs1, rs2) {
1533                (0, r, 0) if r != 0 => Inst::Jalr {
1534                    rd: 0,
1535                    rs1: r,
1536                    imm: 0,
1537                }, // c.jr
1538                (0, r, s) if r != 0 && s != 0 => Inst::Add {
1539                    rd: r,
1540                    rs1: 0,
1541                    rs2: s,
1542                }, // c.mv
1543                (1, 0, 0) => Inst::Reserved { raw: h as u32 }, // c.ebreak → forbidden in PVM2
1544                (1, r, 0) if r != 0 => Inst::Jalr {
1545                    rd: 1,
1546                    rs1: r,
1547                    imm: 0,
1548                }, // c.jalr
1549                (1, r, s) if r != 0 && s != 0 => Inst::Add {
1550                    rd: r,
1551                    rs1: r,
1552                    rs2: s,
1553                }, // c.add
1554                _ => Inst::Reserved { raw: h as u32 },
1555            }
1556        }
1557        0b110 => {
1558            // c.swsp -> sw rs2, uimm(x2)
1559            let imm = (((h >> 9) & 0xF) << 2) | (((h >> 7) & 0x3) << 6);
1560            Inst::Sw {
1561                rs1: 2,
1562                rs2,
1563                imm: imm as i32,
1564            }
1565        }
1566        0b111 => {
1567            // c.sdsp -> sd rs2, uimm(x2)
1568            let imm = (((h >> 10) & 0x7) << 3) | (((h >> 7) & 0x7) << 6);
1569            Inst::Sd {
1570                rs1: 2,
1571                rs2,
1572                imm: imm as i32,
1573            }
1574        }
1575        _ => Inst::Reserved { raw: h as u32 },
1576    }
1577}
1578
1579/// Decode CI-format 6-bit signed immediate.
1580fn decode_ci_imm6(h: u16) -> i32 {
1581    let imm = (((h >> 12) & 1) << 5) | ((h >> 2) & 0x1F);
1582    ((imm as i32) << 26) >> 26
1583}
1584
1585/// Decode CJ-format 12-bit signed immediate (×2 byte offset).
1586fn decode_cj_imm(h: u16) -> i32 {
1587    let b11 = (h >> 12) & 1;
1588    let b4 = (h >> 11) & 1;
1589    let b9_8 = (h >> 9) & 0x3;
1590    let b10 = (h >> 8) & 1;
1591    let b6 = (h >> 7) & 1;
1592    let b7 = (h >> 6) & 1;
1593    let b3_1 = (h >> 3) & 0x7;
1594    let b5 = (h >> 2) & 1;
1595    let imm = (b11 << 11)
1596        | (b10 << 10)
1597        | (b9_8 << 8)
1598        | (b7 << 7)
1599        | (b6 << 6)
1600        | (b5 << 5)
1601        | (b4 << 4)
1602        | (b3_1 << 1);
1603    ((imm as i32) << 20) >> 20
1604}
1605
1606/// Decode CB-format 9-bit signed immediate (×2 byte offset).
1607fn decode_cb_imm(h: u16) -> i32 {
1608    let b8 = (h >> 12) & 1;
1609    let b4_3 = (h >> 10) & 0x3;
1610    let b7_6 = (h >> 5) & 0x3;
1611    let b2_1 = (h >> 3) & 0x3;
1612    let b5 = (h >> 2) & 1;
1613    let imm = (b8 << 8) | (b7_6 << 6) | (b5 << 5) | (b4_3 << 3) | (b2_1 << 1);
1614    ((imm as i32) << 23) >> 23
1615}
1616
1617#[cfg(test)]
1618mod tests {
1619    use super::*;
1620
1621    #[test]
1622    fn decode_add() {
1623        // add x10, x11, x12 = 0x00C58533
1624        let bytes = 0x00C58533u32.to_le_bytes();
1625        assert_eq!(
1626            decode(&bytes),
1627            Some((
1628                Inst::Add {
1629                    rd: 10,
1630                    rs1: 11,
1631                    rs2: 12,
1632                },
1633                4
1634            ))
1635        );
1636    }
1637
1638    #[test]
1639    fn decode_ld() {
1640        // ld x5, 16(x10) = 0x01053283
1641        let bytes = 0x01053283u32.to_le_bytes();
1642        assert_eq!(
1643            decode(&bytes),
1644            Some((
1645                Inst::Ld {
1646                    rd: 5,
1647                    rs1: 10,
1648                    imm: 16,
1649                },
1650                4
1651            ))
1652        );
1653    }
1654
1655    #[test]
1656    fn decode_sd() {
1657        // sd x11, 8(x10) = 0x00B53423
1658        let bytes = 0x00B53423u32.to_le_bytes();
1659        assert_eq!(
1660            decode(&bytes),
1661            Some((
1662                Inst::Sd {
1663                    rs1: 10,
1664                    rs2: 11,
1665                    imm: 8,
1666                },
1667                4
1668            ))
1669        );
1670    }
1671
1672    #[test]
1673    fn decode_addi_negative() {
1674        // addi x10, x11, -4 = 0xFFC58513
1675        let bytes = 0xFFC58513u32.to_le_bytes();
1676        assert_eq!(
1677            decode(&bytes),
1678            Some((
1679                Inst::Addi {
1680                    rd: 10,
1681                    rs1: 11,
1682                    imm: -4,
1683                },
1684                4
1685            ))
1686        );
1687    }
1688
1689    #[test]
1690    fn decode_beq() {
1691        // beq x10, x11, 8 = 0x00B50463
1692        let bytes = 0x00B50463u32.to_le_bytes();
1693        assert_eq!(
1694            decode(&bytes),
1695            Some((
1696                Inst::Beq {
1697                    rs1: 10,
1698                    rs2: 11,
1699                    imm: 8,
1700                },
1701                4
1702            ))
1703        );
1704    }
1705
1706    #[test]
1707    fn decode_jal() {
1708        // jal x1, 12 = 0x00C000EF
1709        let bytes = 0x00C000EFu32.to_le_bytes();
1710        assert_eq!(decode(&bytes), Some((Inst::Jal { rd: 1, imm: 12 }, 4)));
1711    }
1712
1713    #[test]
1714    fn decode_lui() {
1715        // lui x5, 0x12345 = 0x123452B7
1716        let bytes = 0x123452B7u32.to_le_bytes();
1717        assert_eq!(
1718            decode(&bytes),
1719            Some((
1720                Inst::Lui {
1721                    rd: 5,
1722                    imm: 0x12345000,
1723                },
1724                4
1725            ))
1726        );
1727    }
1728
1729    #[test]
1730    fn decode_auipc_native() {
1731        // auipc x5, 0x10 (= 0x00010297) — PVM2 decodes it natively now.
1732        let bytes = 0x00010297u32.to_le_bytes();
1733        assert_eq!(
1734            decode(&bytes),
1735            Some((
1736                Inst::Auipc {
1737                    rd: 5,
1738                    imm: 0x0001_0000,
1739                },
1740                4,
1741            ))
1742        );
1743    }
1744
1745    #[test]
1746    fn decode_ecall_reserved() {
1747        // ecall = 0x00000073
1748        let bytes = 0x00000073u32.to_le_bytes();
1749        match decode(&bytes) {
1750            Some((Inst::Reserved { .. }, 4)) => {}
1751            other => panic!("standard ecall should be Reserved, got {:?}", other),
1752        }
1753    }
1754
1755    #[test]
1756    fn decode_c_mv() {
1757        // c.mv x10, x11 = 0x852E
1758        let bytes = 0x852Eu16.to_le_bytes();
1759        // c.mv → add rd, x0, rs2
1760        assert_eq!(
1761            decode(&bytes),
1762            Some((
1763                Inst::Add {
1764                    rd: 10,
1765                    rs1: 0,
1766                    rs2: 11,
1767                },
1768                2
1769            ))
1770        );
1771    }
1772
1773    #[test]
1774    fn decode_c_addi() {
1775        // c.addi x10, 1 = 0x0505
1776        let bytes = 0x0505u16.to_le_bytes();
1777        assert_eq!(
1778            decode(&bytes),
1779            Some((
1780                Inst::Addi {
1781                    rd: 10,
1782                    rs1: 10,
1783                    imm: 1,
1784                },
1785                2
1786            ))
1787        );
1788    }
1789
1790    #[test]
1791    fn decode_c_li() {
1792        // c.li x10, -1 = 0x557D
1793        let bytes = 0x557Du16.to_le_bytes();
1794        assert_eq!(
1795            decode(&bytes),
1796            Some((
1797                Inst::Addi {
1798                    rd: 10,
1799                    rs1: 0,
1800                    imm: -1,
1801                },
1802                2
1803            ))
1804        );
1805    }
1806
1807    #[test]
1808    fn decode_c_j() {
1809        // c.j 4 = 0xA011 (jumps 4 bytes forward)
1810        let bytes = 0xA011u16.to_le_bytes();
1811        assert_eq!(decode(&bytes), Some((Inst::Jal { rd: 0, imm: 4 }, 2)));
1812    }
1813
1814    #[test]
1815    fn decode_custom_trap() {
1816        // trap: custom-0 (0x0B), funct3=000, rest zero
1817        let bytes = 0x0000000Bu32.to_le_bytes();
1818        assert_eq!(decode(&bytes), Some((Inst::Trap, 4)));
1819    }
1820
1821    #[test]
1822    fn decode_custom_ecall_jar() {
1823        // ecall.jar: custom-0, funct3=001
1824        let bytes = 0x0000100Bu32.to_le_bytes();
1825        assert_eq!(decode(&bytes), Some((Inst::EcallJar, 4)));
1826    }
1827
1828    #[test]
1829    fn decode_custom_ecalli() {
1830        // ecalli imm=5: custom-0 (0x0B), funct3=010, imm12 in bits[31:20]
1831        // wire: (5 << 20) | (0b010 << 12) | 0x0B = 0x0050_200B
1832        let w = (5u32 << 20) | (0b010 << 12) | 0x0B;
1833        let bytes = w.to_le_bytes();
1834        assert_eq!(decode(&bytes), Some((Inst::Ecalli { imm: 5 }, 4)));
1835    }
1836
1837    #[test]
1838    fn decode_custom_ecalli_negative() {
1839        // ecalli imm=-1: imm12 = 0xFFF, sign-extends to -1
1840        let w = (0xFFFu32 << 20) | (0b010 << 12) | 0x0B;
1841        let bytes = w.to_le_bytes();
1842        assert_eq!(decode(&bytes), Some((Inst::Ecalli { imm: -1 }, 4)));
1843    }
1844
1845    #[test]
1846    fn decode_custom0_funct3_011_reserved() {
1847        // custom-0 funct3=011 used to be br_table; PVM2 reverted to
1848        // native control flow and no longer defines it — reserved.
1849        let w = (42u32 << 20) | (1u32 << 15) | (0b011u32 << 12) | (0b00010u32 << 2) | 0b11;
1850        let bytes = w.to_le_bytes();
1851        assert!(matches!(decode(&bytes).unwrap().0, Inst::Reserved { .. }));
1852    }
1853
1854    #[test]
1855    fn decode_auipc() {
1856        // auipc x5, 0x12345 — U-type, opcode 0b0010111.
1857        let w = (0x12345u32 << 12) | (5 << 7) | 0b001_0111;
1858        let bytes = w.to_le_bytes();
1859        assert_eq!(
1860            decode(&bytes),
1861            Some((
1862                Inst::Auipc {
1863                    rd: 5,
1864                    imm: 0x1234_5000,
1865                },
1866                4,
1867            ))
1868        );
1869    }
1870
1871    #[test]
1872    fn decode_jalr() {
1873        // jalr x1, x6, 16 — I-type, opcode 0b1100111, funct3=0.
1874        let w = (16u32 << 20) | (6 << 15) | (1 << 7) | 0b110_0111;
1875        let bytes = w.to_le_bytes();
1876        assert_eq!(
1877            decode(&bytes),
1878            Some((
1879                Inst::Jalr {
1880                    rd: 1,
1881                    rs1: 6,
1882                    imm: 16,
1883                },
1884                4,
1885            ))
1886        );
1887    }
1888
1889    #[test]
1890    fn decode_custom1_callf_now_reserved() {
1891        // Old callf (custom-1, J-type, rd=0) now decodes to Reserved.
1892        // wire: imm=8 J-type field | (rd=0) | (custom-1 major=0b01010) | 0b11
1893        let callf_word = 0x0080_002Bu32;
1894        let bytes = callf_word.to_le_bytes();
1895        let decoded = decode(&bytes).unwrap().0;
1896        assert!(matches!(decoded, Inst::Reserved { .. }));
1897    }
1898
1899    #[test]
1900    fn decompress_c_jr_ra_is_jalr() {
1901        // `c.jr ra` (= 0x8082) decompresses to `jalr x0, x1, 0` — the
1902        // canonical return. A terminator whose target is validated to be
1903        // a basic-block start at runtime.
1904        let bytes = 0x8082u16.to_le_bytes();
1905        assert_eq!(
1906            decode(&bytes),
1907            Some((
1908                Inst::Jalr {
1909                    rd: 0,
1910                    rs1: 1,
1911                    imm: 0
1912                },
1913                2
1914            ))
1915        );
1916    }
1917
1918    #[test]
1919    fn decompress_c_jalr_ra_is_jalr_link() {
1920        // `c.jalr ra` (= 0x9082) decompresses to `jalr x1, x1, 0` — an
1921        // indirect call (writes the link register). c.ebreak (0x9002,
1922        // rs1=0) stays Reserved.
1923        assert_eq!(
1924            decode(&0x9082u16.to_le_bytes()),
1925            Some((
1926                Inst::Jalr {
1927                    rd: 1,
1928                    rs1: 1,
1929                    imm: 0
1930                },
1931                2
1932            ))
1933        );
1934        assert!(matches!(
1935            decode(&0x9002u16.to_le_bytes()).unwrap().0,
1936            Inst::Reserved { .. }
1937        ));
1938    }
1939
1940    // ---- x3/x4 are real (spilled) registers; only x16..x31 reserved ----
1941
1942    #[test]
1943    fn x3_x4_decode_as_live_spilled_registers() {
1944        // add x5, x3, x6 (rs1 = x3) → a live Add the interpreter executes
1945        // (slot 13); the recompiler routes it to the spill path. It is NOT
1946        // reserved, but IS spilled.
1947        let add_x3 = 0x0061_82B3u32; // add x5, x3, x6
1948        assert!(matches!(
1949            decode(&add_x3.to_le_bytes()).unwrap().0,
1950            Inst::Add {
1951                rd: 5,
1952                rs1: 3,
1953                rs2: 6
1954            }
1955        ));
1956        assert!(!word_uses_reserved_reg(add_x3));
1957        assert!(word_uses_spilled_reg(add_x3));
1958
1959        // addi x3, x0, 1 (rd = x3) → live Addi writing x3 (slot 13).
1960        let addi_x3 = 0x0010_0193u32;
1961        assert!(matches!(
1962            decode(&addi_x3.to_le_bytes()).unwrap().0,
1963            Inst::Addi {
1964                rd: 3,
1965                rs1: 0,
1966                imm: 1
1967            }
1968        ));
1969        assert!(!word_uses_reserved_reg(addi_x3));
1970        assert!(word_uses_spilled_reg(addi_x3));
1971
1972        // add x16, x5, x6 (rd = x16) → Reserved: x16..x31 don't exist in
1973        // RV64E (standard RV), so the encoding is illegal — still reserved,
1974        // never spilled.
1975        let add_x16 = 0x0062_8833u32; // add x16, x5, x6
1976        assert!(matches!(
1977            decode(&add_x16.to_le_bytes()).unwrap().0,
1978            Inst::Reserved { .. }
1979        ));
1980        assert!(word_uses_reserved_reg(add_x16));
1981        assert!(!word_uses_spilled_reg(add_x16));
1982    }
1983
1984    #[test]
1985    fn x3_x4_free_instruction_is_unaffected() {
1986        // add x5, x6, x7 — neither reserved nor spilled → decodes normally,
1987        // stays on the recompiler fast path.
1988        let add_ok = 0x0073_02B3u32;
1989        assert!(matches!(
1990            decode(&add_ok.to_le_bytes()).unwrap().0,
1991            Inst::Add { .. }
1992        ));
1993        assert!(!word_uses_reserved_reg(add_ok));
1994        assert!(!word_uses_spilled_reg(add_ok));
1995    }
1996
1997    #[test]
1998    fn immediate_bits_aliasing_x3_are_not_a_register() {
1999        // lui x5, 0x18000 — bits [19:15] of the word are 0b00011 (= 3),
2000        // but for U-type that field is *immediate*, not rs1, so the
2001        // instruction is a normal Lui — neither reserved nor spilled.
2002        let lui = 0x0001_82B7u32;
2003        assert!(matches!(
2004            decode(&lui.to_le_bytes()).unwrap().0,
2005            Inst::Lui { rd: 5, .. }
2006        ));
2007        assert!(!word_uses_reserved_reg(lui));
2008        assert!(!word_uses_spilled_reg(lui));
2009    }
2010
2011    #[test]
2012    fn word_predicate_matches_inst_predicate() {
2013        // The recompiler's fast `word_uses_reserved_reg` (opcode-mask, no
2014        // decode) must equal the interpreter's `Inst::uses_reserved_reg`
2015        // for every *non-reserved* word — that is what makes the two
2016        // engines agree on which encodings are illegal and panic (x16..x31
2017        // only; x3/x4 are valid). (A reserved-decoding word panics either
2018        // way, so its predicate value is unconstrained here.) Sweep every
2019        // 7-bit opcode × funct3 ×
2020        // representative funct7, with each register field ranging over
2021        // {x0, x3, x4, x5} so both reserved and free positions are hit.
2022        for major in 0u32..0x20 {
2023            let opcode7 = (major << 2) | 0b11; // 7-bit opcode, 4-byte marker
2024            for &rd in &[0u32, 3, 4, 5, 16, 31] {
2025                for &rs1 in &[0u32, 3, 4, 5, 16, 31] {
2026                    for &rs2 in &[0u32, 3, 4, 5, 16, 31] {
2027                        for funct3 in 0u32..8 {
2028                            for &funct7 in &[0u32, 0b000_0001, 0b010_0000, 0b000_0111] {
2029                                let w = (funct7 << 25)
2030                                    | (rs2 << 20)
2031                                    | (rs1 << 15)
2032                                    | (funct3 << 12)
2033                                    | (rd << 7)
2034                                    | opcode7;
2035                                let inst = decode_32(w);
2036                                if !matches!(inst, Inst::Reserved { .. }) {
2037                                    assert_eq!(
2038                                        word_uses_reserved_reg(w),
2039                                        inst.uses_reserved_reg(),
2040                                        "word {w:#010x} -> {inst:?}"
2041                                    );
2042                                }
2043                            }
2044                        }
2045                    }
2046                }
2047            }
2048        }
2049    }
2050}