hara_native/vm/machine/instrumentation/
run.rs1use 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 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 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}