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 .chunks_exact(8)
70 .take(self.block_count)
71 .map(|b| u64::from_le_bytes(b.try_into().unwrap_or([0; 8])))
72 .collect()
73 }
74
75 /// Get the number of blocks being tracked
76 #[inline]
77 #[must_use]
78 pub fn block_count(&self) -> usize {
79 self.block_count
80 }
81
82 /// Get the counter base offset
83 #[inline]
84 #[must_use]
85 pub fn counter_base(&self) -> usize {
86 self.counter_base
87 }
88
89 /// Get the total memory size
90 #[inline]
91 #[must_use]
92 pub fn memory_size(&self) -> usize {
93 self.memory.len()
94 }
95
96 /// Check if a block is covered (counter > 0)
97 #[inline]
98 #[must_use]
99 pub fn is_covered(&self, block: BlockId) -> bool {
100 self.read_counter(block) > 0
101 }
102
103 /// Count the number of covered blocks
104 #[must_use]
105 pub fn covered_count(&self) -> usize {
106 self.read_all_counters().iter().filter(|&&c| c > 0).count()
107 }
108
109 /// Calculate coverage percentage
110 #[must_use]
111 pub fn coverage_percent(&self) -> f64 {
112 if self.block_count == 0 {
113 return 100.0; // Vacuously true
114 }
115 (self.covered_count() as f64 / self.block_count as f64) * 100.0
116 }
117}