Skip to main content

javm_exec/
program.rs

1//! PVM program input: raw bytecode + bitmask + jump table.
2//!
3//! javm-exec consumes pre-decoded program fields. The JAR blob
4//! container (cap manifest + data section + code sub-blob) lives
5//! upstream — at the cap layer or integration crate. By the time
6//! the program reaches the execution engine, the caller has already
7//! extracted the executable parts.
8//!
9//! This module is a thin container plus a few helpers. The
10//! interpreter and recompiler both consume `&PvmProgram`.
11
12use crate::error::ProgramError;
13use alloc::vec::Vec;
14
15/// PVM program for execution.
16///
17/// - `code` is the raw byte-encoded PVM bytecode (JAM Gray Paper
18///   Appendix A.5).
19/// - `bitmask` is the **unpacked** form (one byte per code position);
20///   `bitmask[i] == 1` iff a PVM instruction starts at `code[i]`.
21///   Packed bitmasks (1 bit per byte) live only in the serialized
22///   blob format upstream; by the time the program reaches this
23///   layer the upstream parser has unpacked them.
24/// - `jump_table[i]` is the target byte offset within `code` for a
25///   branch / jump whose immediate decodes to `i`.
26/// - `mem_cycles` is the L/S latency tier (see `compute_mem_cycles`).
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct PvmProgram {
29    pub code: Vec<u8>,
30    pub bitmask: Vec<u8>,
31    pub jump_table: Vec<u32>,
32    pub mem_cycles: u8,
33}
34
35impl PvmProgram {
36    /// Construct with validation: `bitmask.len()` must equal
37    /// `code.len()`.
38    pub fn new(
39        code: Vec<u8>,
40        bitmask: Vec<u8>,
41        jump_table: Vec<u32>,
42        mem_cycles: u8,
43    ) -> Result<Self, ProgramError> {
44        if bitmask.len() != code.len() {
45            return Err(ProgramError::BitmaskLenMismatch {
46                code_len: code.len(),
47                bitmask_len: bitmask.len(),
48            });
49        }
50        Ok(Self {
51            code,
52            bitmask,
53            jump_table,
54            mem_cycles,
55        })
56    }
57
58    /// `true` iff a PVM instruction starts at byte offset `pc` in
59    /// `code`. False if `pc` is out of range.
60    pub fn is_insn_start(&self, pc: u32) -> bool {
61        self.bitmask
62            .get(pc as usize)
63            .map(|&b| b != 0)
64            .unwrap_or(false)
65    }
66
67    /// Number of bytecode bytes.
68    pub fn code_len(&self) -> usize {
69        self.code.len()
70    }
71}
72
73/// L/S memory cycle latency tier as a function of accessible page
74/// count (cherry-picked from v2 `javm/src/lib.rs::compute_mem_cycles`):
75///
76/// - ≤ 8 MiB (2048 pages):  25 cycles (L2)
77/// - ≤ 32 MiB (8192 pages): 50 cycles (L3)
78/// - ≤ 256 MiB:             75 cycles (DRAM)
79/// - > 256 MiB:             100 cycles (DRAM saturated)
80pub fn compute_mem_cycles(total_pages: u32) -> u8 {
81    match total_pages {
82        0..=2048 => 25,
83        2049..=8192 => 50,
84        8193..=65536 => 75,
85        _ => 100,
86    }
87}
88
89/// Unpack a packed bitmask (1 bit per byte) into the unpacked form
90/// (one byte per code position; 0 or 1). The packed form is the
91/// serialized representation; the unpacked form is what
92/// `PvmProgram::bitmask` carries.
93pub fn unpack_bitmask(packed: &[u8], code_len: usize) -> Vec<u8> {
94    let mut out = vec![0u8; code_len];
95    for i in 0..code_len {
96        out[i] = (packed[i / 8] >> (i % 8)) & 1;
97    }
98    out
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn new_validates_bitmask_len() {
107        assert!(PvmProgram::new(vec![0u8; 4], vec![1u8; 4], vec![], 25).is_ok());
108        let err = PvmProgram::new(vec![0u8; 4], vec![1u8; 3], vec![], 25).unwrap_err();
109        assert!(matches!(err, ProgramError::BitmaskLenMismatch { .. }));
110    }
111
112    #[test]
113    fn is_insn_start_indexes_bitmask() {
114        let p = PvmProgram::new(vec![0u8, 1, 0, 1], vec![1u8, 0, 1, 0], vec![], 25).unwrap();
115        assert!(p.is_insn_start(0));
116        assert!(!p.is_insn_start(1));
117        assert!(p.is_insn_start(2));
118        assert!(!p.is_insn_start(3));
119        // Out of range → false.
120        assert!(!p.is_insn_start(99));
121    }
122
123    #[test]
124    fn compute_mem_cycles_tiers() {
125        assert_eq!(compute_mem_cycles(0), 25);
126        assert_eq!(compute_mem_cycles(2048), 25);
127        assert_eq!(compute_mem_cycles(2049), 50);
128        assert_eq!(compute_mem_cycles(8192), 50);
129        assert_eq!(compute_mem_cycles(8193), 75);
130        assert_eq!(compute_mem_cycles(65536), 75);
131        assert_eq!(compute_mem_cycles(65537), 100);
132        assert_eq!(compute_mem_cycles(u32::MAX), 100);
133    }
134
135    #[test]
136    fn unpack_bitmask_round_trip() {
137        // Pack [1, 0, 1, 1, 0, 0, 0, 1] into a single byte: 0b1000_1101
138        // Bits are packed LSB-first per v2: bit 0 = pos 0, bit 1 = pos 1, ...
139        let packed = [0b1000_1101u8];
140        let unpacked = unpack_bitmask(&packed, 8);
141        assert_eq!(unpacked, vec![1, 0, 1, 1, 0, 0, 0, 1]);
142    }
143
144    #[test]
145    fn unpack_bitmask_short_code() {
146        // 3-byte code → 1 byte of packed bitmask, 3 entries unpacked.
147        let packed = [0b101u8];
148        let unpacked = unpack_bitmask(&packed, 3);
149        assert_eq!(unpacked, vec![1, 0, 1]);
150    }
151}