Skip to main content

javm_exec/
gas_sim.rs

1//! Single-pass pipeline gas model (JAR v0.8.0).
2//!
3//! O(n) single-pass model tracking per-register completion cycles.
4//! Replaces the full ROB-based pipeline simulation.
5//!
6//! Tracks `reg_done[15]` (cycle when each register is ready) and decode
7//! throughput (4 slots/cycle). No ROB, no priority loop, no EU contention.
8//! See `docs/gas-metering-design.md` for detailed comparison.
9
10use crate::gas_cost::FastCost;
11
12/// Single-pass pipeline gas simulator. O(1) per instruction, stack-allocated.
13pub struct GasSimulator {
14    reg_done: [u32; 15],
15    cycle: u32,
16    decode_used: u8,
17    max_done: u32,
18}
19
20impl Default for GasSimulator {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl GasSimulator {
27    pub fn new() -> Self {
28        Self {
29            reg_done: [0; 15],
30            cycle: 0,
31            decode_used: 0,
32            max_done: 0,
33        }
34    }
35
36    /// Fast path: feed an instruction using direct register indices instead of
37    /// bitmasks. Avoids the shift+OR bitmask construction and trailing_zeros
38    /// extraction loop. For typical 2-source, 1-dest instructions.
39    /// `src1`/`src2` are source register indices (0..14, or 0xFF for "none").
40    /// `dst` is destination register index (0..14, or 0xFF for "none").
41    #[inline(always)]
42    pub fn feed_direct(&mut self, cycles: u8, decode_slots: u8, src1: u8, src2: u8, dst: u8) {
43        // Match Lean semantics: advance cycle only if ALL 4 decode slots are
44        // already consumed. As long as ≥1 slot remains, the new instruction
45        // begins decoding this cycle regardless of how many slots it needs.
46        if self.decode_used >= 4 {
47            self.cycle += 1;
48            self.decode_used = decode_slots;
49        } else {
50            self.decode_used += decode_slots;
51        }
52        let mut start = self.cycle;
53        if src1 < 15 {
54            start = start.max(self.reg_done[src1 as usize]);
55        }
56        if src2 < 15 {
57            start = start.max(self.reg_done[src2 as usize]);
58        }
59        let done = start + cycles as u32;
60        if dst < 15 {
61            self.reg_done[dst as usize] = done;
62        }
63        self.max_done = self.max_done.max(done);
64    }
65
66    /// Process one instruction. O(1).
67    #[inline]
68    pub fn feed(&mut self, cost: &FastCost) {
69        // Decode throughput: 4 slots per cycle.
70        // Match Lean semantics: advance cycle only if ALL 4 slots consumed.
71        if self.decode_used >= 4 {
72            self.cycle += 1;
73            self.decode_used = cost.decode_slots;
74        } else {
75            self.decode_used += cost.decode_slots;
76        }
77
78        // move_reg: zero-cycle frontend-only op, propagate reg_done
79        if cost.is_move_reg {
80            let src_reg = cost.src_mask.trailing_zeros() as usize;
81            let dst_reg = cost.dst_mask.trailing_zeros() as usize;
82            if src_reg < 15 && dst_reg < 15 {
83                self.reg_done[dst_reg] = self.reg_done[src_reg];
84            }
85            return;
86        }
87
88        // Data dependencies: start = max(decode_cycle, max(reg_done[src_regs]))
89        let mut start = self.cycle;
90        let mut src = cost.src_mask;
91        while src != 0 {
92            let r = src.trailing_zeros() as usize;
93            src &= src - 1;
94            if r < 15 {
95                start = start.max(self.reg_done[r]);
96            }
97        }
98
99        // Completion
100        let done = start + cost.cycles as u32;
101
102        // Update destination registers
103        let mut dst = cost.dst_mask;
104        while dst != 0 {
105            let r = dst.trailing_zeros() as usize;
106            dst &= dst - 1;
107            if r < 15 {
108                self.reg_done[r] = done;
109            }
110        }
111
112        // Track maximum completion cycle
113        self.max_done = self.max_done.max(done);
114    }
115
116    /// Return block gas cost: max(max_done - 3, 1).
117    #[inline]
118    pub fn flush_and_get_cost(&self) -> u32 {
119        if self.max_done > 3 {
120            self.max_done - 3
121        } else {
122            1
123        }
124    }
125
126    /// Reset for the next gas block.
127    #[inline]
128    pub fn reset(&mut self) {
129        self.reg_done = [0; 15];
130        self.cycle = 0;
131        self.decode_used = 0;
132        self.max_done = 0;
133    }
134}