1use crate::vm::opcode::Instruction;
10
11#[path = "instrumentation/ring.rs"]
12mod ring;
13#[path = "instrumentation/run.rs"]
14mod run;
15#[path = "instrumentation/step.rs"]
16mod step;
17
18pub use ring::{EventRing, SampledProbe, VmEvent};
19pub use step::{VmBoundary, VmBoundaryOutcome};
20
21pub const BYTECODE_METRICS_SCHEMA: &str = "hal.bytecode-metrics/0-alpha";
22pub const BYTECODE_EVENTS_SCHEMA: &str = "hal.bytecode-events/0-alpha";
23
24#[repr(u8)]
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub enum Opcode {
27 Constant,
28 Nil,
29 True,
30 False,
31 LoadLocal,
32 StoreLocal,
33 Pop,
34 Dup,
35 IntrinsicCall,
36 Jump,
37 JumpIfFalse,
38 Closure,
39 Call,
40 CallStatic,
41 Throw,
42 Rethrow,
43 GetGlobal,
44 DefGlobal,
45 SetGlobal,
46 VarGlobal,
47 DeclareGlobal,
48 MutableFieldGet,
49 MutableFieldSet,
50 InstanceOf,
51 MakeMultiArity,
52 BuildVector,
53 BuildMap,
54 BuildSet,
55 BuildList,
56 ConcatList,
57 ToVector,
58 DefMacro,
59 IntrinsicValue,
60 BuiltinValue,
61 NamespaceValue,
62 NamespaceOperation,
63 DynamicBind,
64 DynamicUnbind,
65 Await,
66 HostCall,
67 DotCall,
68 ProtocolCall,
69 Yield,
70 Return,
71}
72
73impl Opcode {
74 pub const COUNT: usize = 44;
75 pub const ALL: [Self; Self::COUNT] = [
76 Self::Constant,
77 Self::Nil,
78 Self::True,
79 Self::False,
80 Self::LoadLocal,
81 Self::StoreLocal,
82 Self::Pop,
83 Self::Dup,
84 Self::IntrinsicCall,
85 Self::Jump,
86 Self::JumpIfFalse,
87 Self::Closure,
88 Self::Call,
89 Self::CallStatic,
90 Self::Throw,
91 Self::Rethrow,
92 Self::GetGlobal,
93 Self::DefGlobal,
94 Self::SetGlobal,
95 Self::VarGlobal,
96 Self::DeclareGlobal,
97 Self::MutableFieldGet,
98 Self::MutableFieldSet,
99 Self::InstanceOf,
100 Self::MakeMultiArity,
101 Self::BuildVector,
102 Self::BuildMap,
103 Self::BuildSet,
104 Self::BuildList,
105 Self::ConcatList,
106 Self::ToVector,
107 Self::DefMacro,
108 Self::IntrinsicValue,
109 Self::BuiltinValue,
110 Self::NamespaceValue,
111 Self::NamespaceOperation,
112 Self::DynamicBind,
113 Self::DynamicUnbind,
114 Self::Await,
115 Self::HostCall,
116 Self::DotCall,
117 Self::ProtocolCall,
118 Self::Yield,
119 Self::Return,
120 ];
121
122 pub const fn index(self) -> usize {
123 self as usize
124 }
125
126 pub const fn as_keyword(self) -> &'static str {
127 match self {
128 Self::Constant => "constant",
129 Self::Nil => "nil",
130 Self::True => "true",
131 Self::False => "false",
132 Self::LoadLocal => "load-local",
133 Self::StoreLocal => "store-local",
134 Self::Pop => "pop",
135 Self::Dup => "dup",
136 Self::IntrinsicCall => "intrinsic-call",
137 Self::Jump => "jump",
138 Self::JumpIfFalse => "jump-if-false",
139 Self::Closure => "closure",
140 Self::Call => "call",
141 Self::CallStatic => "call-static",
142 Self::Throw => "throw",
143 Self::Rethrow => "rethrow",
144 Self::GetGlobal => "get-global",
145 Self::DefGlobal => "def-global",
146 Self::SetGlobal => "set-global",
147 Self::VarGlobal => "var-global",
148 Self::DeclareGlobal => "declare-global",
149 Self::MutableFieldGet => "mutable-field-get",
150 Self::MutableFieldSet => "mutable-field-set",
151 Self::InstanceOf => "instance-of",
152 Self::MakeMultiArity => "make-multi-arity",
153 Self::BuildVector => "build-vector",
154 Self::BuildMap => "build-map",
155 Self::BuildSet => "build-set",
156 Self::BuildList => "build-list",
157 Self::ConcatList => "concat-list",
158 Self::ToVector => "to-vector",
159 Self::DefMacro => "def-macro",
160 Self::IntrinsicValue => "intrinsic-value",
161 Self::BuiltinValue => "builtin-value",
162 Self::NamespaceValue => "namespace-value",
163 Self::NamespaceOperation => "namespace-operation",
164 Self::DynamicBind => "dynamic-bind",
165 Self::DynamicUnbind => "dynamic-unbind",
166 Self::Await => "await",
167 Self::HostCall => "host-call",
168 Self::DotCall => "dot-call",
169 Self::ProtocolCall => "protocol-call",
170 Self::Yield => "yield",
171 Self::Return => "return",
172 }
173 }
174
175 pub(super) fn from_instruction(instruction: &Instruction) -> Self {
176 match instruction {
177 Instruction::Constant(_) => Self::Constant,
178 Instruction::Nil => Self::Nil,
179 Instruction::True => Self::True,
180 Instruction::False => Self::False,
181 Instruction::LoadLocal(_) => Self::LoadLocal,
182 Instruction::StoreLocal(_) => Self::StoreLocal,
183 Instruction::Pop => Self::Pop,
184 Instruction::Dup => Self::Dup,
185 Instruction::IntrinsicCall { .. } => Self::IntrinsicCall,
186 Instruction::Jump(_) => Self::Jump,
187 Instruction::JumpIfFalse(_) => Self::JumpIfFalse,
188 Instruction::Closure { .. } => Self::Closure,
189 Instruction::Call { .. } => Self::Call,
190 Instruction::CallStatic { .. } => Self::CallStatic,
191 Instruction::Throw => Self::Throw,
192 Instruction::Rethrow => Self::Rethrow,
193 Instruction::GetGlobal(_) => Self::GetGlobal,
194 Instruction::DefGlobal { .. } => Self::DefGlobal,
195 Instruction::SetGlobal(_) => Self::SetGlobal,
196 Instruction::VarGlobal(_) => Self::VarGlobal,
197 Instruction::DeclareGlobal(_) => Self::DeclareGlobal,
198 Instruction::MutableFieldGet(_) => Self::MutableFieldGet,
199 Instruction::MutableFieldSet(_) => Self::MutableFieldSet,
200 Instruction::InstanceOf => Self::InstanceOf,
201 Instruction::MakeMultiArity { .. } => Self::MakeMultiArity,
202 Instruction::BuildVector(_) => Self::BuildVector,
203 Instruction::BuildMap(_) => Self::BuildMap,
204 Instruction::BuildSet(_) => Self::BuildSet,
205 Instruction::BuildList(_) => Self::BuildList,
206 Instruction::ConcatList(_) => Self::ConcatList,
207 Instruction::ToVector => Self::ToVector,
208 Instruction::DefMacro { .. } => Self::DefMacro,
209 Instruction::IntrinsicValue(_) => Self::IntrinsicValue,
210 Instruction::BuiltinValue(_) => Self::BuiltinValue,
211 Instruction::NamespaceValue(_) => Self::NamespaceValue,
212 Instruction::NamespaceOperation(_) => Self::NamespaceOperation,
213 Instruction::DynamicBind(_) => Self::DynamicBind,
214 Instruction::DynamicUnbind(_) => Self::DynamicUnbind,
215 Instruction::Await => Self::Await,
216 Instruction::HostCall => Self::HostCall,
217 Instruction::DotCall { .. } => Self::DotCall,
218 Instruction::ProtocolCall { .. } => Self::ProtocolCall,
219 Instruction::Yield => Self::Yield,
220 Instruction::Return => Self::Return,
221 }
222 }
223}
224
225#[derive(Clone, Copy, Debug, PartialEq, Eq)]
226pub struct InstructionEvent {
227 pub function: u16,
228 pub ip: u32,
229 pub opcode: Opcode,
230 pub stack_depth: u32,
231 pub call_depth: u16,
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq)]
235pub enum TransitionKind {
236 CallEnter,
237 CallReturn,
238 ExceptionUnwind,
239 MachineSuspend,
240 MachineResume,
241}
242
243impl TransitionKind {
244 pub const fn as_keyword(self) -> &'static str {
245 match self {
246 Self::CallEnter => "call/enter",
247 Self::CallReturn => "call/return",
248 Self::ExceptionUnwind => "exception/unwind",
249 Self::MachineSuspend => "machine/suspend",
250 Self::MachineResume => "machine/resume",
251 }
252 }
253}
254
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
256pub struct TransitionEvent {
257 pub kind: TransitionKind,
258 pub from_function: u16,
259 pub from_ip: u32,
260 pub to_function: u16,
261 pub to_ip: u32,
262 pub stack_depth: u32,
263 pub call_depth: u16,
264}
265
266#[derive(Clone, Copy, Debug, PartialEq, Eq)]
267pub enum TerminalKind {
268 Return,
269 Fail,
270}
271
272impl TerminalKind {
273 pub const fn as_keyword(self) -> &'static str {
274 match self {
275 Self::Return => "machine/return",
276 Self::Fail => "machine/fail",
277 }
278 }
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Eq)]
282pub struct TerminalEvent {
283 pub kind: TerminalKind,
284 pub function: u16,
285 pub ip: u32,
286 pub stack_depth: u32,
287 pub call_depth: u16,
288}
289
290pub trait VmProbe {
291 #[inline(always)]
292 fn on_instruction(&mut self, _event: InstructionEvent) {}
293
294 #[inline(always)]
295 fn on_transition(&mut self, _event: TransitionEvent) {}
296
297 #[inline(always)]
298 fn on_terminal(&mut self, _event: TerminalEvent) {}
299}
300
301#[derive(Default)]
302pub struct NoProbe;
303
304impl VmProbe for NoProbe {}
305
306#[derive(Clone, Copy, Debug, PartialEq, Eq)]
307pub struct OpcodeCount {
308 pub opcode: &'static str,
309 pub count: u64,
310}
311
312#[derive(Clone, Debug, PartialEq, Eq)]
313pub struct BytecodeMetrics {
314 pub schema: &'static str,
315 pub instructions: u64,
316 pub opcode_counts: [u64; Opcode::COUNT],
317 pub calls: u64,
318 pub returns: u64,
319 pub unwinds: u64,
320 pub suspensions: u64,
321 pub resumptions: u64,
322 pub terminal_returns: u64,
323 pub failures: u64,
324 pub max_stack_depth: u32,
325 pub max_call_depth: u16,
326}
327
328impl Default for BytecodeMetrics {
329 fn default() -> Self {
330 Self {
331 schema: BYTECODE_METRICS_SCHEMA,
332 instructions: 0,
333 opcode_counts: [0; Opcode::COUNT],
334 calls: 0,
335 returns: 0,
336 unwinds: 0,
337 suspensions: 0,
338 resumptions: 0,
339 terminal_returns: 0,
340 failures: 0,
341 max_stack_depth: 0,
342 max_call_depth: 0,
343 }
344 }
345}
346
347impl BytecodeMetrics {
348 pub fn opcode_count(&self, opcode: Opcode) -> u64 {
349 self.opcode_counts[opcode.index()]
350 }
351
352 pub fn named_opcode_counts(&self) -> impl Iterator<Item = OpcodeCount> + '_ {
353 Opcode::ALL.into_iter().filter_map(|opcode| {
354 let count = self.opcode_count(opcode);
355 (count > 0).then_some(OpcodeCount {
356 opcode: opcode.as_keyword(),
357 count,
358 })
359 })
360 }
361}
362
363#[derive(Default)]
364pub struct CounterProbe {
365 metrics: BytecodeMetrics,
366}
367
368impl CounterProbe {
369 pub fn metrics(&self) -> &BytecodeMetrics {
370 &self.metrics
371 }
372
373 pub fn into_metrics(self) -> BytecodeMetrics {
374 self.metrics
375 }
376
377 pub fn opcode_count(&self, opcode: Opcode) -> u64 {
378 self.metrics.opcode_count(opcode)
379 }
380
381 fn observe_depths(&mut self, stack_depth: u32, call_depth: u16) {
382 self.metrics.max_stack_depth = self.metrics.max_stack_depth.max(stack_depth);
383 self.metrics.max_call_depth = self.metrics.max_call_depth.max(call_depth);
384 }
385}
386
387impl VmProbe for CounterProbe {
388 #[inline(always)]
389 fn on_instruction(&mut self, event: InstructionEvent) {
390 self.metrics.instructions = self.metrics.instructions.saturating_add(1);
391 self.metrics.opcode_counts[event.opcode.index()] =
392 self.metrics.opcode_counts[event.opcode.index()].saturating_add(1);
393 self.observe_depths(event.stack_depth, event.call_depth);
394 }
395
396 #[inline(always)]
397 fn on_transition(&mut self, event: TransitionEvent) {
398 match event.kind {
399 TransitionKind::CallEnter => self.metrics.calls = self.metrics.calls.saturating_add(1),
400 TransitionKind::CallReturn => {
401 self.metrics.returns = self.metrics.returns.saturating_add(1)
402 }
403 TransitionKind::ExceptionUnwind => {
404 self.metrics.unwinds = self.metrics.unwinds.saturating_add(1)
405 }
406 TransitionKind::MachineSuspend => {
407 self.metrics.suspensions = self.metrics.suspensions.saturating_add(1)
408 }
409 TransitionKind::MachineResume => {
410 self.metrics.resumptions = self.metrics.resumptions.saturating_add(1)
411 }
412 }
413 self.observe_depths(event.stack_depth, event.call_depth);
414 }
415
416 #[inline(always)]
417 fn on_terminal(&mut self, event: TerminalEvent) {
418 match event.kind {
419 TerminalKind::Return => {
420 self.metrics.terminal_returns = self.metrics.terminal_returns.saturating_add(1)
421 }
422 TerminalKind::Fail => self.metrics.failures = self.metrics.failures.saturating_add(1),
423 }
424 self.observe_depths(event.stack_depth, event.call_depth);
425 }
426}
427
428#[cfg(test)]
429#[path = "instrumentation/tests.rs"]
430mod tests;