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}