hara_native/instrumentation/
interpreter.rs1use std::collections::{BTreeSet, HashMap};
2
3use crate::core::{EvalFiber, EvalFiberState, PromiseState, Value};
4
5use super::{
6 Capability, ControlLease, DispatchReport, EventAccess, EventKind, EventLocation,
7 InstrumentDirective, InstrumentationError, InstrumentationHub, PortableProjection,
8 ProducerEvent, ProjectionLimits, TargetHandle, TargetKind,
9};
10
11const SEMANTIC_EVENTS: [EventKind; 6] = [
12 EventKind::SemanticBoundary,
13 EventKind::CallEnter,
14 EventKind::CallReturn,
15 EventKind::ExceptionRaise,
16 EventKind::VarSet,
17 EventKind::FieldSet,
18];
19
20pub fn interpreter_capabilities() -> BTreeSet<Capability> {
22 BTreeSet::from([
23 Capability::EventSemanticBoundary,
24 Capability::EventCall,
25 Capability::EventException,
26 Capability::EventEffect,
27 Capability::EventSuspension,
28 Capability::EventLifecycle,
29 Capability::InspectSourceLocation,
30 Capability::InspectCurrentFrame,
31 Capability::InspectFrames,
32 Capability::InspectLocals,
33 Capability::InspectValuePreview,
34 Capability::InspectSnapshot,
35 Capability::ControlPause,
36 Capability::ControlSingleStep,
37 Capability::ControlResume,
38 Capability::ControlSettle,
39 Capability::ControlTerminate,
40 ])
41}
42
43#[derive(Debug, Clone, PartialEq)]
44pub struct InterpreterBoundary {
45 pub state: EvalFiberState,
46 pub paused: bool,
47 pub reports: Vec<DispatchReport>,
48}
49
50pub struct InterpreterTarget {
53 target: TargetHandle,
54 source_id: String,
55 fiber: EvalFiber,
56 semantic_sequence: usize,
57 paused: bool,
58 terminal_emitted: bool,
59}
60
61impl InterpreterTarget {
62 pub fn start(
63 hub: &InstrumentationHub,
64 target: TargetHandle,
65 source_id: impl Into<String>,
66 source: &str,
67 environment: HashMap<String, Value>,
68 ) -> Result<Self, InstrumentationError> {
69 let descriptor = hub.target_descriptor(&target)?;
70 if descriptor.kind != TargetKind::Interpreter {
71 return Err(InstrumentationError::InvalidTarget(
72 "interpreter probe requires an interpreter target".into(),
73 ));
74 }
75 let source_id = source_id.into();
76 if source_id.trim().is_empty() {
77 return Err(InstrumentationError::InvalidTarget(
78 "interpreter source id must be non-empty".into(),
79 ));
80 }
81 let fiber = EvalFiber::start_observed(source, environment)
82 .map_err(InstrumentationError::Execution)?;
83 fiber.configure_instrumentation_capture(false, false);
84 Ok(Self {
85 target,
86 source_id,
87 fiber,
88 semantic_sequence: 0,
89 paused: false,
90 terminal_emitted: false,
91 })
92 }
93
94 pub fn target(&self) -> &TargetHandle {
95 &self.target
96 }
97
98 pub fn state(&self) -> EvalFiberState {
99 self.fiber.state()
100 }
101
102 pub fn pending(&self) -> Option<crate::core::Promise> {
103 self.fiber.pending()
104 }
105
106 pub fn environment(&self) -> HashMap<String, Value> {
107 self.fiber.environment()
108 }
109
110 pub fn environment_clone_count(&self) -> u64 {
111 self.fiber.instrumentation_environment_clone_count()
112 }
113
114 pub fn paused(&self) -> bool {
115 self.paused
116 }
117
118 pub fn step(
120 &mut self,
121 hub: &mut InstrumentationHub,
122 ) -> Result<InterpreterBoundary, InstrumentationError> {
123 let directive = hub.take_directive(&self.target)?;
124 if self.paused && directive == InstrumentDirective::Continue {
125 return Ok(self.boundary(Vec::new()));
126 }
127 let pause_after = directive == InstrumentDirective::StepNext;
128 match directive {
129 InstrumentDirective::Suspend => {
130 self.paused = true;
131 return Ok(self.boundary(Vec::new()));
132 }
133 InstrumentDirective::Terminate => {
134 self.fiber.cancel();
135 self.paused = true;
136 let mut reports = Vec::new();
137 self.emit_terminal(hub, &mut reports)?;
138 return Ok(self.boundary(reports));
139 }
140 InstrumentDirective::Continue | InstrumentDirective::StepNext => {
141 self.paused = false;
142 }
143 }
144
145 self.refresh_capture(hub)?;
146 let before = self.fiber.state();
147 self.fiber.step_observed();
148 let after = self.fiber.state();
149 let mut reports = Vec::new();
150 self.emit_current_semantic(hub, &mut reports)?;
151 if !matches!(before, EvalFiberState::Suspended)
152 && matches!(after, EvalFiberState::Suspended)
153 {
154 self.emit(
155 hub,
156 ProducerEvent::live(EventKind::PromiseSuspend),
157 &mut reports,
158 )?;
159 }
160 self.emit_terminal(hub, &mut reports)?;
161 if pause_after {
162 self.paused = true;
163 }
164 Ok(self.boundary(reports))
165 }
166
167 pub fn run(
171 &mut self,
172 hub: &mut InstrumentationHub,
173 boundary_limit: usize,
174 ) -> Result<Vec<InterpreterBoundary>, InstrumentationError> {
175 let mut boundaries = Vec::new();
176 for _ in 0..boundary_limit {
177 if self.paused || self.finished() {
178 break;
179 }
180 let boundary = self.step(hub)?;
181 let suspended = matches!(boundary.state, EvalFiberState::Suspended);
182 boundaries.push(boundary);
183 if self.paused || suspended || self.finished() {
184 break;
185 }
186 }
187 Ok(boundaries)
188 }
189
190 pub fn continue_execution(
191 &mut self,
192 hub: &InstrumentationHub,
193 lease: &ControlLease,
194 ) -> Result<(), InstrumentationError> {
195 hub.authorize_control(lease, Capability::ControlResume)?;
196 if lease.target() != &self.target {
197 return Err(InstrumentationError::InvalidControlLease {
198 target_id: self.target.target_id().into(),
199 instrument_id: lease.instrument().instrument_id().into(),
200 });
201 }
202 self.paused = false;
203 Ok(())
204 }
205
206 pub fn settle(
208 &mut self,
209 hub: &mut InstrumentationHub,
210 lease: &ControlLease,
211 state: PromiseState,
212 ) -> Result<InterpreterBoundary, InstrumentationError> {
213 hub.authorize_control(lease, Capability::ControlSettle)?;
214 if lease.target() != &self.target {
215 return Err(InstrumentationError::InvalidControlLease {
216 target_id: self.target.target_id().into(),
217 instrument_id: lease.instrument().instrument_id().into(),
218 });
219 }
220 if !matches!(self.fiber.state(), EvalFiberState::Suspended) {
221 return Err(InstrumentationError::Execution(
222 "interpreter target is not suspended".into(),
223 ));
224 }
225 if matches!(state, PromiseState::Pending) {
226 return Err(InstrumentationError::Execution(
227 "interpreter settlement cannot remain pending".into(),
228 ));
229 }
230 self.refresh_capture(hub)?;
231 self.paused = false;
232 self.fiber.resume_observed(state);
233 let mut reports = Vec::new();
234 self.emit(
235 hub,
236 ProducerEvent::live(EventKind::PromiseResume),
237 &mut reports,
238 )?;
239 self.emit_current_semantic(hub, &mut reports)?;
240 self.emit_terminal(hub, &mut reports)?;
241 Ok(self.boundary(reports))
242 }
243
244 fn refresh_capture(&self, hub: &InstrumentationHub) -> Result<(), InstrumentationError> {
245 let mut capture_events = false;
246 let mut capture_environment = false;
247 for event in SEMANTIC_EVENTS {
248 if hub.enabled_for_target(&self.target, event)? {
249 capture_events = true;
250 capture_environment |= hub
251 .requested_projection(&self.target, event)?
252 .needs_interpreter_environment();
253 }
254 }
255 self.fiber
256 .configure_instrumentation_capture(capture_events, capture_environment);
257 Ok(())
258 }
259
260 fn emit_current_semantic(
261 &mut self,
262 hub: &mut InstrumentationHub,
263 reports: &mut Vec<DispatchReport>,
264 ) -> Result<(), InstrumentationError> {
265 let Some((sequence, event)) = self.fiber.instrumentation_event() else {
266 return Ok(());
267 };
268 if sequence <= self.semantic_sequence {
269 return Ok(());
270 }
271 self.semantic_sequence = sequence;
272 self.emit(hub, event, reports)
273 }
274
275 fn emit_terminal(
276 &mut self,
277 hub: &mut InstrumentationHub,
278 reports: &mut Vec<DispatchReport>,
279 ) -> Result<(), InstrumentationError> {
280 if self.terminal_emitted || self.fiber.observed_pending_boundaries() > 0 {
281 return Ok(());
282 }
283 let state = self.fiber.state();
284 let status = match &state {
285 EvalFiberState::Completed(_) => "returned",
286 EvalFiberState::Failed(_) => "failed",
287 EvalFiberState::Cancelled => "cancelled",
288 EvalFiberState::Running | EvalFiberState::Suspended => return Ok(()),
289 };
290 let mut event =
291 ProducerEvent::live(EventKind::ExecutionTerminal).with_data("status", status);
292 match state {
293 EvalFiberState::Completed(value) => {
294 event = event.with_data("result/type", crate::core::portable_type_name(&value));
295 }
296 EvalFiberState::Failed(error) => {
297 event = event.with_data("error", bounded_text(&error, 1_024));
298 }
299 EvalFiberState::Cancelled | EvalFiberState::Running | EvalFiberState::Suspended => {}
300 }
301 self.terminal_emitted = true;
302 self.emit(hub, event, reports)
303 }
304
305 fn emit(
306 &self,
307 hub: &mut InstrumentationHub,
308 event: ProducerEvent,
309 reports: &mut Vec<DispatchReport>,
310 ) -> Result<(), InstrumentationError> {
311 let mut access = InterpreterAccess {
312 fiber: &self.fiber,
313 source_id: &self.source_id,
314 };
315 reports.push(hub.emit(&self.target, event, &mut access)?);
316 Ok(())
317 }
318
319 fn finished(&self) -> bool {
320 matches!(
321 self.fiber.state(),
322 EvalFiberState::Completed(_) | EvalFiberState::Failed(_) | EvalFiberState::Cancelled
323 ) && self.fiber.observed_pending_boundaries() == 0
324 }
325
326 fn boundary(&self, reports: Vec<DispatchReport>) -> InterpreterBoundary {
327 InterpreterBoundary {
328 state: self.fiber.state(),
329 paused: self.paused,
330 reports,
331 }
332 }
333}
334
335struct InterpreterAccess<'a> {
336 fiber: &'a EvalFiber,
337 source_id: &'a str,
338}
339
340impl EventAccess for InterpreterAccess<'_> {
341 fn source_location(&mut self) -> Option<EventLocation> {
342 self.fiber.instrumentation_source_location(self.source_id)
343 }
344
345 fn current_frame(&mut self, limits: ProjectionLimits) -> Option<PortableProjection> {
346 self.fiber.instrumentation_current_frame(limits)
347 }
348
349 fn frames(&mut self, limits: ProjectionLimits) -> Option<PortableProjection> {
350 self.fiber.instrumentation_frames(limits)
351 }
352
353 fn locals(&mut self, limits: ProjectionLimits) -> Option<PortableProjection> {
354 self.fiber.instrumentation_locals(limits)
355 }
356
357 fn value_preview(&mut self, limits: ProjectionLimits) -> Option<PortableProjection> {
358 self.fiber.instrumentation_value_preview(limits)
359 }
360
361 fn machine_snapshot(&mut self, limits: ProjectionLimits) -> Option<PortableProjection> {
362 self.fiber.instrumentation_snapshot(limits)
363 }
364}
365
366fn bounded_text(value: &str, limit: usize) -> String {
367 if value.chars().count() <= limit {
368 return value.into();
369 }
370 let mut output = value.chars().take(limit).collect::<String>();
371 output.push('…');
372 output
373}
374
375#[cfg(test)]
376#[path = "interpreter/tests.rs"]
377mod tests;