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}