Skip to main content

wave_emu/
stats.rs

1// Copyright 2026 Ojima Abraham
2// SPDX-License-Identifier: Apache-2.0
3
4//! Execution statistics tracking. Counts instructions by category (integer, float,
5//!
6//! memory, control, wave ops, atomics), memory accesses, barriers, and divergent
7//! branches. Aggregated across waves and workgroups for final reporting.
8
9#[derive(Debug, Clone, Default)]
10pub struct ExecutionStats {
11    pub instructions_executed: u64,
12    pub integer_ops: u64,
13    pub float_ops: u64,
14    pub memory_ops: u64,
15    pub control_ops: u64,
16    pub wave_ops: u64,
17    pub atomic_ops: u64,
18
19    pub device_loads: u64,
20    pub device_load_bytes: u64,
21    pub device_stores: u64,
22    pub device_store_bytes: u64,
23
24    pub local_loads: u64,
25    pub local_load_bytes: u64,
26    pub local_stores: u64,
27    pub local_store_bytes: u64,
28
29    pub barriers: u64,
30    pub divergent_branches: u64,
31
32    pub workgroups_executed: u64,
33    pub waves_executed: u64,
34}
35
36impl ExecutionStats {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    pub fn record_instruction(&mut self, category: InstructionCategory) {
42        self.instructions_executed += 1;
43        match category {
44            InstructionCategory::Integer => self.integer_ops += 1,
45            InstructionCategory::Float => self.float_ops += 1,
46            InstructionCategory::Memory => self.memory_ops += 1,
47            InstructionCategory::Control => self.control_ops += 1,
48            InstructionCategory::WaveOp => self.wave_ops += 1,
49            InstructionCategory::Atomic => self.atomic_ops += 1,
50        }
51    }
52
53    pub fn record_device_load(&mut self, bytes: u64) {
54        self.device_loads += 1;
55        self.device_load_bytes += bytes;
56    }
57
58    pub fn record_device_store(&mut self, bytes: u64) {
59        self.device_stores += 1;
60        self.device_store_bytes += bytes;
61    }
62
63    pub fn record_local_load(&mut self, bytes: u64) {
64        self.local_loads += 1;
65        self.local_load_bytes += bytes;
66    }
67
68    pub fn record_local_store(&mut self, bytes: u64) {
69        self.local_stores += 1;
70        self.local_store_bytes += bytes;
71    }
72
73    pub fn record_barrier(&mut self) {
74        self.barriers += 1;
75    }
76
77    pub fn record_divergent_branch(&mut self) {
78        self.divergent_branches += 1;
79    }
80
81    pub fn record_wave(&mut self) {
82        self.waves_executed += 1;
83    }
84
85    pub fn merge(&mut self, other: &ExecutionStats) {
86        self.instructions_executed += other.instructions_executed;
87        self.integer_ops += other.integer_ops;
88        self.float_ops += other.float_ops;
89        self.memory_ops += other.memory_ops;
90        self.control_ops += other.control_ops;
91        self.wave_ops += other.wave_ops;
92        self.atomic_ops += other.atomic_ops;
93
94        self.device_loads += other.device_loads;
95        self.device_load_bytes += other.device_load_bytes;
96        self.device_stores += other.device_stores;
97        self.device_store_bytes += other.device_store_bytes;
98
99        self.local_loads += other.local_loads;
100        self.local_load_bytes += other.local_load_bytes;
101        self.local_stores += other.local_stores;
102        self.local_store_bytes += other.local_store_bytes;
103
104        self.barriers += other.barriers;
105        self.divergent_branches += other.divergent_branches;
106
107        self.waves_executed += other.waves_executed;
108    }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum InstructionCategory {
113    Integer,
114    Float,
115    Memory,
116    Control,
117    WaveOp,
118    Atomic,
119}
120
121pub struct TraceWriter {
122    enabled: bool,
123}
124
125impl TraceWriter {
126    pub fn new(enabled: bool) -> Self {
127        Self { enabled }
128    }
129
130    pub fn trace_instruction(&self, workgroup_id: [u32; 3], wave_id: u32, pc: u32, disasm: &str) {
131        if self.enabled {
132            eprintln!(
133                "wg({},{},{}) wave[{}] pc=0x{:04x}: {}",
134                workgroup_id[0], workgroup_id[1], workgroup_id[2], wave_id, pc, disasm
135            );
136        }
137    }
138
139    pub fn is_enabled(&self) -> bool {
140        self.enabled
141    }
142}
143
144impl Default for TraceWriter {
145    fn default() -> Self {
146        Self::new(false)
147    }
148}
149
150impl std::fmt::Display for ExecutionStats {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        writeln!(f, "Execution Statistics:")?;
153        writeln!(f, "  Instructions executed: {}", self.instructions_executed)?;
154        writeln!(f, "    Integer ops:         {}", self.integer_ops)?;
155        writeln!(f, "    Float ops:           {}", self.float_ops)?;
156        writeln!(f, "    Memory ops:          {}", self.memory_ops)?;
157        writeln!(f, "    Control ops:         {}", self.control_ops)?;
158        writeln!(f, "    Wave ops:            {}", self.wave_ops)?;
159        writeln!(f, "    Atomic ops:          {}", self.atomic_ops)?;
160        writeln!(f)?;
161        writeln!(f, "  Device memory:")?;
162        writeln!(
163            f,
164            "    Loads:  {} ({} bytes)",
165            self.device_loads, self.device_load_bytes
166        )?;
167        writeln!(
168            f,
169            "    Stores: {} ({} bytes)",
170            self.device_stores, self.device_store_bytes
171        )?;
172        writeln!(f)?;
173        writeln!(f, "  Local memory:")?;
174        writeln!(
175            f,
176            "    Loads:  {} ({} bytes)",
177            self.local_loads, self.local_load_bytes
178        )?;
179        writeln!(
180            f,
181            "    Stores: {} ({} bytes)",
182            self.local_stores, self.local_store_bytes
183        )?;
184        writeln!(f)?;
185        writeln!(f, "  Barriers: {}", self.barriers)?;
186        writeln!(f, "  Divergent branches: {}", self.divergent_branches)?;
187        writeln!(f)?;
188        writeln!(f, "  Workgroups executed: {}", self.workgroups_executed)?;
189        writeln!(f, "  Waves executed: {}", self.waves_executed)?;
190        Ok(())
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn test_stats_record_instruction() {
200        let mut stats = ExecutionStats::new();
201        stats.record_instruction(InstructionCategory::Integer);
202        stats.record_instruction(InstructionCategory::Float);
203        stats.record_instruction(InstructionCategory::Float);
204
205        assert_eq!(stats.instructions_executed, 3);
206        assert_eq!(stats.integer_ops, 1);
207        assert_eq!(stats.float_ops, 2);
208    }
209
210    #[test]
211    fn test_stats_merge() {
212        let mut stats1 = ExecutionStats::new();
213        stats1.instructions_executed = 100;
214        stats1.device_loads = 50;
215
216        let mut stats2 = ExecutionStats::new();
217        stats2.instructions_executed = 200;
218        stats2.device_loads = 30;
219
220        stats1.merge(&stats2);
221
222        assert_eq!(stats1.instructions_executed, 300);
223        assert_eq!(stats1.device_loads, 80);
224    }
225
226    #[test]
227    fn test_stats_display() {
228        let stats = ExecutionStats::new();
229        let output = format!("{stats}");
230        assert!(output.contains("Execution Statistics:"));
231    }
232}