1use super::super::*;
11use super::semantic;
12use crate::instrumentation::{
13 EventKind, EventLocation, PortableProjection, ProducerEvent, ProjectionLimits,
14};
15use crate::kernel::{read_forms, SpannedForm};
16use std::collections::BTreeMap;
17
18impl EvalFiber {
19 pub fn start_observed(source: &str, env: HashMap<String, Value>) -> Result<Self, String> {
21 let spanned = read_forms(source).map_err(|error| error.to_string())?;
22 let forms = spanned.iter().map(|form| form.form.clone()).collect();
23 Self::start_forms_observed_internal(forms, Some(Rc::new(spanned)), env)
24 }
25
26 pub fn start_forms_observed(
28 forms: Vec<Form>,
29 env: HashMap<String, Value>,
30 ) -> Result<Self, String> {
31 Self::start_forms_observed_internal(forms, None, env)
32 }
33
34 fn start_forms_observed_internal(
35 forms: Vec<Form>,
36 source_forms: Option<Rc<Vec<SpannedForm>>>,
37 env: HashMap<String, Value>,
38 ) -> Result<Self, String> {
39 let (namespace_registry, environment) = execution_context(env);
40 let env = Rc::new(RefCell::new(environment));
41 semantic::register_context(&env, source_forms, true, true);
44 let execution_env = env.clone();
45 let forms = Rc::new(forms);
46 let resume: Resume =
47 Box::new(move |_| forms_cps(forms, 0, Value::Nil, execution_env, Box::new(Step::Done)));
48 Ok(Self {
49 env,
50 namespace_registry,
51 pending: None,
52 resume: Some(resume),
53 state: EvalFiberState::Running,
54 })
55 }
56
57 pub(crate) fn configure_instrumentation_capture(
60 &self,
61 capture_events: bool,
62 capture_environment: bool,
63 ) {
64 semantic::configure_capture(&self.env, capture_events, capture_environment);
65 }
66
67 pub(crate) fn instrumentation_environment_clone_count(&self) -> u64 {
68 semantic::environment_clone_count(&self.env)
69 }
70
71 pub(crate) fn instrumentation_event(&self) -> Option<(usize, ProducerEvent)> {
74 let boundary = semantic::current_boundary(&self.env)?;
75 let kind = match boundary.rule {
76 semantic::EvalSemanticRule::FormReturn | semantic::EvalSemanticRule::ValueReturn => {
77 EventKind::SemanticBoundary
78 }
79 semantic::EvalSemanticRule::CallEnter => EventKind::CallEnter,
80 semantic::EvalSemanticRule::CallReturn => EventKind::CallReturn,
81 semantic::EvalSemanticRule::VarDefine | semantic::EvalSemanticRule::VarSet => {
82 EventKind::VarSet
83 }
84 semantic::EvalSemanticRule::FieldSet => EventKind::FieldSet,
85 semantic::EvalSemanticRule::ErrorRaise | semantic::EvalSemanticRule::ErrorCatch => {
86 EventKind::ExceptionRaise
87 }
88 };
89 let mut event = ProducerEvent::live(kind).with_data("rule", boundary.rule.as_keyword());
90 match &boundary.payload {
91 semantic::EvalSemanticPayload::Result(value) => {
92 event = event.with_data("result/type", crate::core::portable_type_name(value));
93 if boundary.rule == semantic::EvalSemanticRule::CallReturn {
94 if let Some(function) = &boundary.function {
95 event = event.with_data("function", function);
96 }
97 }
98 }
99 semantic::EvalSemanticPayload::Call { name, arguments } => {
100 event = event
101 .with_data("function", name)
102 .with_data("arguments/count", arguments.len().to_string());
103 }
104 semantic::EvalSemanticPayload::Effect {
105 target,
106 before,
107 after,
108 } => {
109 event = event
110 .with_data("target", target)
111 .with_data("before/present", before.is_some().to_string())
112 .with_data("after/type", crate::core::portable_type_name(after));
113 }
114 semantic::EvalSemanticPayload::Error { message, caught } => {
115 event = event
116 .with_data("caught", caught.to_string())
117 .with_data("message", bounded_text(message, 1_024));
118 }
119 }
120 Some((boundary.sequence, event))
121 }
122
123 pub(crate) fn instrumentation_source_location(&self, source_id: &str) -> Option<EventLocation> {
124 let boundary = semantic::current_boundary(&self.env)?;
125 Some(EventLocation {
126 source_id: Some(source_id.into()),
127 function: boundary.function,
128 ..EventLocation::default()
129 })
130 }
131
132 pub(crate) fn instrumentation_current_frame(
133 &self,
134 limits: ProjectionLimits,
135 ) -> Option<PortableProjection> {
136 let boundary = semantic::current_boundary(&self.env)?;
137 Some(environment_projection(
138 "interpreter/current-frame",
139 &boundary.environment,
140 limits,
141 ))
142 }
143
144 pub(crate) fn instrumentation_frames(
145 &self,
146 limits: ProjectionLimits,
147 ) -> Option<PortableProjection> {
148 let boundary = semantic::current_boundary(&self.env)?;
149 let session = self.env.borrow();
150 let mut projection = PortableProjection::new("interpreter/frames")
151 .with_field("current/bindings", boundary.environment.len().to_string())
152 .with_field("session/bindings", session.len().to_string());
153 let current = environment_projection("current", &boundary.environment, limits);
154 let session = environment_projection("session", &session, limits);
155 for (name, value) in current.fields {
156 projection.fields.insert(format!("current/{name}"), value);
157 }
158 for (name, value) in session.fields {
159 projection.fields.insert(format!("session/{name}"), value);
160 }
161 Some(projection)
162 }
163
164 pub(crate) fn instrumentation_locals(
165 &self,
166 limits: ProjectionLimits,
167 ) -> Option<PortableProjection> {
168 let environment = self.env.borrow();
169 Some(environment_projection(
170 "interpreter/locals",
171 &environment,
172 limits,
173 ))
174 }
175
176 pub(crate) fn instrumentation_value_preview(
177 &self,
178 limits: ProjectionLimits,
179 ) -> Option<PortableProjection> {
180 let boundary = semantic::current_boundary(&self.env)?;
181 let display_chars = limits.max_bytes.min(16_384);
182 let projection = match &boundary.payload {
183 semantic::EvalSemanticPayload::Result(value) => {
184 let kind = if boundary.rule == semantic::EvalSemanticRule::CallReturn {
185 "interpreter/call-return-preview"
186 } else {
187 "interpreter/value-preview"
188 };
189 let mut projection = PortableProjection::new(kind)
190 .with_field("kind", crate::core::portable_type_name(value))
191 .with_field("display", bounded_text(&value.display(), display_chars));
192 if let Some(function) = &boundary.function {
193 projection
194 .fields
195 .insert("function".into(), function.clone());
196 }
197 projection
198 }
199 semantic::EvalSemanticPayload::Call { name, arguments } => {
200 let mut projection = PortableProjection::new("interpreter/call-preview")
201 .with_field("function", name)
202 .with_field("arguments/count", arguments.len().to_string());
203 for (index, argument) in arguments.iter().take(limits.max_items).enumerate() {
204 projection.fields.insert(
205 format!("argument/{index}"),
206 bounded_text(&argument.display(), display_chars),
207 );
208 }
209 projection
210 }
211 semantic::EvalSemanticPayload::Effect {
212 target,
213 before,
214 after,
215 } => {
216 let mut projection = PortableProjection::new("interpreter/effect-preview")
217 .with_field("target", target)
218 .with_field("after", bounded_text(&after.display(), display_chars));
219 if let Some(before) = before {
220 projection.fields.insert(
221 "before".into(),
222 bounded_text(&before.display(), display_chars),
223 );
224 }
225 projection
226 }
227 semantic::EvalSemanticPayload::Error { message, caught } => {
228 PortableProjection::new("interpreter/error-preview")
229 .with_field("caught", caught.to_string())
230 .with_field("message", bounded_text(message, display_chars))
231 }
232 };
233 Some(projection)
234 }
235
236 pub(crate) fn instrumentation_snapshot(
237 &self,
238 limits: ProjectionLimits,
239 ) -> Option<PortableProjection> {
240 let mut projection = PortableProjection::new("interpreter/snapshot")
241 .with_field("state", instrumentation_state_keyword(&self.state))
242 .with_field(
243 "semantic/pending",
244 semantic::pending_count(&self.env).to_string(),
245 )
246 .with_field(
247 "environment/clones",
248 self.instrumentation_environment_clone_count().to_string(),
249 );
250 if let Some(promise) = &self.pending {
251 projection.fields.insert(
252 "promise/state".into(),
253 promise_state_keyword(&promise.state()).into(),
254 );
255 }
256 if let Some(locals) = self.instrumentation_locals(limits) {
257 for (name, value) in locals.fields {
258 projection.fields.insert(format!("locals/{name}"), value);
259 }
260 }
261 Some(projection)
262 }
263
264 pub fn observed_paused(&self) -> bool {
266 matches!(self.state, EvalFiberState::Running)
267 && self.pending.is_none()
268 && self.resume.is_some()
269 }
270
271 pub fn observed_pending_boundaries(&self) -> usize {
273 semantic::pending_count(&self.env)
274 }
275
276 pub fn step_observed(&mut self) -> EvalFiberState {
278 if semantic::advance_pending(&self.env) {
279 return self.state();
280 }
281 if !matches!(self.state, EvalFiberState::Running) {
282 return self.state();
283 }
284 let Some(resume) = self.resume.take() else {
285 self.state = EvalFiberState::Failed("observed evaluator continuation missing".into());
286 return self.state();
287 };
288 let step = with_namespace_registry(&self.namespace_registry, || {
289 semantic::with_active_context(&self.env, || resume(PromiseState::Pending))
290 });
291 self.accept_observed(step);
292 semantic::advance_pending(&self.env);
293 self.state()
294 }
295
296 pub fn run_observed(&mut self, boundary_limit: usize) -> EvalFiberState {
298 for _ in 0..boundary_limit {
299 if !matches!(self.state, EvalFiberState::Running)
300 && semantic::pending_count(&self.env) == 0
301 {
302 break;
303 }
304 self.step_observed();
305 }
306 self.state()
307 }
308
309 pub fn resume_observed(&mut self, state: PromiseState) -> EvalFiberState {
311 if !matches!(self.state, EvalFiberState::Suspended) {
312 return self.state();
313 }
314 let Some(resume) = self.resume.take() else {
315 self.state = EvalFiberState::Failed("fiber continuation missing".into());
316 return self.state();
317 };
318 self.pending = None;
319 self.state = EvalFiberState::Running;
320 let step = with_namespace_registry(&self.namespace_registry, || {
321 semantic::with_active_context(&self.env, || resume(state))
322 });
323 self.accept_observed(step);
324 semantic::advance_pending(&self.env);
325 self.state()
326 }
327
328 fn accept_observed(&mut self, step: Step) {
329 match step {
330 Step::Continue(next) => {
331 self.resume = Some(Box::new(move |_| next()));
332 self.pending = None;
333 self.state = EvalFiberState::Running;
334 }
335 Step::Done(Ok(value)) => {
336 self.resume = None;
337 self.pending = None;
338 self.state = EvalFiberState::Completed(value);
339 }
340 Step::Done(Err(error)) => {
341 self.resume = None;
342 self.pending = None;
343 self.state = EvalFiberState::Failed(error);
344 }
345 Step::Wait(promise, resume) => {
346 self.pending = Some(promise);
347 self.resume = Some(resume);
348 self.state = EvalFiberState::Suspended;
349 }
350 Step::Yield(_, _) => {
351 self.resume = None;
352 self.pending = None;
353 self.state =
354 EvalFiberState::Failed("coroutine/yield used outside of a coroutine".into());
355 }
356 }
357 }
358}
359
360impl Drop for EvalFiber {
361 fn drop(&mut self) {
362 semantic::remove_context(&self.env);
363 }
364}
365
366fn environment_projection(
367 kind: &str,
368 environment: &HashMap<String, Value>,
369 limits: ProjectionLimits,
370) -> PortableProjection {
371 let mut entries = environment.iter().collect::<Vec<_>>();
372 entries.sort_by(|left, right| left.0.cmp(right.0));
373 let retained = entries.len().min(limits.max_items);
374 let display_chars = limits.max_bytes.min(16_384);
375 let mut fields = BTreeMap::new();
376 for (name, value) in entries.into_iter().take(retained) {
377 fields.insert(
378 format!("binding/{name}"),
379 bounded_text(&value.display(), display_chars),
380 );
381 }
382 fields.insert("bindings/count".into(), environment.len().to_string());
383 fields.insert(
384 "bindings/omitted".into(),
385 environment.len().saturating_sub(retained).to_string(),
386 );
387 PortableProjection {
388 kind: kind.into(),
389 fields,
390 }
391}
392
393fn bounded_text(value: &str, limit: usize) -> String {
394 if value.chars().count() <= limit {
395 return value.into();
396 }
397 let mut output = value.chars().take(limit).collect::<String>();
398 output.push('…');
399 output
400}
401
402fn instrumentation_state_keyword(state: &EvalFiberState) -> &'static str {
403 match state {
404 EvalFiberState::Running => "running",
405 EvalFiberState::Suspended => "suspended",
406 EvalFiberState::Completed(_) => "returned",
407 EvalFiberState::Failed(_) => "failed",
408 EvalFiberState::Cancelled => "cancelled",
409 }
410}
411
412fn promise_state_keyword(state: &PromiseState) -> &'static str {
413 match state {
414 PromiseState::Pending => "pending",
415 PromiseState::Fulfilled(_) => "fulfilled",
416 PromiseState::Rejected(_) => "rejected",
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn live_fiber_starts_paused_and_executes_one_trampoline_at_a_time() {
426 let mut fiber =
427 EvalFiber::start_observed("(do 1 2 (+ 1 (* 2 3)))", HashMap::new()).unwrap();
428 assert_eq!(fiber.state(), EvalFiberState::Running);
429 assert!(fiber.observed_paused());
430
431 let first = fiber.run_observed(1);
432 assert_eq!(first, EvalFiberState::Running);
433 assert!(fiber.observed_paused());
434
435 let mut boundaries = 1;
436 while matches!(fiber.state(), EvalFiberState::Running) {
437 fiber.step_observed();
438 boundaries += 1;
439 assert!(boundaries < 64, "observed evaluation did not terminate");
440 }
441 assert!(boundaries > 2);
442 assert_eq!(fiber.state(), EvalFiberState::Completed(Value::Number(7)));
443 }
444
445 #[test]
446 fn promise_suspension_retains_the_real_promise_and_resume_continuation() {
447 let promise = Promise::new();
448 let mut env = HashMap::new();
449 env.insert("pending-value".into(), Value::Promise(promise.clone()));
450 let mut fiber = EvalFiber::start_observed("(Coroutine/await pending-value)", env).unwrap();
451
452 fiber.run_observed(16);
453 assert_eq!(fiber.state(), EvalFiberState::Suspended);
454 let retained = fiber.pending().expect("retained promise");
455 assert!(retained.same_identity(&promise));
456
457 assert!(promise.resolve(Value::Number(42)));
458 let resumed = fiber.resume_observed(promise.state());
459 assert_eq!(resumed, EvalFiberState::Running);
460 assert!(fiber.observed_paused());
461
462 let completed = fiber.run_observed(16);
463 assert_eq!(completed, EvalFiberState::Completed(Value::Number(42)));
464 }
465
466 #[test]
467 fn cancellation_discards_a_paused_live_continuation() {
468 let mut fiber = EvalFiber::start_observed("(do 1 2 3)", HashMap::new()).unwrap();
469 assert!(fiber.cancel());
470 assert_eq!(fiber.state(), EvalFiberState::Cancelled);
471 assert!(!fiber.observed_paused());
472 assert_eq!(fiber.step_observed(), EvalFiberState::Cancelled);
473 assert!(!fiber.cancel());
474 }
475
476 #[test]
477 fn ordinary_eval_fiber_remains_full_speed() {
478 let fiber = EvalFiber::start("(+ 19 23)", HashMap::new()).unwrap();
479 assert_eq!(fiber.state(), EvalFiberState::Completed(Value::Number(42)));
480 }
481
482 #[test]
483 fn disabled_instrumentation_capture_avoids_environment_clones() {
484 let mut fiber = EvalFiber::start_observed("(+ 19 23)", HashMap::new()).unwrap();
485 fiber.configure_instrumentation_capture(false, false);
486 fiber.run_observed(32);
487 assert_eq!(fiber.instrumentation_environment_clone_count(), 0);
488 assert_eq!(fiber.state(), EvalFiberState::Completed(Value::Number(42)));
489 }
490}