Skip to main content

javm_exec/
interp.rs

1//! Byte-PVM interpreter.
2//!
3//! Predecodes a [`PvmProgram`] via [`crate::decode::predecode`] and
4//! dispatches over the resulting [`DecodedInst`](crate::DecodedInst) array.
5//!
6//! Cherry-picked from v2 `javm/src/interpreter/mod.rs::run` (~787
7//! LOC). The opcode-dispatch arms are verbatim modulo two adaptations:
8//!
9//! 1. **State as parameters.** v2's `Interpreter` owns gas/regs/mem/
10//!    code/etc. v3's `Interpreter::run` is a free function that takes
11//!    `&PvmProgram + &mut Regs + &mut Mem + &mut GasCounter +
12//!    &mut dyn EcallHandler`. The predecoded state is computed on
13//!    entry (matching v2's `Interpreter::new` flow, but inline rather
14//!    than stored).
15//!
16//! 2. **Ecall routing.** v2 returns `ExitReason::Ecall` / `HostCall`
17//!    directly to the kernel. v3 routes both through the
18//!    `EcallHandler` trait: on `Continue` the loop resumes at
19//!    `inst.next_idx`; on `Exit(reason)` the run returns. The
20//!    diagnostic `regs.gpr[7]` "unsupported opcode" recording from
21//!    the MVP is dropped (full coverage means it can't fire).
22//!
23//! Preserved optimizations (vs v2):
24//! - Predecoded `DecodedInst` flat layout (40 bytes; no `Args` enum
25//!   matching in the hot loop).
26//! - `pc_to_idx` hot-loop indexing for dynamic jumps.
27//! - Gas-block charging via `inst.bb_gas_cost` (JAR v0.8.0).
28//! - `do_load!` / `do_store!` macros (zero overhead).
29//! - Fast-path `mem.read_*` / `write_*` helpers (single MOV on x86).
30
31use crate::decode::predecode;
32use crate::ecall::{EcallHandler, EcallKind, EcallResult};
33use crate::exit::ExitReason;
34use crate::gas::GasCounter;
35use crate::instruction::Opcode;
36use crate::mem::Memory;
37use crate::program::PvmProgram;
38use crate::regs::Regs;
39
40/// Namespace for byte-PVM execution.
41pub struct Interpreter;
42
43impl Interpreter {
44    /// Execute `program` starting at `regs.pc`. Returns the terminal
45    /// [`ExitReason`]. On return, `regs.pc` reflects the PC at exit
46    /// (already advanced past an ecall instruction if exit came from
47    /// the handler; otherwise the PC of the offending instruction).
48    pub fn run<M: Memory>(
49        program: &PvmProgram,
50        regs: &mut Regs,
51        mem: &mut M,
52        gas: &mut GasCounter,
53        handler: &mut dyn EcallHandler,
54    ) -> ExitReason {
55        // Macros for repetitive load/store dispatch arms. Each macro
56        // expands to the same code as the hand-written variants, so
57        // there is zero runtime overhead.
58        macro_rules! do_store {
59            ($mem:expr, $exit:ident, $addr:expr, $write_fn:ident, $val:expr) => {{
60                let a = $addr;
61                if !$mem.$write_fn(a, $val) {
62                    $exit = Some(ExitReason::PageFault(a & !0xFFF));
63                }
64            }};
65        }
66        macro_rules! do_load {
67            ($mem:expr, $regs:expr, $exit:ident, $dst:expr, $addr:expr, $read_fn:ident, |$v:ident| $conv:expr) => {{
68                let a = $addr;
69                match $mem.$read_fn(a) {
70                    Some($v) => {
71                        $regs.gpr[$dst] = $conv;
72                    }
73                    None => {
74                        $exit = Some(ExitReason::PageFault(a & !0xFFF));
75                    }
76                }
77            }};
78        }
79
80        let predecoded = predecode(program);
81        let insts = &predecoded.decoded_insts;
82        let pc_to_idx = &predecoded.pc_to_idx;
83        let basic_block_starts = &predecoded.basic_block_starts;
84        let jump_table = &program.jump_table;
85
86        // Resolve starting PC to instruction index.
87        let mut idx = if (regs.pc as usize) < pc_to_idx.len() {
88            pc_to_idx[regs.pc as usize]
89        } else {
90            u32::MAX
91        };
92        if idx == u32::MAX {
93            return ExitReason::Panic;
94        }
95
96        loop {
97            // SAFETY: idx is maintained within 0..insts.len() by the
98            // predecoder and incremented only via validated next_idx /
99            // target_idx values.
100            let inst = *unsafe { insts.get_unchecked(idx as usize) };
101
102            // Per-gas-block charging (JAR v0.8.0): only at PC=0 and
103            // post-terminator starts.
104            if inst.bb_gas_cost > 0 && gas.charge(inst.bb_gas_cost as u64).is_err() {
105                regs.pc = inst.pc as u64;
106                return ExitReason::OutOfGas;
107            }
108
109            let ra = inst.ra as usize;
110            let rb = inst.rb as usize;
111            let rd = inst.rd as usize;
112            let imm1 = inst.imm1;
113
114            // Most instructions advance sequentially. Branches/jumps
115            // set branch_idx to the pre-resolved instruction index.
116            let mut branch_idx: u32 = u32::MAX;
117            let mut exit: Option<ExitReason> = None;
118
119            match inst.opcode {
120                // === No arguments ===
121                Opcode::Trap => {
122                    exit = Some(ExitReason::Trap);
123                }
124                Opcode::Fallthrough | Opcode::Unlikely => {}
125                Opcode::Ecall => {
126                    regs.pc = inst.next_pc as u64;
127                    match handler.handle(EcallKind::Ecall, regs, mem) {
128                        EcallResult::Continue => {
129                            idx = inst.next_idx;
130                            continue;
131                        }
132                        EcallResult::Exit(reason) => return reason,
133                    }
134                }
135
136                // === One immediate ===
137                Opcode::Ecalli => {
138                    regs.pc = inst.next_pc as u64;
139                    match handler.handle(EcallKind::Ecalli(imm1 as u32), regs, mem) {
140                        EcallResult::Continue => {
141                            idx = inst.next_idx;
142                            continue;
143                        }
144                        EcallResult::Exit(reason) => return reason,
145                    }
146                }
147
148                // === One register + extended immediate ===
149                Opcode::LoadImm64 => {
150                    regs.gpr[ra] = imm1;
151                }
152
153                // === One offset (jump) ===
154                Opcode::Jump => {
155                    if inst.target_idx != u32::MAX {
156                        branch_idx = inst.target_idx;
157                    } else {
158                        exit = Some(ExitReason::Panic);
159                    }
160                }
161
162                // === One register + one immediate ===
163                Opcode::JumpInd => {
164                    let addr = regs.gpr[ra].wrapping_add(imm1) % (1u64 << 32);
165                    match djump(addr, jump_table, basic_block_starts) {
166                        Ok(target_pc) => {
167                            let t = target_pc as usize;
168                            if t < pc_to_idx.len() {
169                                let tidx = pc_to_idx[t];
170                                if tidx != u32::MAX {
171                                    branch_idx = tidx;
172                                } else {
173                                    exit = Some(ExitReason::Panic);
174                                }
175                            } else {
176                                exit = Some(ExitReason::Panic);
177                            }
178                        }
179                        Err(reason) => exit = Some(reason),
180                    }
181                }
182                Opcode::LoadImm => {
183                    regs.gpr[ra] = imm1;
184                }
185
186                // === Two registers ===
187                Opcode::MoveReg => {
188                    regs.gpr[rd] = regs.gpr[ra];
189                }
190                Opcode::Sbrk => {
191                    // JAR v0.8.0: sbrk removed.
192                    exit = Some(ExitReason::Panic);
193                }
194                Opcode::CountSetBits64 => {
195                    regs.gpr[rd] = regs.gpr[ra].count_ones() as u64;
196                }
197                Opcode::CountSetBits32 => {
198                    regs.gpr[rd] = (regs.gpr[ra] as u32).count_ones() as u64;
199                }
200                Opcode::LeadingZeroBits64 => {
201                    regs.gpr[rd] = regs.gpr[ra].leading_zeros() as u64;
202                }
203                Opcode::LeadingZeroBits32 => {
204                    regs.gpr[rd] = (regs.gpr[ra] as u32).leading_zeros() as u64;
205                }
206                Opcode::TrailingZeroBits64 => {
207                    regs.gpr[rd] = regs.gpr[ra].trailing_zeros() as u64;
208                }
209                Opcode::TrailingZeroBits32 => {
210                    regs.gpr[rd] = (regs.gpr[ra] as u32).trailing_zeros() as u64;
211                }
212                Opcode::SignExtend8 => {
213                    regs.gpr[rd] = regs.gpr[ra] as u8 as i8 as i64 as u64;
214                }
215                Opcode::SignExtend16 => {
216                    regs.gpr[rd] = regs.gpr[ra] as u16 as i16 as i64 as u64;
217                }
218                Opcode::ZeroExtend16 => {
219                    regs.gpr[rd] = regs.gpr[ra] as u16 as u64;
220                }
221                Opcode::ReverseBytes => {
222                    regs.gpr[rd] = regs.gpr[ra].swap_bytes();
223                }
224
225                // === Two registers + one immediate ===
226                Opcode::AddImm32 => {
227                    regs.gpr[ra] = crate::args::sign_extend_32(regs.gpr[rb].wrapping_add(imm1));
228                }
229                Opcode::AddImm64 => {
230                    regs.gpr[ra] = regs.gpr[rb].wrapping_add(imm1);
231                }
232                Opcode::MulImm32 => {
233                    regs.gpr[ra] = crate::args::sign_extend_32(
234                        (regs.gpr[rb] as u32).wrapping_mul(imm1 as u32) as u64,
235                    );
236                }
237                Opcode::MulImm64 => {
238                    regs.gpr[ra] = regs.gpr[rb].wrapping_mul(imm1);
239                }
240                Opcode::AndImm => {
241                    regs.gpr[ra] = regs.gpr[rb] & imm1;
242                }
243                Opcode::XorImm => {
244                    regs.gpr[ra] = regs.gpr[rb] ^ imm1;
245                }
246                Opcode::OrImm => {
247                    regs.gpr[ra] = regs.gpr[rb] | imm1;
248                }
249                Opcode::SetLtUImm => {
250                    regs.gpr[ra] = if regs.gpr[rb] < imm1 { 1 } else { 0 };
251                }
252                Opcode::SetLtSImm => {
253                    regs.gpr[ra] = if (regs.gpr[rb] as i64) < (imm1 as i64) {
254                        1
255                    } else {
256                        0
257                    };
258                }
259                Opcode::SetGtUImm => {
260                    regs.gpr[ra] = if regs.gpr[rb] > imm1 { 1 } else { 0 };
261                }
262                Opcode::SetGtSImm => {
263                    regs.gpr[ra] = if (regs.gpr[rb] as i64) > (imm1 as i64) {
264                        1
265                    } else {
266                        0
267                    };
268                }
269                Opcode::ShloLImm32 => {
270                    regs.gpr[ra] = crate::args::sign_extend_32(
271                        (regs.gpr[rb] as u32).wrapping_shl((imm1 % 32) as u32) as u64,
272                    );
273                }
274                Opcode::ShloRImm32 => {
275                    regs.gpr[ra] = crate::args::sign_extend_32(
276                        (regs.gpr[rb] as u32).wrapping_shr((imm1 % 32) as u32) as u64,
277                    );
278                }
279                Opcode::SharRImm32 => {
280                    regs.gpr[ra] =
281                        (regs.gpr[rb] as u32 as i32).wrapping_shr((imm1 % 32) as u32) as i64 as u64;
282                }
283                Opcode::ShloLImm64 => {
284                    regs.gpr[ra] = regs.gpr[rb].wrapping_shl((imm1 % 64) as u32);
285                }
286                Opcode::ShloRImm64 => {
287                    regs.gpr[ra] = regs.gpr[rb].wrapping_shr((imm1 % 64) as u32);
288                }
289                Opcode::SharRImm64 => {
290                    regs.gpr[ra] = (regs.gpr[rb] as i64).wrapping_shr((imm1 % 64) as u32) as u64;
291                }
292                Opcode::NegAddImm32 => {
293                    regs.gpr[ra] =
294                        crate::args::sign_extend_32(imm1.wrapping_sub(regs.gpr[rb]) as u32 as u64);
295                }
296                Opcode::NegAddImm64 => {
297                    regs.gpr[ra] = imm1.wrapping_sub(regs.gpr[rb]);
298                }
299                Opcode::CmovIzImm => {
300                    if regs.gpr[rb] == 0 {
301                        regs.gpr[ra] = imm1;
302                    }
303                }
304                Opcode::CmovNzImm => {
305                    if regs.gpr[rb] != 0 {
306                        regs.gpr[ra] = imm1;
307                    }
308                }
309                Opcode::RotR64Imm => {
310                    regs.gpr[ra] = regs.gpr[rb].rotate_right((imm1 % 64) as u32);
311                }
312                Opcode::RotR32Imm => {
313                    regs.gpr[ra] = crate::args::sign_extend_32(
314                        (regs.gpr[rb] as u32).rotate_right((imm1 % 32) as u32) as u64,
315                    );
316                }
317
318                // ImmAlt variants: op ra, imm, rb (imm is the "left" operand).
319                Opcode::ShloLImmAlt32 => {
320                    regs.gpr[ra] = crate::args::sign_extend_32(
321                        (imm1 as u32).wrapping_shl((regs.gpr[rb] % 32) as u32) as u64,
322                    );
323                }
324                Opcode::ShloRImmAlt32 => {
325                    regs.gpr[ra] = crate::args::sign_extend_32(
326                        (imm1 as u32).wrapping_shr((regs.gpr[rb] % 32) as u32) as u64,
327                    );
328                }
329                Opcode::SharRImmAlt32 => {
330                    regs.gpr[ra] = ((imm1 as u32) as i32).wrapping_shr((regs.gpr[rb] % 32) as u32)
331                        as i64 as u64;
332                }
333                Opcode::ShloLImmAlt64 => {
334                    regs.gpr[ra] = imm1.wrapping_shl((regs.gpr[rb] % 64) as u32);
335                }
336                Opcode::ShloRImmAlt64 => {
337                    regs.gpr[ra] = imm1.wrapping_shr((regs.gpr[rb] % 64) as u32);
338                }
339                Opcode::SharRImmAlt64 => {
340                    regs.gpr[ra] = (imm1 as i64).wrapping_shr((regs.gpr[rb] % 64) as u32) as u64;
341                }
342                Opcode::RotR64ImmAlt => {
343                    regs.gpr[ra] = imm1.rotate_right((regs.gpr[rb] % 64) as u32);
344                }
345                Opcode::RotR32ImmAlt => {
346                    regs.gpr[ra] = crate::args::sign_extend_32(
347                        (imm1 as u32).rotate_right((regs.gpr[rb] % 32) as u32) as u64,
348                    );
349                }
350
351                // === Two registers + one offset (branches) ===
352                Opcode::BranchEq
353                | Opcode::BranchNe
354                | Opcode::BranchLtU
355                | Opcode::BranchGeU
356                | Opcode::BranchLtS
357                | Opcode::BranchGeS => {
358                    let (a, b) = (regs.gpr[ra], regs.gpr[rb]);
359                    let cond = match inst.opcode {
360                        Opcode::BranchEq => a == b,
361                        Opcode::BranchNe => a != b,
362                        Opcode::BranchLtU => a < b,
363                        Opcode::BranchGeU => a >= b,
364                        Opcode::BranchLtS => (a as i64) < (b as i64),
365                        Opcode::BranchGeS => (a as i64) >= (b as i64),
366                        _ => unreachable!(),
367                    };
368                    if cond {
369                        if inst.target_idx != u32::MAX {
370                            branch_idx = inst.target_idx;
371                        } else {
372                            exit = Some(ExitReason::Panic);
373                        }
374                    }
375                }
376
377                // === Three register ALU ===
378                Opcode::Add32 => {
379                    regs.gpr[rd] =
380                        crate::args::sign_extend_32(regs.gpr[ra].wrapping_add(regs.gpr[rb]));
381                }
382                Opcode::Sub32 => {
383                    regs.gpr[rd] =
384                        crate::args::sign_extend_32(regs.gpr[ra].wrapping_sub(regs.gpr[rb]));
385                }
386                Opcode::Add64 => {
387                    regs.gpr[rd] = regs.gpr[ra].wrapping_add(regs.gpr[rb]);
388                }
389                Opcode::Sub64 => {
390                    regs.gpr[rd] = regs.gpr[ra].wrapping_sub(regs.gpr[rb]);
391                }
392                Opcode::Mul32 => {
393                    regs.gpr[rd] = crate::args::sign_extend_32(
394                        (regs.gpr[ra] as u32).wrapping_mul(regs.gpr[rb] as u32) as u64,
395                    );
396                }
397                Opcode::Mul64 => {
398                    regs.gpr[rd] = regs.gpr[ra].wrapping_mul(regs.gpr[rb]);
399                }
400                Opcode::And => {
401                    regs.gpr[rd] = regs.gpr[ra] & regs.gpr[rb];
402                }
403                Opcode::Or => {
404                    regs.gpr[rd] = regs.gpr[ra] | regs.gpr[rb];
405                }
406                Opcode::Xor => {
407                    regs.gpr[rd] = regs.gpr[ra] ^ regs.gpr[rb];
408                }
409                Opcode::SetLtU => {
410                    regs.gpr[rd] = if regs.gpr[ra] < regs.gpr[rb] { 1 } else { 0 };
411                }
412                Opcode::SetLtS => {
413                    regs.gpr[rd] = if (regs.gpr[ra] as i64) < (regs.gpr[rb] as i64) {
414                        1
415                    } else {
416                        0
417                    };
418                }
419                Opcode::CmovIz => {
420                    if regs.gpr[rb] == 0 {
421                        regs.gpr[rd] = regs.gpr[ra];
422                    }
423                }
424                Opcode::CmovNz => {
425                    if regs.gpr[rb] != 0 {
426                        regs.gpr[rd] = regs.gpr[ra];
427                    }
428                }
429                Opcode::ShloL32 => {
430                    regs.gpr[rd] = crate::args::sign_extend_32(
431                        (regs.gpr[ra] as u32).wrapping_shl((regs.gpr[rb] % 32) as u32) as u64,
432                    );
433                }
434                Opcode::ShloR32 => {
435                    regs.gpr[rd] = crate::args::sign_extend_32(
436                        (regs.gpr[ra] as u32).wrapping_shr((regs.gpr[rb] % 32) as u32) as u64,
437                    );
438                }
439                Opcode::SharR32 => {
440                    regs.gpr[rd] = (regs.gpr[ra] as u32 as i32)
441                        .wrapping_shr((regs.gpr[rb] % 32) as u32)
442                        as i64 as u64;
443                }
444                Opcode::ShloL64 => {
445                    regs.gpr[rd] = regs.gpr[ra].wrapping_shl((regs.gpr[rb] % 64) as u32);
446                }
447                Opcode::ShloR64 => {
448                    regs.gpr[rd] = regs.gpr[ra].wrapping_shr((regs.gpr[rb] % 64) as u32);
449                }
450                Opcode::SharR64 => {
451                    regs.gpr[rd] =
452                        (regs.gpr[ra] as i64).wrapping_shr((regs.gpr[rb] % 64) as u32) as u64;
453                }
454                Opcode::RotL64 => {
455                    regs.gpr[rd] = regs.gpr[ra].rotate_left((regs.gpr[rb] % 64) as u32);
456                }
457                Opcode::RotR64 => {
458                    regs.gpr[rd] = regs.gpr[ra].rotate_right((regs.gpr[rb] % 64) as u32);
459                }
460                Opcode::RotL32 => {
461                    regs.gpr[rd] = crate::args::sign_extend_32(
462                        (regs.gpr[ra] as u32).rotate_left((regs.gpr[rb] % 32) as u32) as u64,
463                    );
464                }
465                Opcode::RotR32 => {
466                    regs.gpr[rd] = crate::args::sign_extend_32(
467                        (regs.gpr[ra] as u32).rotate_right((regs.gpr[rb] % 32) as u32) as u64,
468                    );
469                }
470                Opcode::AndInv => {
471                    regs.gpr[rd] = regs.gpr[ra] & !regs.gpr[rb];
472                }
473                Opcode::OrInv => {
474                    regs.gpr[rd] = regs.gpr[ra] | !regs.gpr[rb];
475                }
476                Opcode::Xnor => {
477                    regs.gpr[rd] = !(regs.gpr[ra] ^ regs.gpr[rb]);
478                }
479                Opcode::Max => {
480                    regs.gpr[rd] = core::cmp::max(regs.gpr[ra] as i64, regs.gpr[rb] as i64) as u64;
481                }
482                Opcode::MaxU => {
483                    regs.gpr[rd] = core::cmp::max(regs.gpr[ra], regs.gpr[rb]);
484                }
485                Opcode::Min => {
486                    regs.gpr[rd] = core::cmp::min(regs.gpr[ra] as i64, regs.gpr[rb] as i64) as u64;
487                }
488                Opcode::MinU => {
489                    regs.gpr[rd] = core::cmp::min(regs.gpr[ra], regs.gpr[rb]);
490                }
491
492                // === Indirect loads (two reg + imm) ===
493                Opcode::LoadIndU8 => do_load!(
494                    mem,
495                    regs,
496                    exit,
497                    ra,
498                    regs.gpr[rb].wrapping_add(imm1) as u32,
499                    read_u8,
500                    |v| v as u64
501                ),
502                Opcode::LoadIndI8 => do_load!(
503                    mem,
504                    regs,
505                    exit,
506                    ra,
507                    regs.gpr[rb].wrapping_add(imm1) as u32,
508                    read_u8,
509                    |v| v as i8 as i64 as u64
510                ),
511                Opcode::LoadIndU16 => do_load!(
512                    mem,
513                    regs,
514                    exit,
515                    ra,
516                    regs.gpr[rb].wrapping_add(imm1) as u32,
517                    read_u16_le,
518                    |v| v as u64
519                ),
520                Opcode::LoadIndI16 => do_load!(
521                    mem,
522                    regs,
523                    exit,
524                    ra,
525                    regs.gpr[rb].wrapping_add(imm1) as u32,
526                    read_u16_le,
527                    |v| v as i16 as i64 as u64
528                ),
529                Opcode::LoadIndU32 => do_load!(
530                    mem,
531                    regs,
532                    exit,
533                    ra,
534                    regs.gpr[rb].wrapping_add(imm1) as u32,
535                    read_u32_le,
536                    |v| v as u64
537                ),
538                Opcode::LoadIndI32 => do_load!(
539                    mem,
540                    regs,
541                    exit,
542                    ra,
543                    regs.gpr[rb].wrapping_add(imm1) as u32,
544                    read_u32_le,
545                    |v| v as i32 as i64 as u64
546                ),
547                Opcode::LoadIndU64 => do_load!(
548                    mem,
549                    regs,
550                    exit,
551                    ra,
552                    regs.gpr[rb].wrapping_add(imm1) as u32,
553                    read_u64_le,
554                    |v| v
555                ),
556
557                // === Indirect stores (two reg + imm) ===
558                Opcode::StoreIndU8 => do_store!(
559                    mem,
560                    exit,
561                    regs.gpr[rb].wrapping_add(imm1) as u32,
562                    write_u8,
563                    regs.gpr[ra] as u8
564                ),
565                Opcode::StoreIndU16 => do_store!(
566                    mem,
567                    exit,
568                    regs.gpr[rb].wrapping_add(imm1) as u32,
569                    write_u16_le,
570                    regs.gpr[ra] as u16
571                ),
572                Opcode::StoreIndU32 => do_store!(
573                    mem,
574                    exit,
575                    regs.gpr[rb].wrapping_add(imm1) as u32,
576                    write_u32_le,
577                    regs.gpr[ra] as u32
578                ),
579                Opcode::StoreIndU64 => do_store!(
580                    mem,
581                    exit,
582                    regs.gpr[rb].wrapping_add(imm1) as u32,
583                    write_u64_le,
584                    regs.gpr[ra]
585                ),
586
587                // === Div/Rem (three reg, common in crypto) ===
588                Opcode::DivU32 => {
589                    let b = regs.gpr[rb] as u32;
590                    regs.gpr[rd] = (regs.gpr[ra] as u32)
591                        .checked_div(b)
592                        .map(|q| crate::args::sign_extend_32(q as u64))
593                        .unwrap_or(u64::MAX);
594                }
595                Opcode::DivU64 => {
596                    let b = regs.gpr[rb];
597                    regs.gpr[rd] = regs.gpr[ra].checked_div(b).unwrap_or(u64::MAX);
598                }
599                Opcode::DivS32 => {
600                    let a = regs.gpr[ra] as i32;
601                    let b = regs.gpr[rb] as i32;
602                    regs.gpr[rd] = if b == 0 {
603                        u64::MAX
604                    } else if a == i32::MIN && b == -1 {
605                        a as u64
606                    } else {
607                        crate::args::sign_extend_32((a / b) as i64 as u64)
608                    };
609                }
610                Opcode::DivS64 => {
611                    let a = regs.gpr[ra] as i64;
612                    let b = regs.gpr[rb] as i64;
613                    regs.gpr[rd] = if b == 0 {
614                        u64::MAX
615                    } else if a == i64::MIN && b == -1 {
616                        a as u64
617                    } else {
618                        (a / b) as u64
619                    };
620                }
621                Opcode::RemU32 => {
622                    let b = regs.gpr[rb] as u32;
623                    regs.gpr[rd] = if b == 0 {
624                        crate::args::sign_extend_32(regs.gpr[ra] as u32 as u64)
625                    } else {
626                        crate::args::sign_extend_32((regs.gpr[ra] as u32 % b) as u64)
627                    };
628                }
629                Opcode::RemU64 => {
630                    let b = regs.gpr[rb];
631                    regs.gpr[rd] = if b == 0 {
632                        regs.gpr[ra]
633                    } else {
634                        regs.gpr[ra] % b
635                    };
636                }
637                Opcode::RemS32 => {
638                    let a = regs.gpr[ra] as i32;
639                    let b = regs.gpr[rb] as i32;
640                    regs.gpr[rd] = if b == 0 {
641                        a as u64
642                    } else if a == i32::MIN && b == -1 {
643                        0
644                    } else {
645                        crate::args::sign_extend_32((a % b) as i64 as u64)
646                    };
647                }
648                Opcode::RemS64 => {
649                    let a = regs.gpr[ra] as i64;
650                    let b = regs.gpr[rb] as i64;
651                    regs.gpr[rd] = if b == 0 {
652                        a as u64
653                    } else if a == i64::MIN && b == -1 {
654                        0
655                    } else {
656                        (a % b) as u64
657                    };
658                }
659                Opcode::MulUpperSS => {
660                    regs.gpr[rd] = ((regs.gpr[ra] as i64 as i128)
661                        .wrapping_mul(regs.gpr[rb] as i64 as i128)
662                        >> 64) as u64;
663                }
664                Opcode::MulUpperUU => {
665                    regs.gpr[rd] =
666                        ((regs.gpr[ra] as u128).wrapping_mul(regs.gpr[rb] as u128) >> 64) as u64;
667                }
668                Opcode::MulUpperSU => {
669                    regs.gpr[rd] = ((regs.gpr[ra] as i64 as i128)
670                        .wrapping_mul(regs.gpr[rb] as u128 as i128)
671                        >> 64) as u64;
672                }
673
674                // === Two immediates (store_imm: addr = imm1, value = imm2) ===
675                Opcode::StoreImmU8 => {
676                    do_store!(mem, exit, imm1 as u32, write_u8, inst.imm2 as u8)
677                }
678                Opcode::StoreImmU16 => {
679                    do_store!(mem, exit, imm1 as u32, write_u16_le, inst.imm2 as u16)
680                }
681                Opcode::StoreImmU32 => {
682                    do_store!(mem, exit, imm1 as u32, write_u32_le, inst.imm2 as u32)
683                }
684                Opcode::StoreImmU64 => {
685                    do_store!(mem, exit, imm1 as u32, write_u64_le, inst.imm2)
686                }
687
688                // === Absolute address loads (addr = imm1) ===
689                Opcode::LoadU8 => {
690                    do_load!(mem, regs, exit, ra, imm1 as u32, read_u8, |v| v as u64)
691                }
692                Opcode::LoadI8 => {
693                    do_load!(
694                        mem,
695                        regs,
696                        exit,
697                        ra,
698                        imm1 as u32,
699                        read_u8,
700                        |v| v as i8 as i64 as u64
701                    )
702                }
703                Opcode::LoadU16 => {
704                    do_load!(mem, regs, exit, ra, imm1 as u32, read_u16_le, |v| v as u64)
705                }
706                Opcode::LoadI16 => do_load!(mem, regs, exit, ra, imm1 as u32, read_u16_le, |v| v
707                    as i16
708                    as i64
709                    as u64),
710                Opcode::LoadU32 => {
711                    do_load!(mem, regs, exit, ra, imm1 as u32, read_u32_le, |v| v as u64)
712                }
713                Opcode::LoadI32 => do_load!(mem, regs, exit, ra, imm1 as u32, read_u32_le, |v| v
714                    as i32
715                    as i64
716                    as u64),
717                Opcode::LoadU64 => {
718                    do_load!(mem, regs, exit, ra, imm1 as u32, read_u64_le, |v| v)
719                }
720
721                // === Absolute address stores (addr = imm1, value = reg[ra]) ===
722                Opcode::StoreU8 => {
723                    do_store!(mem, exit, imm1 as u32, write_u8, regs.gpr[ra] as u8)
724                }
725                Opcode::StoreU16 => {
726                    do_store!(mem, exit, imm1 as u32, write_u16_le, regs.gpr[ra] as u16)
727                }
728                Opcode::StoreU32 => {
729                    do_store!(mem, exit, imm1 as u32, write_u32_le, regs.gpr[ra] as u32)
730                }
731                Opcode::StoreU64 => {
732                    do_store!(mem, exit, imm1 as u32, write_u64_le, regs.gpr[ra])
733                }
734
735                // === Store imm indirect (addr = reg[ra] + imm1, value = imm2) ===
736                Opcode::StoreImmIndU8 => do_store!(
737                    mem,
738                    exit,
739                    regs.gpr[ra].wrapping_add(imm1) as u32,
740                    write_u8,
741                    inst.imm2 as u8
742                ),
743                Opcode::StoreImmIndU16 => do_store!(
744                    mem,
745                    exit,
746                    regs.gpr[ra].wrapping_add(imm1) as u32,
747                    write_u16_le,
748                    inst.imm2 as u16
749                ),
750                Opcode::StoreImmIndU32 => do_store!(
751                    mem,
752                    exit,
753                    regs.gpr[ra].wrapping_add(imm1) as u32,
754                    write_u32_le,
755                    inst.imm2 as u32
756                ),
757                Opcode::StoreImmIndU64 => do_store!(
758                    mem,
759                    exit,
760                    regs.gpr[ra].wrapping_add(imm1) as u32,
761                    write_u64_le,
762                    inst.imm2
763                ),
764
765                // === LoadImmJump (reg[ra] = imm1, branch to target) ===
766                Opcode::LoadImmJump => {
767                    regs.gpr[ra] = imm1;
768                    if inst.target_idx != u32::MAX {
769                        branch_idx = inst.target_idx;
770                    } else {
771                        exit = Some(ExitReason::Panic);
772                    }
773                }
774
775                // === BranchImm variants (cond on reg[ra] vs imm1) ===
776                Opcode::BranchEqImm
777                | Opcode::BranchNeImm
778                | Opcode::BranchLtUImm
779                | Opcode::BranchLeUImm
780                | Opcode::BranchGeUImm
781                | Opcode::BranchGtUImm
782                | Opcode::BranchLtSImm
783                | Opcode::BranchLeSImm
784                | Opcode::BranchGeSImm
785                | Opcode::BranchGtSImm => {
786                    let (a, b) = (regs.gpr[ra], imm1);
787                    let cond = match inst.opcode {
788                        Opcode::BranchEqImm => a == b,
789                        Opcode::BranchNeImm => a != b,
790                        Opcode::BranchLtUImm => a < b,
791                        Opcode::BranchLeUImm => a <= b,
792                        Opcode::BranchGeUImm => a >= b,
793                        Opcode::BranchGtUImm => a > b,
794                        Opcode::BranchLtSImm => (a as i64) < (b as i64),
795                        Opcode::BranchLeSImm => (a as i64) <= (b as i64),
796                        Opcode::BranchGeSImm => (a as i64) >= (b as i64),
797                        Opcode::BranchGtSImm => (a as i64) > (b as i64),
798                        _ => unreachable!(),
799                    };
800                    if cond {
801                        if inst.target_idx != u32::MAX {
802                            branch_idx = inst.target_idx;
803                        } else {
804                            exit = Some(ExitReason::Panic);
805                        }
806                    }
807                }
808
809                // === Two registers + two immediates ===
810                Opcode::LoadImmJumpInd => {
811                    regs.gpr[ra] = imm1;
812                    let addr = regs.gpr[rb].wrapping_add(inst.imm2) % (1u64 << 32);
813                    match djump(addr, jump_table, basic_block_starts) {
814                        Ok(target_pc) => {
815                            let t = target_pc as usize;
816                            if t < pc_to_idx.len() {
817                                let tidx = pc_to_idx[t];
818                                if tidx != u32::MAX {
819                                    branch_idx = tidx;
820                                } else {
821                                    exit = Some(ExitReason::Panic);
822                                }
823                            } else {
824                                exit = Some(ExitReason::Panic);
825                            }
826                        }
827                        Err(reason) => exit = Some(reason),
828                    }
829                }
830            }
831
832            if let Some(reason) = exit {
833                regs.pc = inst.pc as u64;
834                return reason;
835            }
836
837            idx = if branch_idx == u32::MAX {
838                inst.next_idx
839            } else {
840                branch_idx
841            };
842        }
843    }
844}
845
846/// Dynamic-jump address resolution (eq A.18).
847///
848/// `a` is the post-imm-add jump value (mod 2^32, but passed as u64
849/// here). The jump table is indexed by `a / 2`, minus 1. Targets
850/// must land on basic-block starts.
851fn djump(a: u64, jump_table: &[u32], basic_block_starts: &[bool]) -> Result<u32, ExitReason> {
852    const ZA: u64 = 2;
853    if a == 0 || a > (jump_table.len() as u64) * ZA || !a.is_multiple_of(ZA) {
854        return Err(ExitReason::Panic);
855    }
856    let idx = (a / ZA) as usize - 1;
857    let target = jump_table[idx];
858    let t = target as usize;
859    if t >= basic_block_starts.len() || !basic_block_starts[t] {
860        return Err(ExitReason::Panic);
861    }
862    Ok(target)
863}
864
865#[cfg(test)]
866mod tests {
867    use super::*;
868    use crate::ecall::PanickingHandler;
869    use crate::mem::Mem;
870    use crate::regs::REG_COUNT;
871
872    /// Helper: build a PvmProgram from a single trap byte.
873    fn single_byte_prog(opcode_byte: u8) -> PvmProgram {
874        PvmProgram::new(vec![opcode_byte], vec![1u8], vec![], 25).unwrap()
875    }
876
877    fn run_with_panic_handler(prog: &PvmProgram, gas: u64) -> (ExitReason, Regs) {
878        let mut regs = Regs::new();
879        let mut mem = Mem::new();
880        let mut g = GasCounter::new(gas);
881        let mut h = PanickingHandler;
882        let r = Interpreter::run(prog, &mut regs, &mut mem, &mut g, &mut h);
883        (r, regs)
884    }
885
886    #[test]
887    fn trap_returns_trap() {
888        let (r, _) = run_with_panic_handler(&single_byte_prog(0), 1000);
889        assert_eq!(r, ExitReason::Trap);
890    }
891
892    #[test]
893    fn fallthrough_falls_into_sentinel_trap() {
894        let (r, _) = run_with_panic_handler(&single_byte_prog(1), 1000);
895        assert_eq!(r, ExitReason::Trap);
896    }
897
898    #[test]
899    fn unlikely_falls_into_sentinel_trap() {
900        let (r, _) = run_with_panic_handler(&single_byte_prog(2), 1000);
901        assert_eq!(r, ExitReason::Trap);
902    }
903
904    /// Ecalli with `imm = 42` routes through the EcallHandler.
905    #[test]
906    fn ecalli_routes_through_handler() {
907        // Ecalli (opcode 10, OneImm category): [10, 42, <next-trap>].
908        let prog = PvmProgram::new(vec![10u8, 42, 0], vec![1, 0, 1], vec![], 25).unwrap();
909
910        struct Capture {
911            seen: Option<EcallKind>,
912        }
913        impl EcallHandler for Capture {
914            fn handle(
915                &mut self,
916                kind: EcallKind,
917                _r: &mut Regs,
918                _m: &mut dyn Memory,
919            ) -> EcallResult {
920                self.seen = Some(kind);
921                EcallResult::Exit(ExitReason::HostCall(match kind {
922                    EcallKind::Ecalli(op) => op,
923                    EcallKind::Ecall => 0,
924                }))
925            }
926        }
927
928        let mut regs = Regs::new();
929        let mut mem = Mem::new();
930        let mut gas = GasCounter::new(1000);
931        let mut h = Capture { seen: None };
932        let r = Interpreter::run(&prog, &mut regs, &mut mem, &mut gas, &mut h);
933        assert_eq!(r, ExitReason::HostCall(42));
934        assert_eq!(h.seen, Some(EcallKind::Ecalli(42)));
935    }
936
937    // ====================================================================
938    // Conformance tests: cherry-picked from v2
939    // `javm/src/interpreter/mod.rs::tests`. Each test ports a v2 single-
940    // step test by extending its program with a trailing trap so the v3
941    // `run()` exit reason is `Trap` and final register state can be
942    // observed afterward.
943    // ====================================================================
944
945    /// Run `program` with starting registers; panic handler. Returns
946    /// `(exit_reason, regs, gas_used)`.
947    fn run_with_regs(
948        code: Vec<u8>,
949        bitmask: Vec<u8>,
950        initial_regs: [u64; REG_COUNT],
951        gas_budget: u64,
952    ) -> (ExitReason, Regs, u64) {
953        let prog = PvmProgram::new(code, bitmask, vec![], 25).unwrap();
954        let mut regs = Regs::new();
955        regs.gpr = initial_regs;
956        let mut mem = Mem::new();
957        let mut g = GasCounter::new(gas_budget);
958        let mut h = PanickingHandler;
959        let r = Interpreter::run(&prog, &mut regs, &mut mem, &mut g, &mut h);
960        (r, regs, gas_budget - g.remaining())
961    }
962
963    #[test]
964    fn out_of_gas_in_long_fallthrough() {
965        // 100 fallthroughs, only 5 gas — should OOG.
966        let (r, _, _) = run_with_regs(vec![1u8; 100], vec![1u8; 100], [0; REG_COUNT], 5);
967        assert_eq!(r, ExitReason::OutOfGas);
968    }
969
970    #[test]
971    fn empty_program_panics() {
972        // Zero-length code: starting PC is OOB → Panic.
973        let prog = PvmProgram::new(vec![], vec![], vec![], 25).unwrap();
974        let mut regs = Regs::new();
975        let mut mem = Mem::new();
976        let mut g = GasCounter::new(100);
977        let mut h = PanickingHandler;
978        assert_eq!(
979            Interpreter::run(&prog, &mut regs, &mut mem, &mut g, &mut h),
980            ExitReason::Panic
981        );
982    }
983
984    #[test]
985    fn load_imm_sets_register() {
986        // LoadImm (opcode 51, OneRegOneImm), reg 0, imm = 42 (4 bytes LE).
987        // bytes: [51, 0x00, 42, 0, 0, 0, 0 (trap)]
988        // bitmask: [1, 0, 0, 0, 0, 0, 1] — instruction is 6 bytes, then trap.
989        let code = vec![51, 0x00, 42, 0, 0, 0, 0];
990        let bitmask = vec![1, 0, 0, 0, 0, 0, 1];
991        let (r, regs, _) = run_with_regs(code, bitmask, [0; REG_COUNT], 100);
992        assert_eq!(r, ExitReason::Trap);
993        assert_eq!(regs.gpr[0], 42);
994    }
995
996    #[test]
997    fn add_imm_64_two_reg_one_imm() {
998        // AddImm64 (opcode 149, TwoRegOneImm), reg byte 0x10 (rA=0, rB=1), imm=10.
999        // reg[1] = 32 + imm 10 → reg[0] = 42.
1000        let code = vec![149, 0x10, 10, 0, 0, 0, 0];
1001        let bitmask = vec![1, 0, 0, 0, 0, 0, 1];
1002        let mut regs = [0u64; REG_COUNT];
1003        regs[1] = 32;
1004        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1005        assert_eq!(r, ExitReason::Trap);
1006        assert_eq!(regs_out.gpr[0], 42);
1007    }
1008
1009    #[test]
1010    fn add64_three_reg() {
1011        // Add64 (opcode 200, ThreeReg), reg byte 0x10 (rA=0, rB=1), rD=2.
1012        // reg[0]=100 + reg[1]=200 → reg[2]=300.
1013        let code = vec![200, 0x10, 2, 0];
1014        let bitmask = vec![1, 0, 0, 1];
1015        let mut regs = [0u64; REG_COUNT];
1016        regs[0] = 100;
1017        regs[1] = 200;
1018        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1019        assert_eq!(r, ExitReason::Trap);
1020        assert_eq!(regs_out.gpr[2], 300);
1021    }
1022
1023    #[test]
1024    fn sub64_three_reg() {
1025        // Sub64 (opcode 201). 300 - 100 = 200.
1026        let code = vec![201, 0x10, 2, 0];
1027        let bitmask = vec![1, 0, 0, 1];
1028        let mut regs = [0u64; REG_COUNT];
1029        regs[0] = 300;
1030        regs[1] = 100;
1031        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1032        assert_eq!(r, ExitReason::Trap);
1033        assert_eq!(regs_out.gpr[2], 200);
1034    }
1035
1036    #[test]
1037    fn and_three_reg() {
1038        // And (opcode 210). 0xFF00 & 0x0FF0 = 0x0F00.
1039        let code = vec![210, 0x10, 2, 0];
1040        let bitmask = vec![1, 0, 0, 1];
1041        let mut regs = [0u64; REG_COUNT];
1042        regs[0] = 0xFF00;
1043        regs[1] = 0x0FF0;
1044        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1045        assert_eq!(r, ExitReason::Trap);
1046        assert_eq!(regs_out.gpr[2], 0x0F00);
1047    }
1048
1049    #[test]
1050    fn set_lt_u_three_reg() {
1051        // SetLtU (opcode 216). 5 < 10 → 1.
1052        let code = vec![216, 0x10, 2, 0];
1053        let bitmask = vec![1, 0, 0, 1];
1054        let mut regs = [0u64; REG_COUNT];
1055        regs[0] = 5;
1056        regs[1] = 10;
1057        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1058        assert_eq!(r, ExitReason::Trap);
1059        assert_eq!(regs_out.gpr[2], 1);
1060    }
1061
1062    #[test]
1063    fn move_reg_two_reg() {
1064        // MoveReg (opcode 100, TwoReg). reg byte 0x10 = rD=0, rA=1.
1065        let code = vec![100, 0x10, 0];
1066        let bitmask = vec![1, 0, 1];
1067        let mut regs = [0u64; REG_COUNT];
1068        regs[1] = 42;
1069        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1070        assert_eq!(r, ExitReason::Trap);
1071        assert_eq!(regs_out.gpr[0], 42);
1072    }
1073
1074    #[test]
1075    fn count_set_bits_64() {
1076        // CountSetBits64 (opcode 102). 0xFF has 8 set bits.
1077        let code = vec![102, 0x10, 0];
1078        let bitmask = vec![1, 0, 1];
1079        let mut regs = [0u64; REG_COUNT];
1080        regs[1] = 0xFF;
1081        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1082        assert_eq!(r, ExitReason::Trap);
1083        assert_eq!(regs_out.gpr[0], 8);
1084    }
1085
1086    #[test]
1087    fn div_u64_by_zero_returns_max() {
1088        // DivU64 (opcode 203). 100 / 0 → u64::MAX (per spec; not a fault).
1089        let code = vec![203, 0x10, 2, 0];
1090        let bitmask = vec![1, 0, 0, 1];
1091        let mut regs = [0u64; REG_COUNT];
1092        regs[0] = 100;
1093        regs[1] = 0;
1094        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1095        assert_eq!(r, ExitReason::Trap);
1096        assert_eq!(regs_out.gpr[2], u64::MAX);
1097    }
1098
1099    #[test]
1100    fn sign_extend_8() {
1101        // SignExtend8 (opcode 108). 0x80 → sign-extended -128.
1102        let code = vec![108, 0x10, 0];
1103        let bitmask = vec![1, 0, 1];
1104        let mut regs = [0u64; REG_COUNT];
1105        regs[1] = 0x80;
1106        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1107        assert_eq!(r, ExitReason::Trap);
1108        assert_eq!(regs_out.gpr[0] as i64, -128);
1109    }
1110
1111    #[test]
1112    fn reverse_bytes_u64() {
1113        // ReverseBytes (opcode 111).
1114        let code = vec![111, 0x10, 0];
1115        let bitmask = vec![1, 0, 1];
1116        let mut regs = [0u64; REG_COUNT];
1117        regs[1] = 0x0123456789ABCDEF;
1118        let (r, regs_out, _) = run_with_regs(code, bitmask, regs, 100);
1119        assert_eq!(r, ExitReason::Trap);
1120        assert_eq!(regs_out.gpr[0], 0xEFCDAB8967452301);
1121    }
1122
1123    #[test]
1124    fn sbrk_panics() {
1125        // Sbrk (opcode 101) was removed in JAR v0.8.0; the interpreter
1126        // returns Panic.
1127        let code = vec![101, 0x00];
1128        let bitmask = vec![1, 0];
1129        let (r, _, _) = run_with_regs(code, bitmask, [0; REG_COUNT], 100);
1130        assert_eq!(r, ExitReason::Panic);
1131    }
1132
1133    #[test]
1134    fn page_fault_on_unmapped_load() {
1135        // LoadU8 (opcode 52). Mem is empty → page fault.
1136        // LoadU8 layout: [opcode, reg_byte, addr_LE...] (OneRegOneImm).
1137        // Here the immediate is 0x1000 (4 bytes LE).
1138        let code = vec![52, 0x00, 0x00, 0x10, 0x00, 0x00, 0];
1139        let bitmask = vec![1, 0, 0, 0, 0, 0, 1];
1140        let (r, _, _) = run_with_regs(code, bitmask, [0; REG_COUNT], 100);
1141        assert_eq!(r, ExitReason::PageFault(0x1000));
1142    }
1143
1144    /// Branch-target / gas-block boundary: v2 issue #155 regression.
1145    /// Verifies that branch targets are valid basic-block landing
1146    /// sites but do NOT introduce new gas-block starts.
1147    #[test]
1148    fn gas_blocks_exclude_branch_targets() {
1149        use crate::decode::{
1150            compute_basic_block_starts, compute_block_gas_costs, compute_gas_block_starts,
1151        };
1152
1153        // Layout (verbatim from v2 test):
1154        //   PC 0: Fallthrough (1)  — terminator
1155        //   PC 1: MoveReg 0,1      — not terminator (skip=1)
1156        //   PC 3: MoveReg 0,1      — not terminator (skip=1)
1157        //   PC 5: Jump, offset = -2 LE → target = PC 3
1158        //   PC 10: Trap            — catches fallthrough
1159        let code = vec![1, 100, 0x10, 100, 0x10, 40, 0xFE, 0xFF, 0xFF, 0xFF, 0];
1160        let bitmask = vec![1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1];
1161
1162        let bb = compute_basic_block_starts(&code, &bitmask);
1163        let gas = compute_gas_block_starts(&code, &bitmask);
1164        let costs = compute_block_gas_costs(&code, &bitmask, &gas, 25);
1165
1166        // PC 3 is a branch target → in bb_starts, NOT in gas_starts.
1167        assert!(bb[3], "PC 3 is a branch target");
1168        assert!(!gas[3], "PC 3 is NOT a gas block start");
1169        assert_eq!(costs[3], 0, "PC 3 carries no gas cost");
1170        // PC 1, 10 are post-terminator → gas block starts.
1171        assert!(gas[1] && gas[10]);
1172        assert!(costs[1] > 0 && costs[10] > 0);
1173    }
1174}