Skip to main content

aver/vm/
profile.rs

1use std::collections::BTreeMap;
2
3use super::opcode::opcode_name;
4use super::types::{CallFrame, CodeStore};
5
6#[derive(Debug, Clone, Default)]
7pub struct VmReturnStats {
8    pub total_entries: u64,
9    pub thin_entries: u64,
10    pub parent_thin_entries: u64,
11    pub thin_fast_returns: u64,
12    pub parent_thin_fast_returns: u64,
13    pub young_truncate_fast_returns: u64,
14    pub thin_slow_returns: u64,
15    pub parent_thin_slow_returns: u64,
16    pub regular_slow_returns: u64,
17}
18
19#[derive(Debug, Clone)]
20pub struct VmFunctionProfile {
21    pub name: String,
22    pub thin: bool,
23    pub parent_thin: bool,
24    pub entries: u64,
25    pub fast_returns: u64,
26    pub young_truncate_fast_returns: u64,
27    pub slow_returns: u64,
28}
29
30#[derive(Debug, Clone)]
31pub struct VmOpcodeProfile {
32    pub opcode: u8,
33    pub name: &'static str,
34    pub count: u64,
35}
36
37#[derive(Debug, Clone)]
38pub struct VmBuiltinProfile {
39    pub name: String,
40    pub count: u64,
41}
42
43#[derive(Debug, Clone)]
44pub struct VmProfileReport {
45    pub total_opcodes: u64,
46    pub opcodes: Vec<VmOpcodeProfile>,
47    pub functions: Vec<VmFunctionProfile>,
48    pub builtins: Vec<VmBuiltinProfile>,
49    pub returns: VmReturnStats,
50}
51
52impl VmProfileReport {
53    pub fn merge(&mut self, other: &Self) {
54        assert_eq!(
55            self.opcodes.len(),
56            other.opcodes.len(),
57            "opcode profile shapes must match"
58        );
59        assert_eq!(
60            self.functions.len(),
61            other.functions.len(),
62            "function profile shapes must match"
63        );
64
65        self.total_opcodes += other.total_opcodes;
66        for (dst, src) in self.opcodes.iter_mut().zip(other.opcodes.iter()) {
67            assert_eq!(dst.opcode, src.opcode, "opcode profile ordering must match");
68            dst.count += src.count;
69        }
70        for (dst, src) in self.functions.iter_mut().zip(other.functions.iter()) {
71            assert_eq!(dst.name, src.name, "function profile ordering must match");
72            dst.entries += src.entries;
73            dst.fast_returns += src.fast_returns;
74            dst.young_truncate_fast_returns += src.young_truncate_fast_returns;
75            dst.slow_returns += src.slow_returns;
76        }
77        for src in &other.builtins {
78            if let Some(dst) = self.builtins.iter_mut().find(|dst| dst.name == src.name) {
79                dst.count += src.count;
80            } else {
81                self.builtins.push(src.clone());
82            }
83        }
84        self.builtins
85            .sort_by(|a, b| b.count.cmp(&a.count).then_with(|| a.name.cmp(&b.name)));
86
87        self.returns.total_entries += other.returns.total_entries;
88        self.returns.thin_entries += other.returns.thin_entries;
89        self.returns.parent_thin_entries += other.returns.parent_thin_entries;
90        self.returns.thin_fast_returns += other.returns.thin_fast_returns;
91        self.returns.parent_thin_fast_returns += other.returns.parent_thin_fast_returns;
92        self.returns.young_truncate_fast_returns += other.returns.young_truncate_fast_returns;
93        self.returns.thin_slow_returns += other.returns.thin_slow_returns;
94        self.returns.parent_thin_slow_returns += other.returns.parent_thin_slow_returns;
95        self.returns.regular_slow_returns += other.returns.regular_slow_returns;
96    }
97}
98
99#[derive(Debug, Clone, Copy)]
100pub(crate) enum ReturnPathProfileKind {
101    Fast,
102    YoungTruncateFast,
103    Slow,
104}
105
106#[derive(Debug, Clone)]
107pub(crate) struct VmProfileState {
108    opcode_counts: [u64; 256],
109    prev_opcode: u8,
110    bigram_counts: BTreeMap<(u8, u8), u64>,
111    function_entries: Vec<u64>,
112    function_fast_returns: Vec<u64>,
113    function_young_truncate_fast_returns: Vec<u64>,
114    function_slow_returns: Vec<u64>,
115    builtin_calls: BTreeMap<String, u64>,
116    return_stats: VmReturnStats,
117}
118
119impl VmProfileState {
120    pub(crate) fn new(function_count: usize) -> Self {
121        Self {
122            opcode_counts: [0; 256],
123            prev_opcode: 0xFF,
124            bigram_counts: BTreeMap::new(),
125            function_entries: vec![0; function_count],
126            function_fast_returns: vec![0; function_count],
127            function_young_truncate_fast_returns: vec![0; function_count],
128            function_slow_returns: vec![0; function_count],
129            builtin_calls: BTreeMap::new(),
130            return_stats: VmReturnStats::default(),
131        }
132    }
133
134    pub(crate) fn record_opcode(&mut self, opcode: u8) {
135        self.opcode_counts[opcode as usize] += 1;
136        if self.prev_opcode != 0xFF {
137            *self
138                .bigram_counts
139                .entry((self.prev_opcode, opcode))
140                .or_insert(0) += 1;
141        }
142        self.prev_opcode = opcode;
143    }
144
145    pub(crate) fn top_bigrams(&self, n: usize) -> Vec<((u8, u8), u64)> {
146        let mut pairs: Vec<_> = self.bigram_counts.iter().map(|(&k, &v)| (k, v)).collect();
147        pairs.sort_by(|a, b| b.1.cmp(&a.1));
148        pairs.truncate(n);
149        pairs
150    }
151
152    pub(crate) fn record_function_entry(&mut self, chunk: &super::types::FnChunk, fn_id: u32) {
153        let fn_idx = fn_id as usize;
154        self.function_entries[fn_idx] += 1;
155        self.return_stats.total_entries += 1;
156        if chunk.parent_thin {
157            self.return_stats.parent_thin_entries += 1;
158        } else if chunk.thin {
159            self.return_stats.thin_entries += 1;
160        }
161    }
162
163    pub(crate) fn record_builtin_call(&mut self, name: &str) {
164        *self.builtin_calls.entry(name.to_string()).or_insert(0) += 1;
165    }
166
167    pub(crate) fn record_return_path(&mut self, frame: &CallFrame, kind: ReturnPathProfileKind) {
168        let fn_idx = frame.fn_id as usize;
169        match kind {
170            ReturnPathProfileKind::Fast => {
171                self.function_fast_returns[fn_idx] += 1;
172                if frame.parent_thin {
173                    self.return_stats.parent_thin_fast_returns += 1;
174                } else if frame.thin {
175                    self.return_stats.thin_fast_returns += 1;
176                }
177            }
178            ReturnPathProfileKind::YoungTruncateFast => {
179                self.function_young_truncate_fast_returns[fn_idx] += 1;
180                self.return_stats.young_truncate_fast_returns += 1;
181            }
182            ReturnPathProfileKind::Slow => {
183                self.function_slow_returns[fn_idx] += 1;
184                if frame.parent_thin {
185                    self.return_stats.parent_thin_slow_returns += 1;
186                } else if frame.thin {
187                    self.return_stats.thin_slow_returns += 1;
188                } else {
189                    self.return_stats.regular_slow_returns += 1;
190                }
191            }
192        }
193    }
194
195    pub(crate) fn report(&self, code: &CodeStore) -> VmProfileReport {
196        let total_opcodes = self.opcode_counts.iter().sum();
197        let mut opcodes = self
198            .opcode_counts
199            .iter()
200            .enumerate()
201            .filter_map(|(opcode, count)| {
202                (*count > 0).then_some(VmOpcodeProfile {
203                    opcode: opcode as u8,
204                    name: opcode_name(opcode as u8),
205                    count: *count,
206                })
207            })
208            .collect::<Vec<_>>();
209        opcodes.sort_by(|a, b| b.count.cmp(&a.count).then_with(|| a.opcode.cmp(&b.opcode)));
210
211        let mut functions = code
212            .functions
213            .iter()
214            .enumerate()
215            .filter_map(|(idx, chunk)| {
216                let entries = self.function_entries[idx];
217                let fast_returns = self.function_fast_returns[idx];
218                let young_truncate_fast_returns = self.function_young_truncate_fast_returns[idx];
219                let slow_returns = self.function_slow_returns[idx];
220                (entries > 0
221                    || fast_returns > 0
222                    || young_truncate_fast_returns > 0
223                    || slow_returns > 0)
224                    .then_some(VmFunctionProfile {
225                        name: chunk.name.clone(),
226                        thin: chunk.thin,
227                        parent_thin: chunk.parent_thin,
228                        entries,
229                        fast_returns,
230                        young_truncate_fast_returns,
231                        slow_returns,
232                    })
233            })
234            .collect::<Vec<_>>();
235        functions.sort_by(|a, b| b.entries.cmp(&a.entries).then_with(|| a.name.cmp(&b.name)));
236
237        let mut builtins = self
238            .builtin_calls
239            .iter()
240            .map(|(name, count)| VmBuiltinProfile {
241                name: name.clone(),
242                count: *count,
243            })
244            .collect::<Vec<_>>();
245        builtins.sort_by(|a, b| b.count.cmp(&a.count).then_with(|| a.name.cmp(&b.name)));
246
247        VmProfileReport {
248            total_opcodes,
249            opcodes,
250            functions,
251            builtins,
252            returns: self.return_stats.clone(),
253        }
254    }
255}