Skip to main content

hara_native/vm/machine/instrumentation/
run.rs

1use super::super::{Dispatch, Machine, VmOutcome, VmSlot};
2use super::{
3    InstructionEvent, Opcode, TerminalEvent, TerminalKind, TransitionEvent, TransitionKind, VmProbe,
4};
5use crate::core::{PromiseState, Value};
6use crate::vm::error::VmError;
7use crate::vm::opcode::Instruction;
8
9impl Machine {
10    /// Runs through the same production dispatch implementation while emitting
11    /// compact scalar events. This path deliberately does not execute tracing
12    /// JIT recordings; JIT internals have a separate telemetry contract.
13    pub fn run_instrumented<P: VmProbe>(&mut self, probe: &mut P) -> VmOutcome {
14        let program = self.program.clone();
15        self.clear_instrumented_jit_state();
16        loop {
17            let Some(function) = program.functions.get(self.function) else {
18                let error = VmError::new("function index out of range", 0, None);
19                probe.on_terminal(self.terminal_event(TerminalKind::Fail));
20                return VmOutcome::Failed(error);
21            };
22            let Some(instruction) = function.code.get(self.ip) else {
23                let error = self.error(function, "instruction pointer out of range");
24                probe.on_terminal(self.terminal_event(TerminalKind::Fail));
25                return VmOutcome::Failed(error);
26            };
27            probe.on_instruction(self.instruction_event(instruction));
28            let from_function = self.function;
29            let from_ip = self.ip;
30            match self.dispatch(&program, function, instruction) {
31                Dispatch::Next(ip) => self.ip = ip,
32                Dispatch::Unwound(ip) => {
33                    self.ip = ip;
34                    probe.on_transition(self.transition_event(
35                        TransitionKind::ExceptionUnwind,
36                        from_function,
37                        from_ip,
38                    ));
39                }
40                Dispatch::Call { callee, args } => {
41                    if let Err(message) = self.enter_callable(&program, callee, args) {
42                        match self.raise(function, message) {
43                            Ok(target) => {
44                                self.ip = target;
45                                probe.on_transition(self.transition_event(
46                                    TransitionKind::ExceptionUnwind,
47                                    from_function,
48                                    from_ip,
49                                ));
50                            }
51                            Err(error) => {
52                                probe.on_terminal(self.terminal_event(TerminalKind::Fail));
53                                return VmOutcome::Failed(error);
54                            }
55                        }
56                    } else {
57                        probe.on_transition(self.transition_event(
58                            TransitionKind::CallEnter,
59                            from_function,
60                            from_ip,
61                        ));
62                    }
63                }
64                Dispatch::CallStatic {
65                    prototype,
66                    args,
67                    captures,
68                } => {
69                    self.enter_or_spawn(&program, prototype, args, captures);
70                    probe.on_transition(self.transition_event(
71                        TransitionKind::CallEnter,
72                        from_function,
73                        from_ip,
74                    ));
75                }
76                Dispatch::CallStaticDirect { prototype, argc } => {
77                    self.enter_static_direct(&program, prototype, argc);
78                    probe.on_transition(self.transition_event(
79                        TransitionKind::CallEnter,
80                        from_function,
81                        from_ip,
82                    ));
83                }
84                Dispatch::Returned(value) => {
85                    self.stack.truncate(self.frame.base());
86                    if let Some(caller) = self.calls.pop() {
87                        self.function = caller.function;
88                        let completed = std::mem::replace(&mut self.frame, caller.frame);
89                        self.free_locals.push(completed.into_locals());
90                        self.ip = caller.call_ip + 1;
91                        self.stack.push(value);
92                        probe.on_transition(self.transition_event(
93                            TransitionKind::CallReturn,
94                            from_function,
95                            from_ip,
96                        ));
97                    } else {
98                        probe.on_terminal(self.terminal_event(TerminalKind::Return));
99                        return VmOutcome::Returned(Self::into_value(program.clone(), value));
100                    }
101                }
102                Dispatch::Suspended(promise) => {
103                    probe.on_transition(self.transition_event(
104                        TransitionKind::MachineSuspend,
105                        from_function,
106                        from_ip,
107                    ));
108                    return VmOutcome::Suspended(promise);
109                }
110                Dispatch::Yielded(value) => {
111                    probe.on_transition(self.transition_event(
112                        TransitionKind::MachineSuspend,
113                        from_function,
114                        from_ip,
115                    ));
116                    return VmOutcome::Yielded(value);
117                }
118                Dispatch::Failed(error) => {
119                    probe.on_terminal(self.terminal_event(TerminalKind::Fail));
120                    return VmOutcome::Failed(error);
121                }
122            }
123        }
124    }
125
126    /// Applies one settlement to a machine stopped at `Await`, emits the
127    /// resume or unwind boundary, and continues through `run_instrumented`.
128    pub fn resume_instrumented<P: VmProbe>(
129        &mut self,
130        state: PromiseState,
131        probe: &mut P,
132    ) -> VmOutcome {
133        let from_function = self.function;
134        let from_ip = self.ip;
135        let Some(function) = self.program.functions.get(self.function).cloned() else {
136            let error = VmError::new("function index out of range", 0, None);
137            probe.on_terminal(self.terminal_event(TerminalKind::Fail));
138            return VmOutcome::Failed(error);
139        };
140        if !matches!(function.code.get(self.ip), Some(Instruction::Await)) {
141            let error = self.error(&function, "VM is not suspended at await");
142            probe.on_terminal(self.terminal_event(TerminalKind::Fail));
143            return VmOutcome::Failed(error);
144        }
145        match state {
146            PromiseState::Pending => {
147                let promise = match self.stack.last().and_then(VmSlot::runtime_value) {
148                    Some(Value::Promise(promise)) => promise,
149                    _ => {
150                        let error = self.error(&function, "await expects a promise");
151                        probe.on_terminal(self.terminal_event(TerminalKind::Fail));
152                        return VmOutcome::Failed(error);
153                    }
154                };
155                probe.on_transition(self.transition_event(
156                    TransitionKind::MachineSuspend,
157                    from_function,
158                    from_ip,
159                ));
160                return VmOutcome::Suspended(promise);
161            }
162            PromiseState::Fulfilled(value) => {
163                self.stack.pop();
164                self.stack.push(value.into());
165                self.ip += 1;
166                probe.on_transition(self.transition_event(
167                    TransitionKind::MachineResume,
168                    from_function,
169                    from_ip,
170                ));
171            }
172            PromiseState::Rejected(error) => {
173                self.stack.pop();
174                match self.raise(&function, crate::core::promise_rejection_error(error)) {
175                    Ok(target) => {
176                        self.ip = target;
177                        probe.on_transition(self.transition_event(
178                            TransitionKind::ExceptionUnwind,
179                            from_function,
180                            from_ip,
181                        ));
182                    }
183                    Err(error) => {
184                        probe.on_terminal(self.terminal_event(TerminalKind::Fail));
185                        return VmOutcome::Failed(error);
186                    }
187                }
188            }
189        }
190        self.run_instrumented(probe)
191    }
192
193    #[inline(always)]
194    fn instruction_event(&self, instruction: &Instruction) -> InstructionEvent {
195        InstructionEvent {
196            function: saturating_u16(self.function),
197            ip: saturating_u32(self.ip),
198            opcode: Opcode::from_instruction(instruction),
199            stack_depth: saturating_u32(self.stack.len()),
200            call_depth: saturating_u16(self.calls.len()),
201        }
202    }
203
204    #[inline(always)]
205    fn transition_event(
206        &self,
207        kind: TransitionKind,
208        from_function: usize,
209        from_ip: usize,
210    ) -> TransitionEvent {
211        TransitionEvent {
212            kind,
213            from_function: saturating_u16(from_function),
214            from_ip: saturating_u32(from_ip),
215            to_function: saturating_u16(self.function),
216            to_ip: saturating_u32(self.ip),
217            stack_depth: saturating_u32(self.stack.len()),
218            call_depth: saturating_u16(self.calls.len()),
219        }
220    }
221
222    #[inline(always)]
223    fn terminal_event(&self, kind: TerminalKind) -> TerminalEvent {
224        TerminalEvent {
225            kind,
226            function: saturating_u16(self.function),
227            ip: saturating_u32(self.ip),
228            stack_depth: saturating_u32(self.stack.len()),
229            call_depth: saturating_u16(self.calls.len()),
230        }
231    }
232
233    #[cfg(feature = "tracing-jit")]
234    fn clear_instrumented_jit_state(&mut self) {
235        self.jit_path.clear();
236        self.jit_loop_entries.clear();
237        self.jit_suppressed_range = None;
238    }
239
240    #[cfg(not(feature = "tracing-jit"))]
241    fn clear_instrumented_jit_state(&mut self) {}
242}
243
244#[inline(always)]
245fn saturating_u16(value: usize) -> u16 {
246    value.min(u16::MAX as usize) as u16
247}
248
249#[inline(always)]
250fn saturating_u32(value: usize) -> u32 {
251    value.min(u32::MAX as usize) as u32
252}