Skip to main content

hara_native/vm/machine/
instrumentation.rs

1//! Low-overhead, opt-in instrumentation for the production bytecode machine.
2//!
3//! Unlike `machine::observation`, this module does not project values, locals,
4//! handlers, or source strings on every instruction. It emits copy-only scalar
5//! events through a monomorphized probe and keeps the ordinary `Machine::run`
6//! path unchanged. The boundary API executes one real dispatch operation and
7//! leaves all expensive inspection to matching shared-hub subscriptions.
8
9use 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;