jugar_probar/coverage/memory.rs
1//! Zero-Copy WASM Memory View (Muda Elimination)
2//!
3//! Per spec ยง5.3: Zero-copy coverage collection eliminates serialization waste.
4//!
5//! Direct access to WASM linear memory for reading coverage counters
6//! without any copying or serialization overhead.
7
8use super::BlockId;
9
10/// Zero-copy WASM memory view for coverage (Muda elimination)
11///
12/// Provides direct access to coverage counters stored in WASM linear memory.
13/// Counters are stored as little-endian u64 values.
14#[derive(Debug)]
15pub struct CoverageMemoryView<'a> {
16 /// Direct pointer to WASM linear memory
17 memory: &'a [u8],
18 /// Counter array offset in bytes
19 counter_base: usize,
20 /// Number of blocks (counters)
21 block_count: usize,
22}
23
24impl<'a> CoverageMemoryView<'a> {
25 /// Create a new memory view
26 ///
27 /// # Arguments
28 ///
29 /// * `memory` - Reference to WASM linear memory
30 /// * `counter_base` - Byte offset where counters start
31 /// * `block_count` - Number of blocks/counters
32 #[must_use]
33 pub fn new(memory: &'a [u8], counter_base: usize, block_count: usize) -> Self {
34 Self {
35 memory,
36 counter_base,
37 block_count,
38 }
39 }
40
41 /// Read counter without copy (Genchi Genbutsu)
42 ///
43 /// Directly reads the u64 counter value for the given block.
44 #[inline]
45 #[must_use]
46 pub fn read_counter(&self, block: BlockId) -> u64 {
47 let idx = block.as_u32() as usize;
48 if idx >= self.block_count {
49 return 0;
50 }
51
52 let offset = self.counter_base + idx * 8;
53 if offset + 8 > self.memory.len() {
54 return 0;
55 }
56
57 let bytes = &self.memory[offset..offset + 8];
58 u64::from_le_bytes(bytes.try_into().unwrap_or([0; 8]))
59 }
60
61 /// SIMD batch read all counters
62 ///
63 /// Reads all counters at once. In a full implementation, this would
64 /// use Trueno's SIMD operations for acceleration.
65 #[must_use]
66 pub fn read_all_counters(&self) -> Vec<u64> {
67 let slice = &self.memory[self.counter_base..];
68 slice
69 .as_chunks::<8>()
70 .0
71 .iter()
72 .take(self.block_count)
73 .map(|b| u64::from_le_bytes(*b))
74 .collect()
75 }
76
77 /// Get the number of blocks being tracked
78 #[inline]
79 #[must_use]
80 pub fn block_count(&self) -> usize {
81 self.block_count
82 }
83
84 /// Get the counter base offset
85 #[inline]
86 #[must_use]
87 pub fn counter_base(&self) -> usize {
88 self.counter_base
89 }
90
91 /// Get the total memory size
92 #[inline]
93 #[must_use]
94 pub fn memory_size(&self) -> usize {
95 self.memory.len()
96 }
97
98 /// Check if a block is covered (counter > 0)
99 #[inline]
100 #[must_use]
101 pub fn is_covered(&self, block: BlockId) -> bool {
102 self.read_counter(block) > 0
103 }
104
105 /// Count the number of covered blocks
106 #[must_use]
107 pub fn covered_count(&self) -> usize {
108 self.read_all_counters().iter().filter(|&&c| c > 0).count()
109 }
110
111 /// Calculate coverage percentage
112 #[must_use]
113 pub fn coverage_percent(&self) -> f64 {
114 if self.block_count == 0 {
115 return 100.0; // Vacuously true
116 }
117 (self.covered_count() as f64 / self.block_count as f64) * 100.0
118 }
119}