1use std::cell::RefCell;
2use std::collections::{BTreeMap, HashSet};
3use std::fmt;
4use std::rc::Rc;
5
6use yulang_runtime as runtime;
7use yulang_typed_ir as typed_ir;
8
9use crate::cps_frame_trace::{
10 CpsFrameTraceEvent, CpsFrameTraceLayer, CpsFrameTraceSlot, push_cps_frame_trace_event,
11};
12use crate::cps_ir::{
13 CpsContinuation, CpsContinuationId, CpsFunction, CpsHandlerArm, CpsHandlerEnv, CpsHandlerId,
14 CpsLiteral, CpsModule, CpsStmt, CpsTerminator, CpsValueId,
15};
16
17pub type CpsEvalResult<T> = Result<T, CpsEvalError>;
18
19thread_local! {
20 static LATEST_HANDLER_ENVS: RefCell<Vec<CpsLatestHandlerEnv>> = const { RefCell::new(Vec::new()) };
21}
22
23fn trace_enabled() -> bool {
24 std::env::var_os("YULANG_CPS_TRACE_FRAMES").is_some()
25}
26
27fn trace_cps(event: &str, msg: impl std::fmt::Display) {
28 if trace_enabled() {
29 eprintln!("[cps-trace] {event}: {msg}");
30 }
31}
32
33fn summarize_cps_value(value: &CpsRuntimeValue) -> String {
36 match value {
37 CpsRuntimeValue::Plain(v) => format!("Plain({v:?})"),
38 CpsRuntimeValue::Thunk(thunk) => {
39 format!(
40 "Thunk(owner={}, entry={:?})",
41 thunk.owner_function, thunk.entry
42 )
43 }
44 CpsRuntimeValue::Closure(closure) => format!(
45 "Closure(owner={}, entry={:?}, recursive_self={:?})",
46 closure.owner_function, closure.entry, closure.recursive_self,
47 ),
48 CpsRuntimeValue::Resumption(resumption) => format!(
49 "Resumption(owner={}, target={:?}, frames={}, handlers={}, guards={})",
50 resumption.owner_function,
51 resumption.target,
52 resumption.return_frames.len(),
53 resumption.handlers.len(),
54 resumption.guard_stack.len(),
55 ),
56 CpsRuntimeValue::List(items) => {
57 let preview = items
58 .iter()
59 .take(3)
60 .map(summarize_cps_value)
61 .collect::<Vec<_>>()
62 .join(", ");
63 format!("List(len={}, [{}])", items.len(), preview)
64 }
65 CpsRuntimeValue::Tuple(items) => {
66 let preview = items
67 .iter()
68 .map(summarize_cps_value)
69 .collect::<Vec<_>>()
70 .join(", ");
71 format!("Tuple([{preview}])")
72 }
73 CpsRuntimeValue::Record(fields) => {
74 let preview = fields
75 .iter()
76 .map(|(name, value)| format!("{}: {}", name.0, summarize_cps_value(value)))
77 .collect::<Vec<_>>()
78 .join(", ");
79 format!("Record({{{preview}}})")
80 }
81 CpsRuntimeValue::Variant { tag, value } => match value {
82 Some(value) => format!("Variant({}, {})", tag.0, summarize_cps_value(value)),
83 None => format!("Variant({})", tag.0),
84 },
85 CpsRuntimeValue::ScopeReturn {
86 prompt,
87 target,
88 value,
89 } => format!(
90 "ScopeReturn(prompt={}, target={:?}, value={})",
91 prompt.0,
92 target,
93 summarize_cps_value(value),
94 ),
95 CpsRuntimeValue::RoutedJump(jump) => format!(
96 "RoutedJump(owner={}, target={:?}, value={}, threshold={})",
97 jump.owner_function,
98 jump.target,
99 summarize_cps_value(&jump.value),
100 jump.return_frame_threshold,
101 ),
102 CpsRuntimeValue::Aborted(value) => format!("Aborted({})", summarize_cps_value(value)),
103 }
104}
105
106#[derive(Debug, Clone, PartialEq)]
107pub enum CpsEvalError {
108 MissingFunction {
109 name: String,
110 },
111 MissingContinuation {
112 function: String,
113 id: CpsContinuationId,
114 },
115 MissingHandler {
116 function: String,
117 id: CpsHandlerId,
118 },
119 ContinuationArgumentMismatch {
120 function: String,
121 id: CpsContinuationId,
122 expected: usize,
123 actual: usize,
124 },
125 FunctionArgumentMismatch {
126 function: String,
127 expected: usize,
128 actual: usize,
129 },
130 MissingValue {
131 function: String,
132 id: CpsValueId,
133 },
134 ExpectedPlainValue {
135 function: String,
136 id: CpsValueId,
137 },
138 ExpectedResumption {
139 function: String,
140 id: CpsValueId,
141 },
142 UnsupportedPrimitive {
143 op: typed_ir::PrimitiveOp,
144 },
145 PrimitiveTypeMismatch {
146 op: typed_ir::PrimitiveOp,
147 value: runtime::VmValue,
148 },
149 InvalidPrimitiveArity {
150 op: typed_ir::PrimitiveOp,
151 expected: usize,
152 actual: usize,
153 },
154 ExpectedRecord {
155 function: String,
156 value: runtime::VmValue,
157 },
158 MissingRecordField {
159 function: String,
160 field: typed_ir::Name,
161 },
162 MissingGuard,
163 ExpectedGuard {
164 function: String,
165 id: CpsValueId,
166 value: runtime::VmValue,
167 },
168 EscapedScopeReturn {
173 function: String,
174 prompt: u64,
175 target: CpsContinuationId,
176 },
177}
178
179impl fmt::Display for CpsEvalError {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 match self {
182 CpsEvalError::MissingFunction { name } => {
183 write!(f, "CPS function {name} is missing")
184 }
185 CpsEvalError::MissingContinuation { function, id } => {
186 write!(f, "CPS function {function} is missing continuation {id:?}")
187 }
188 CpsEvalError::MissingHandler { function, id } => {
189 write!(f, "CPS function {function} is missing handler {id:?}")
190 }
191 CpsEvalError::ContinuationArgumentMismatch {
192 function,
193 id,
194 expected,
195 actual,
196 } => write!(
197 f,
198 "CPS continuation {function}::{id:?} expected {expected} arguments, got {actual}"
199 ),
200 CpsEvalError::FunctionArgumentMismatch {
201 function,
202 expected,
203 actual,
204 } => write!(
205 f,
206 "CPS function {function} expected {expected} arguments, got {actual}"
207 ),
208 CpsEvalError::MissingValue { function, id } => {
209 write!(f, "CPS function {function} references missing value {id:?}")
210 }
211 CpsEvalError::ExpectedPlainValue { function, id } => {
212 write!(f, "CPS function {function} expected plain value {id:?}")
213 }
214 CpsEvalError::ExpectedResumption { function, id } => {
215 write!(
216 f,
217 "CPS function {function} expected resumption value {id:?}"
218 )
219 }
220 CpsEvalError::UnsupportedPrimitive { op } => {
221 write!(f, "CPS evaluator does not support primitive {op:?} yet")
222 }
223 CpsEvalError::PrimitiveTypeMismatch { op, value } => {
224 write!(f, "CPS primitive {op:?} cannot accept value {value:?}")
225 }
226 CpsEvalError::InvalidPrimitiveArity {
227 op,
228 expected,
229 actual,
230 } => write!(
231 f,
232 "CPS primitive {op:?} expected {expected} arguments, got {actual}"
233 ),
234 CpsEvalError::ExpectedRecord { function, value } => {
235 write!(
236 f,
237 "CPS function {function} expected record value, got {value:?}"
238 )
239 }
240 CpsEvalError::MissingRecordField { function, field } => {
241 write!(
242 f,
243 "CPS function {function} selected missing record field {field:?}"
244 )
245 }
246 CpsEvalError::MissingGuard => write!(f, "CPS evaluator has no active guard id"),
247 CpsEvalError::ExpectedGuard {
248 function,
249 id,
250 value,
251 } => write!(
252 f,
253 "CPS function {function} expected guard value {id:?}, got {value:?}"
254 ),
255 CpsEvalError::EscapedScopeReturn {
256 function,
257 prompt,
258 target,
259 } => write!(
260 f,
261 "ScopeReturn (prompt {prompt}, target {target:?}) escaped \
262 from CPS function {function} without a matching handler"
263 ),
264 }
265 }
266}
267
268impl std::error::Error for CpsEvalError {}
269
270pub fn eval_cps_module(module: &CpsModule) -> CpsEvalResult<Vec<runtime::VmValue>> {
271 module
272 .roots
273 .iter()
274 .map(|root| {
275 let value = with_fresh_handler_env_overlay(|| eval_function(module, root, Vec::new()))?;
276 let value = resolve_routed_jump(module, value, &[])?;
277 if let CpsRuntimeValue::ScopeReturn { prompt, target, .. } = &value {
281 return Err(CpsEvalError::EscapedScopeReturn {
282 function: root.name.clone(),
283 prompt: prompt.0,
284 target: *target,
285 });
286 }
287 into_plain_value(root, CpsValueId(usize::MAX), unwrap_aborted(value))
288 })
289 .collect()
290}
291
292fn with_fresh_handler_env_overlay<T>(f: impl FnOnce() -> T) -> T {
293 let previous = LATEST_HANDLER_ENVS.with(|envs| envs.replace(Vec::new()));
294 let result = f();
295 LATEST_HANDLER_ENVS.with(|envs| {
296 envs.replace(previous);
297 });
298 result
299}
300
301fn unwrap_aborted(value: CpsRuntimeValue) -> CpsRuntimeValue {
302 match value {
303 CpsRuntimeValue::Aborted(inner) => unwrap_aborted(*inner),
304 other => other,
305 }
306}
307
308enum ScopeReturnAction {
312 Value(CpsRuntimeValue),
314 JumpOrExit {
323 target: CpsContinuationId,
324 value: CpsRuntimeValue,
325 return_frame_threshold: usize,
326 },
327 Propagate(CpsRuntimeValue),
329}
330
331fn handle_scope_return(
332 result: CpsRuntimeValue,
333 active_handlers: &mut Vec<CpsHandlerFrame>,
334 _return_frames: &[CpsReturnFrame],
335 current_function: &str,
336 current_eval_id: CpsEvalId,
337) -> ScopeReturnAction {
338 match result {
339 CpsRuntimeValue::ScopeReturn {
340 prompt,
341 target,
342 value,
343 } => {
344 if let Some(index) = active_handlers.iter().rposition(|frame| {
354 frame.prompt == prompt && frame.install_eval_id == current_eval_id
355 }) {
356 let frame = &active_handlers[index];
357 let frame_owner_match =
361 target == EXIT_RWH_TARGET || frame.escape_owner_function == current_function;
362 let frame_owner = frame.escape_owner_function.clone();
363 let frame_install_eval = frame.install_eval_id;
364 let truncate_at = frame.return_frame_threshold;
365 if !frame_owner_match {
366 trace_cps(
367 "ScopeReturnDispatch",
368 format!(
369 "fn={} eval={} prompt={} target={:?} matched=yes install_eval={} owner={} owner_match=no action=Propagate",
370 current_function,
371 current_eval_id.0,
372 prompt.0,
373 target,
374 frame_install_eval.0,
375 frame_owner,
376 ),
377 );
378 return ScopeReturnAction::Propagate(CpsRuntimeValue::ScopeReturn {
379 prompt,
380 target,
381 value,
382 });
383 }
384 trace_cps(
385 "ScopeReturnDispatch",
386 format!(
387 "fn={} eval={} prompt={} target={:?} matched=yes install_eval={} owner={} owner_match=yes truncate_at={} action=JumpOrExit",
388 current_function,
389 current_eval_id.0,
390 prompt.0,
391 target,
392 frame_install_eval.0,
393 frame_owner,
394 truncate_at,
395 ),
396 );
397 active_handlers.truncate(index);
398 ScopeReturnAction::JumpOrExit {
399 target,
400 value: *value,
401 return_frame_threshold: truncate_at,
402 }
403 } else {
404 trace_cps(
405 "ScopeReturnDispatch",
406 format!(
407 "fn={} eval={} prompt={} target={:?} matched=no action=Propagate",
408 current_function, current_eval_id.0, prompt.0, target,
409 ),
410 );
411 ScopeReturnAction::Propagate(CpsRuntimeValue::ScopeReturn {
412 prompt,
413 target,
414 value,
415 })
416 }
417 }
418 other => ScopeReturnAction::Value(other),
419 }
420}
421
422fn into_inherited(mut handlers: Vec<CpsHandlerFrame>) -> Vec<CpsHandlerFrame> {
426 for frame in &mut handlers {
427 frame.inherited = true;
428 }
429 handlers
430}
431
432fn cps_value_from_vm(value: runtime::VmValue) -> CpsRuntimeValue {
433 match value {
434 runtime::VmValue::Tuple(items) => {
435 CpsRuntimeValue::Tuple(items.into_iter().map(cps_value_from_vm).collect())
436 }
437 runtime::VmValue::Variant { tag, value } => CpsRuntimeValue::Variant {
438 tag,
439 value: value.map(|v| Box::new(cps_value_from_vm(*v))),
440 },
441 runtime::VmValue::List(list) => {
442 let items = list
443 .to_vec()
444 .into_iter()
445 .map(|item| cps_value_from_vm((*item).clone()))
446 .collect::<Vec<_>>();
447 CpsRuntimeValue::List(Rc::new(items))
448 }
449 other => CpsRuntimeValue::Plain(other),
450 }
451}
452
453fn cps_value_to_vm(value: CpsRuntimeValue) -> Option<runtime::VmValue> {
454 match value {
455 CpsRuntimeValue::Plain(value) => Some(value),
456 CpsRuntimeValue::Aborted(inner) => cps_value_to_vm(*inner),
457 CpsRuntimeValue::RoutedJump(_) => None,
458 CpsRuntimeValue::ScopeReturn { .. } => None,
463 CpsRuntimeValue::Tuple(items) => Some(runtime::VmValue::Tuple(
464 items
465 .into_iter()
466 .map(cps_value_to_vm)
467 .collect::<Option<Vec<_>>>()?,
468 )),
469 CpsRuntimeValue::Record(fields) => Some(runtime::VmValue::Record(
470 fields
471 .into_iter()
472 .map(|(name, value)| Some((name, cps_value_to_vm(value)?)))
473 .collect::<Option<BTreeMap<_, _>>>()?,
474 )),
475 CpsRuntimeValue::Variant { tag, value } => Some(runtime::VmValue::Variant {
476 tag,
477 value: match value {
478 Some(value) => Some(Box::new(cps_value_to_vm(*value)?)),
479 None => None,
480 },
481 }),
482 CpsRuntimeValue::List(items) => {
483 let vm_items = items
484 .iter()
485 .cloned()
486 .map(cps_value_to_vm)
487 .collect::<Option<Vec<_>>>()?;
488 let mut tree = runtime::runtime::list_tree::ListTree::empty();
489 for item in vm_items {
490 tree = runtime::runtime::list_tree::ListTree::concat(
491 tree,
492 runtime::runtime::list_tree::ListTree::singleton(Rc::new(item)),
493 );
494 }
495 Some(runtime::VmValue::List(tree))
496 }
497 CpsRuntimeValue::Resumption(_)
498 | CpsRuntimeValue::Thunk(_)
499 | CpsRuntimeValue::Closure(_) => None,
500 }
501}
502
503fn eval_function(
504 module: &CpsModule,
505 function: &CpsFunction,
506 args: Vec<runtime::VmValue>,
507) -> CpsEvalResult<CpsRuntimeValue> {
508 eval_function_with_context(
509 module,
510 function,
511 args.into_iter().map(CpsRuntimeValue::Plain).collect(),
512 Vec::new(),
513 Vec::new(),
514 Vec::new(),
515 Vec::new(),
516 0,
517 )
518}
519
520fn eval_function_with_context(
521 module: &CpsModule,
522 function: &CpsFunction,
523 args: Vec<CpsRuntimeValue>,
524 active_handlers: Vec<CpsHandlerFrame>,
525 guard_stack: Vec<CpsGuardEntry>,
526 return_frames: Vec<CpsReturnFrame>,
527 active_blocked: Vec<CpsBlockedEffect>,
528 initial_frame_count: usize,
529) -> CpsEvalResult<CpsRuntimeValue> {
530 if function.params.len() != args.len() {
531 return Err(CpsEvalError::FunctionArgumentMismatch {
532 function: function.name.clone(),
533 expected: function.params.len(),
534 actual: args.len(),
535 });
536 }
537 eval_continuations(
540 module,
541 function,
542 function.entry,
543 args,
544 Vec::new(),
545 active_handlers,
546 guard_stack,
547 return_frames,
548 active_blocked,
549 initial_frame_count,
550 )
551}
552
553fn eval_continuations(
559 module: &CpsModule,
560 function: &CpsFunction,
561 entry: CpsContinuationId,
562 initial_args: Vec<CpsRuntimeValue>,
563 initial_values: Vec<Option<CpsRuntimeValue>>,
564 active_handlers: Vec<CpsHandlerFrame>,
565 guard_stack: Vec<CpsGuardEntry>,
566 return_frames: Vec<CpsReturnFrame>,
567 active_blocked: Vec<CpsBlockedEffect>,
568 initial_frame_count: usize,
569) -> CpsEvalResult<CpsRuntimeValue> {
570 let current_eval_id = fresh_eval_id();
571 resume_continuation(
572 module,
573 function,
574 entry,
575 initial_args,
576 initial_values,
577 into_inherited(active_handlers),
578 guard_stack,
579 return_frames,
580 active_blocked,
581 initial_frame_count,
582 current_eval_id,
583 )
584}
585
586fn resume_continuation(
593 module: &CpsModule,
594 function: &CpsFunction,
595 entry: CpsContinuationId,
596 initial_args: Vec<CpsRuntimeValue>,
597 initial_values: Vec<Option<CpsRuntimeValue>>,
598 active_handlers: Vec<CpsHandlerFrame>,
599 guard_stack: Vec<CpsGuardEntry>,
600 return_frames: Vec<CpsReturnFrame>,
601 active_blocked: Vec<CpsBlockedEffect>,
602 initial_frame_count: usize,
603 current_eval_id: CpsEvalId,
604) -> CpsEvalResult<CpsRuntimeValue> {
605 let mut values = initial_values;
606 let mut current = entry;
607 let mut args = initial_args;
608 let mut guard_stack = guard_stack;
609 let mut active_handlers = active_handlers;
610 let mut return_frames = return_frames;
611 let active_blocked = active_blocked;
612 let initial_frame_count = initial_frame_count;
613 let current_eval_id = current_eval_id;
614 let mut next_guard_id = guard_stack
615 .iter()
616 .map(|entry| entry.id)
617 .max()
618 .map_or(0, |id| id + 1);
619 macro_rules! dispatch_scope_return {
621 ($cont:lifetime, $result:expr, $dest:expr) => {{
622 let result = resolve_routed_jump(module, $result, &return_frames)?;
623 if matches!(
624 result,
625 CpsRuntimeValue::Aborted(_) | CpsRuntimeValue::RoutedJump(_)
626 ) {
627 return Ok(result);
628 }
629 match handle_scope_return(
630 result,
631 &mut active_handlers,
632 &return_frames,
633 &function.name,
634 current_eval_id,
635 ) {
636 ScopeReturnAction::Value(v) => write_value(&mut values, *$dest, v),
640 ScopeReturnAction::JumpOrExit { target, value, return_frame_threshold }
641 if target == EXIT_RWH_TARGET =>
642 {
643 if return_frames.len() > return_frame_threshold {
645 return_frames.truncate(return_frame_threshold);
646 }
647 write_value(&mut values, *$dest, value);
648 }
649 ScopeReturnAction::JumpOrExit { target, value, return_frame_threshold } => {
650 if return_frames.len() > return_frame_threshold {
651 return_frames.truncate(return_frame_threshold);
652 }
653 current = target;
654 args = vec![value];
655 continue $cont;
656 }
657 ScopeReturnAction::Propagate(v) => {
658 if let Some(routed) =
665 try_route_scope_return_through_return_frames(
666 module,
667 &v,
668 &return_frames,
669 initial_frame_count,
670 )?
671 {
672 return Ok(routed);
673 }
674 return Ok(v);
675 }
676 }
677 }};
678 }
679 'cont: loop {
680 let continuation = continuation_by_id(function, current)?;
681 assign_continuation_args(&mut values, function, continuation, args)?;
682 args = Vec::new();
683
684 for stmt in &continuation.stmts {
685 match stmt {
686 CpsStmt::Literal { dest, literal } => {
687 write_value(
688 &mut values,
689 *dest,
690 CpsRuntimeValue::Plain(eval_literal(literal)),
691 );
692 }
693 CpsStmt::FreshGuard { dest, var } => {
694 let id = next_guard_id;
695 next_guard_id += 1;
696 guard_stack.push(CpsGuardEntry { var: *var, id });
697 write_value(
698 &mut values,
699 *dest,
700 CpsRuntimeValue::Plain(runtime::VmValue::EffectId(id)),
701 );
702 }
703 CpsStmt::PeekGuard { dest } => {
704 let id = guard_stack
705 .last()
706 .map(|entry| entry.id)
707 .ok_or(CpsEvalError::MissingGuard)?;
708 write_value(
709 &mut values,
710 *dest,
711 CpsRuntimeValue::Plain(runtime::VmValue::EffectId(id)),
712 );
713 }
714 CpsStmt::FindGuard { dest, guard } => {
715 let guard = read_effect_id(function, &values, *guard)?;
716 write_value(
717 &mut values,
718 *dest,
719 CpsRuntimeValue::Plain(runtime::VmValue::Bool(
720 guard_stack.iter().any(|entry| entry.id == guard),
721 )),
722 );
723 }
724 CpsStmt::MakeThunk { dest, entry } => {
725 let thunk_values = values.clone();
726 write_value(
727 &mut values,
728 *dest,
729 CpsRuntimeValue::Thunk(Rc::new(CpsThunk {
730 owner_function: function.name.clone(),
731 entry: *entry,
732 values: Rc::new(thunk_values),
733 handlers: Rc::new(active_handlers.clone()),
734 guard_stack: Rc::new(guard_stack.clone()),
735 blocked: Rc::new(Vec::new()),
736 })),
737 );
738 }
739 CpsStmt::AddThunkBoundary {
740 dest,
741 thunk,
742 guard,
743 allowed,
744 active,
745 } => {
746 let guard = read_effect_id(function, &values, *guard)?;
747 let value = add_thunk_boundary(
748 read_value(function, &values, *thunk)?,
749 guard,
750 allowed.clone(),
751 *active,
752 );
753 write_value(&mut values, *dest, value);
754 }
755 CpsStmt::MakeClosure { dest, entry } => {
756 let closure_values = values.clone();
757 write_value(
758 &mut values,
759 *dest,
760 CpsRuntimeValue::Closure(Rc::new(CpsClosure {
761 owner_function: function.name.clone(),
762 entry: *entry,
763 values: Rc::new(closure_values),
764 recursive_self: None,
765 })),
766 );
767 }
768 CpsStmt::MakeRecursiveClosure { dest, entry } => {
769 let closure_values = values.clone();
770 let closure = CpsRuntimeValue::Closure(Rc::new(CpsClosure {
771 owner_function: function.name.clone(),
772 entry: *entry,
773 values: Rc::new(closure_values),
774 recursive_self: Some(*dest),
775 }));
776 write_value(&mut values, *dest, closure);
777 }
778 CpsStmt::ForceThunk { dest, thunk } => {
779 let mut result = read_value(function, &values, *thunk)?;
789 loop {
790 match result {
791 CpsRuntimeValue::Thunk(thunk) => {
792 let handlers = if !active_handlers.is_empty() {
793 active_handlers.clone()
794 } else {
795 thunk.handlers.as_ref().clone()
796 };
797 let guards = if !guard_stack.is_empty() {
798 guard_stack.clone()
799 } else {
800 thunk.guard_stack.as_ref().clone()
801 };
802 let owner = function_by_name(module, &thunk.owner_function)?;
803 let inherited = return_frames.len();
809 result = eval_continuations(
810 module,
811 owner,
812 thunk.entry,
813 Vec::new(),
814 thunk.values.as_ref().clone(),
815 handlers,
816 guards,
817 return_frames.clone(),
818 active_blocked_for_thunk(&active_blocked, &thunk),
819 inherited,
820 )?;
821 if matches!(result, CpsRuntimeValue::ScopeReturn { .. }) {
822 break;
823 }
824 }
825 _ => break,
826 }
827 }
828 dispatch_scope_return!('cont, result, dest);
829 }
830 CpsStmt::Tuple { dest, items } => {
831 let items = items
832 .iter()
833 .map(|id| read_value(function, &values, *id))
834 .collect::<CpsEvalResult<Vec<_>>>()?;
835 write_value(&mut values, *dest, CpsRuntimeValue::Tuple(items));
836 }
837 CpsStmt::Record { dest, base, fields } => {
838 let mut record = match base {
839 Some(base) => match read_value(function, &values, *base)? {
840 CpsRuntimeValue::Record(fields) => fields,
841 CpsRuntimeValue::Plain(runtime::VmValue::Record(fields)) => fields
842 .into_iter()
843 .map(|(name, value)| (name, CpsRuntimeValue::Plain(value)))
844 .collect(),
845 value => {
846 return Err(CpsEvalError::ExpectedRecord {
847 function: function.name.clone(),
848 value: into_plain_value(function, *base, value)?,
849 });
850 }
851 },
852 None => BTreeMap::new(),
853 };
854 for field in fields {
855 record.insert(
856 field.name.clone(),
857 read_value(function, &values, field.value)?,
858 );
859 }
860 write_value(&mut values, *dest, CpsRuntimeValue::Record(record));
861 }
862 CpsStmt::RecordWithoutFields { dest, base, fields } => {
863 let mut record = match read_value(function, &values, *base)? {
864 CpsRuntimeValue::Record(fields) => fields,
865 CpsRuntimeValue::Plain(runtime::VmValue::Record(fields)) => fields
866 .into_iter()
867 .map(|(name, value)| (name, CpsRuntimeValue::Plain(value)))
868 .collect(),
869 value => {
870 return Err(CpsEvalError::ExpectedRecord {
871 function: function.name.clone(),
872 value: into_plain_value(function, *base, value)?,
873 });
874 }
875 };
876 for field in fields {
877 record.remove(field);
878 }
879 write_value(&mut values, *dest, CpsRuntimeValue::Record(record));
880 }
881 CpsStmt::Variant { dest, tag, value } => {
882 let value = value
883 .map(|id| read_value(function, &values, id))
884 .transpose()?
885 .map(Box::new);
886 write_value(
887 &mut values,
888 *dest,
889 CpsRuntimeValue::Variant {
890 tag: tag.clone(),
891 value,
892 },
893 );
894 }
895 CpsStmt::Select { dest, base, field } => {
896 let value = match read_value(function, &values, *base)? {
897 CpsRuntimeValue::Record(fields) => {
898 fields.get(field).cloned().ok_or_else(|| {
899 CpsEvalError::MissingRecordField {
900 function: function.name.clone(),
901 field: field.clone(),
902 }
903 })?
904 }
905 CpsRuntimeValue::Plain(runtime::VmValue::Record(fields)) => fields
906 .get(field)
907 .cloned()
908 .map(CpsRuntimeValue::Plain)
909 .ok_or_else(|| CpsEvalError::MissingRecordField {
910 function: function.name.clone(),
911 field: field.clone(),
912 })?,
913 value => {
914 return Err(CpsEvalError::ExpectedRecord {
915 function: function.name.clone(),
916 value: into_plain_value(function, *base, value)?,
917 });
918 }
919 };
920 write_value(&mut values, *dest, value);
921 }
922 CpsStmt::TupleGet { dest, tuple, index } => {
923 let value = match read_value(function, &values, *tuple)? {
924 CpsRuntimeValue::Tuple(items) => {
925 items.get(*index).cloned().ok_or_else(|| {
926 CpsEvalError::MissingRecordField {
927 function: function.name.clone(),
928 field: typed_ir::Name(index.to_string()),
929 }
930 })?
931 }
932 CpsRuntimeValue::Plain(runtime::VmValue::Tuple(items)) => {
933 cps_value_from_vm(items.get(*index).cloned().ok_or_else(|| {
934 CpsEvalError::MissingRecordField {
935 function: function.name.clone(),
936 field: typed_ir::Name(index.to_string()),
937 }
938 })?)
939 }
940 other => other,
941 };
942 write_value(&mut values, *dest, value);
943 }
944 CpsStmt::SelectWithDefault {
945 dest,
946 base,
947 field,
948 default,
949 } => {
950 let default = read_value(function, &values, *default)?;
951 let value = match read_value(function, &values, *base)? {
952 CpsRuntimeValue::Record(fields) => fields.get(field).cloned(),
953 CpsRuntimeValue::Plain(runtime::VmValue::Record(fields)) => {
954 fields.get(field).cloned().map(CpsRuntimeValue::Plain)
955 }
956 value => {
957 return Err(CpsEvalError::ExpectedRecord {
958 function: function.name.clone(),
959 value: into_plain_value(function, *base, value)?,
960 });
961 }
962 }
963 .unwrap_or(default);
964 write_value(&mut values, *dest, value);
965 }
966 CpsStmt::RecordHasField { dest, base, field } => {
967 let has_field = match read_value(function, &values, *base)? {
968 CpsRuntimeValue::Record(fields) => fields.contains_key(field),
969 CpsRuntimeValue::Plain(runtime::VmValue::Record(fields)) => {
970 fields.contains_key(field)
971 }
972 value => {
973 return Err(CpsEvalError::ExpectedRecord {
974 function: function.name.clone(),
975 value: into_plain_value(function, *base, value)?,
976 });
977 }
978 };
979 write_value(
980 &mut values,
981 *dest,
982 CpsRuntimeValue::Plain(runtime::VmValue::Bool(has_field)),
983 );
984 }
985 CpsStmt::VariantTagEq { dest, variant, tag } => {
986 let matches = match read_value(function, &values, *variant)? {
987 CpsRuntimeValue::Variant { tag: actual, .. } => actual == *tag,
988 CpsRuntimeValue::Plain(runtime::VmValue::Variant {
989 tag: actual, ..
990 }) => actual == *tag,
991 _ => false,
992 };
993 write_value(
994 &mut values,
995 *dest,
996 CpsRuntimeValue::Plain(runtime::VmValue::Bool(matches)),
997 );
998 }
999 CpsStmt::VariantPayload { dest, variant } => {
1000 let value = match read_value(function, &values, *variant)? {
1001 CpsRuntimeValue::Variant {
1002 value: Some(value), ..
1003 } => *value,
1004 CpsRuntimeValue::Variant { value: None, .. } => {
1005 CpsRuntimeValue::Plain(runtime::VmValue::Unit)
1006 }
1007 CpsRuntimeValue::Plain(runtime::VmValue::Variant {
1008 value: Some(value),
1009 ..
1010 }) => cps_value_from_vm(*value),
1011 CpsRuntimeValue::Plain(runtime::VmValue::Variant {
1012 value: None, ..
1013 }) => CpsRuntimeValue::Plain(runtime::VmValue::Unit),
1014 other => other,
1015 };
1016 write_value(&mut values, *dest, value);
1017 }
1018 CpsStmt::Primitive { dest, op, args } => {
1019 let arg_values = args
1020 .iter()
1021 .map(|id| read_value(function, &values, *id))
1022 .collect::<CpsEvalResult<Vec<_>>>()?;
1023 let result = eval_cps_primitive(*op, arg_values.clone())?;
1024 if matches!(
1027 op,
1028 typed_ir::PrimitiveOp::ListEmpty
1029 | typed_ir::PrimitiveOp::ListSingleton
1030 | typed_ir::PrimitiveOp::ListMerge
1031 | typed_ir::PrimitiveOp::ListLen
1032 | typed_ir::PrimitiveOp::ListIndex
1033 | typed_ir::PrimitiveOp::ListIndexRangeRaw
1034 ) {
1035 trace_cps(
1036 "PrimitiveList",
1037 format!(
1038 "fn={} cont={:?} op={:?} args=[{}] result={}",
1039 function.name,
1040 current,
1041 op,
1042 arg_values
1043 .iter()
1044 .map(summarize_cps_value)
1045 .collect::<Vec<_>>()
1046 .join(", "),
1047 summarize_cps_value(&result),
1048 ),
1049 );
1050 }
1051 write_value(&mut values, *dest, result);
1052 }
1053 CpsStmt::DirectCall {
1054 dest,
1055 target,
1056 args: arg_ids,
1057 } => {
1058 let target_function = function_by_name(module, target)?;
1059 let call_args = arg_ids
1060 .iter()
1061 .map(|id| read_value(function, &values, *id))
1062 .collect::<CpsEvalResult<Vec<_>>>()?;
1063 let inherited = return_frames.len();
1069 let result = eval_function_with_context(
1070 module,
1071 target_function,
1072 call_args,
1073 active_handlers.clone(),
1074 guard_stack.clone(),
1075 return_frames.clone(),
1076 active_blocked.clone(),
1077 inherited,
1078 )?;
1079 dispatch_scope_return!('cont, result, dest);
1080 }
1081 CpsStmt::ApplyClosure { dest, closure, arg } => {
1082 let callable = read_value(function, &values, *closure)?;
1088 let arg_preview = read_value(function, &values, *arg)
1089 .ok()
1090 .as_ref()
1091 .map(summarize_cps_value)
1092 .unwrap_or_else(|| "?".to_string());
1093 trace_cps(
1094 "ApplyClosure",
1095 format!(
1096 "fn={} cont={:?} callable={} arg={} return_frames.len={} initial={}",
1097 function.name,
1098 current,
1099 summarize_cps_value(&callable),
1100 arg_preview,
1101 return_frames.len(),
1102 initial_frame_count,
1103 ),
1104 );
1105 let result = match callable {
1106 CpsRuntimeValue::Closure(closure) => {
1107 let arg = read_value(function, &values, *arg)?;
1108 let owner = function_by_name(module, &closure.owner_function)?;
1109 let mut closure_values = closure.values.as_ref().clone();
1110 if let Some(self_id) = closure.recursive_self {
1111 write_value(
1112 &mut closure_values,
1113 self_id,
1114 CpsRuntimeValue::Closure(closure.clone()),
1115 );
1116 }
1117 let inherited = return_frames.len();
1122 eval_continuations(
1123 module,
1124 owner,
1125 closure.entry,
1126 vec![arg],
1127 closure_values,
1128 active_handlers.clone(),
1129 guard_stack.clone(),
1130 return_frames.clone(),
1131 active_blocked.clone(),
1132 inherited,
1133 )?
1134 }
1135 CpsRuntimeValue::Resumption(resumption) => {
1136 let arg = read_plain_value(function, &values, *arg)?;
1140 let owner = function_by_name(module, &resumption.owner_function)?;
1141 let anchor = resumption.handled_anchor;
1142 let resumed_handlers = merge_resumption_handlers(
1143 resumption.handlers.as_ref(),
1144 &active_handlers,
1145 anchor,
1146 );
1147 let adjusted_frames = merge_extras_into_frames(
1148 resumption.return_frames.as_ref(),
1149 &active_handlers,
1150 anchor,
1151 );
1152 trace_cps(
1153 "ResumeHandlerMerge",
1154 format!(
1155 "site=ApplyClosure(Resumption) fn={} eval={} anchor={:?} captured={} current={} merged={}",
1156 function.name,
1157 current_eval_id.0,
1158 anchor.map(|a| (a.prompt.0, a.install_eval_id.0)),
1159 summarize_handler_stack(resumption.handlers.as_ref()),
1160 summarize_handler_stack(&active_handlers),
1161 summarize_handler_stack(&resumed_handlers),
1162 ),
1163 );
1164 eval_continuations(
1167 module,
1168 owner,
1169 resumption.target,
1170 vec![CpsRuntimeValue::Plain(arg)],
1171 resumption.values.as_ref().clone(),
1172 resumed_handlers,
1173 resumption.guard_stack.as_ref().clone(),
1174 adjusted_frames,
1175 resumption.active_blocked.as_ref().clone(),
1176 0,
1177 )?
1178 }
1179 _ => {
1180 return Err(CpsEvalError::ExpectedPlainValue {
1181 function: function.name.clone(),
1182 id: *closure,
1183 });
1184 }
1185 };
1186 dispatch_scope_return!('cont, result, dest);
1187 }
1188 CpsStmt::CloneContinuation { dest, source } => {
1189 let value = read_value(function, &values, *source)?;
1190 write_value(&mut values, *dest, value);
1191 }
1192 CpsStmt::Resume {
1193 dest,
1194 resumption,
1195 arg,
1196 } => {
1197 let resumption = read_resumption(function, &values, *resumption)?;
1198 let arg = read_plain_value(function, &values, *arg)?;
1199 let owner = function_by_name(module, &resumption.owner_function)?;
1200 let anchor = resumption.handled_anchor;
1201 let resumed_handlers = merge_resumption_handlers(
1202 resumption.handlers.as_ref(),
1203 &active_handlers,
1204 anchor,
1205 );
1206 let adjusted_frames = merge_extras_into_frames(
1207 resumption.return_frames.as_ref(),
1208 &active_handlers,
1209 anchor,
1210 );
1211 trace_cps(
1212 "ResumeHandlerMerge",
1213 format!(
1214 "site=Resume fn={} eval={} anchor={:?} captured={} current={} merged={}",
1215 function.name,
1216 current_eval_id.0,
1217 anchor.map(|a| (a.prompt.0, a.install_eval_id.0)),
1218 summarize_handler_stack(resumption.handlers.as_ref()),
1219 summarize_handler_stack(&active_handlers),
1220 summarize_handler_stack(&resumed_handlers),
1221 ),
1222 );
1223 let result = eval_continuations(
1226 module,
1227 owner,
1228 resumption.target,
1229 vec![CpsRuntimeValue::Plain(arg)],
1230 resumption.values.as_ref().clone(),
1231 resumed_handlers,
1232 resumption.guard_stack.as_ref().clone(),
1233 adjusted_frames,
1234 resumption.active_blocked.as_ref().clone(),
1235 0,
1236 )?;
1237 dispatch_scope_return!('cont, result, dest);
1238 }
1239 CpsStmt::InstallHandler {
1240 handler,
1241 envs,
1242 value,
1243 escape,
1244 } => {
1245 let envs = capture_handler_envs(function, &values, envs)?;
1246 let pushed_prompt = fresh_prompt();
1247 let threshold = return_frames.len();
1248 active_handlers.push(CpsHandlerFrame {
1249 prompt: pushed_prompt,
1250 handler: *handler,
1251 guard_stack: guard_stack.clone(),
1252 envs: envs.clone(),
1253 escape: *escape,
1254 escape_owner_function: function.name.clone(),
1255 inherited: false,
1256 return_frame_threshold: threshold,
1257 install_eval_id: current_eval_id,
1258 });
1259 return_frames.push(CpsReturnFrame {
1260 prompt_exit: Some(CpsPromptExitFrame {
1261 prompt: pushed_prompt,
1262 }),
1263 owner_function: function.name.clone(),
1264 continuation: *value,
1265 values: Rc::new(values.clone()),
1266 active_handlers: active_handlers.clone(),
1267 guard_stack: guard_stack.clone(),
1268 active_blocked: active_blocked.clone(),
1269 owner_initial_frame_count: initial_frame_count,
1270 owner_eval_id: current_eval_id,
1271 });
1272 trace_cps(
1273 "InstallHandler",
1274 format!(
1275 "fn={} eval={} cont={:?} handler={:?} prompt={} value={:?} escape={:?} threshold={} handlers.now={}",
1276 function.name,
1277 current_eval_id.0,
1278 current,
1279 handler,
1280 pushed_prompt.0,
1281 value,
1282 escape,
1283 threshold,
1284 active_handlers.len(),
1285 ),
1286 );
1287 }
1288 CpsStmt::UninstallHandler { handler } => {
1289 if let Some(pos) = active_handlers
1290 .iter()
1291 .rposition(|frame| frame.handler == *handler)
1292 {
1293 active_handlers.remove(pos);
1294 }
1295 }
1296 CpsStmt::ResumeWithHandler {
1297 dest,
1298 resumption,
1299 arg,
1300 handler,
1301 envs,
1302 } => {
1303 let resumption = read_resumption(function, &values, *resumption)?;
1304 let arg = read_plain_value(function, &values, *arg)?;
1305 let updates_existing_handler_env =
1306 envs.iter().any(|env| !env.targets.is_empty());
1307 let envs = capture_handler_envs(function, &values, envs)?;
1308 let owner = function_by_name(module, &resumption.owner_function)?;
1309 let rebase_existing_handler_env = updates_existing_handler_env
1310 && resumption
1311 .handlers
1312 .iter()
1313 .any(|frame| frame.handler == *handler);
1314 overlay_handler_envs_in_stack(
1315 &function.name,
1316 &mut active_handlers,
1317 *handler,
1318 &envs,
1319 true,
1320 );
1321 let pushed_prompt = fresh_prompt();
1326 if !rebase_existing_handler_env {
1327 active_handlers.push(CpsHandlerFrame {
1328 prompt: pushed_prompt,
1329 handler: *handler,
1330 guard_stack: guard_stack.clone(),
1331 envs: envs.clone(),
1332 escape: EXIT_RWH_TARGET,
1333 escape_owner_function: function.name.clone(),
1334 inherited: false,
1335 return_frame_threshold: return_frames.len(),
1336 install_eval_id: current_eval_id,
1337 });
1338 }
1339 let inner_handlers = {
1346 let mut stack = resumption.handlers.as_ref().clone();
1347 overlay_handler_envs_in_stack(
1348 &function.name,
1349 &mut stack,
1350 *handler,
1351 &envs,
1352 false,
1353 );
1354 if !rebase_existing_handler_env {
1355 let mut owned = active_handlers
1356 .last()
1357 .cloned()
1358 .expect("just pushed RWH frame");
1359 owned.inherited = true;
1360 stack.push(owned);
1361 }
1362 stack
1363 };
1364 let pushed_extra = if rebase_existing_handler_env {
1365 Vec::new()
1366 } else {
1367 active_handlers
1368 .iter()
1369 .filter(|f| f.prompt == pushed_prompt)
1370 .cloned()
1371 .collect::<Vec<_>>()
1372 };
1373 let mut captured_frames = resumption.return_frames.as_ref().clone();
1377 overlay_handler_envs_in_frames(
1378 &function.name,
1379 &mut captured_frames,
1380 *handler,
1381 &envs,
1382 false,
1383 );
1384 let adjusted_frames =
1385 append_resume_with_handler_frames(&captured_frames, &pushed_extra);
1386 let adjusted_frames = if rebase_existing_handler_env {
1387 own_captured_return_frames(adjusted_frames)
1388 } else {
1389 adjusted_frames
1390 };
1391 trace_cps(
1392 "ResumeHandlerMerge",
1393 format!(
1394 "site=ResumeWithHandler(rebased) fn={} eval={} pushed_prompt={} captured={} pushed_extra={} inner={}",
1395 function.name,
1396 current_eval_id.0,
1397 pushed_prompt.0,
1398 summarize_handler_stack(resumption.handlers.as_ref()),
1399 summarize_handler_stack(&pushed_extra),
1400 summarize_handler_stack(&inner_handlers),
1401 ),
1402 );
1403 let result = eval_continuations(
1404 module,
1405 owner,
1406 resumption.target,
1407 vec![CpsRuntimeValue::Plain(arg)],
1408 resumption.values.as_ref().clone(),
1409 inner_handlers,
1410 resumption.guard_stack.as_ref().clone(),
1411 adjusted_frames,
1412 resumption.active_blocked.as_ref().clone(),
1413 0,
1414 )?;
1415 dispatch_scope_return!('cont, result, dest);
1416 if let Some(pos) = active_handlers
1421 .iter()
1422 .rposition(|f| f.prompt == pushed_prompt)
1423 {
1424 active_handlers.truncate(pos);
1425 }
1426 }
1427 }
1428 }
1429
1430 match &continuation.terminator {
1431 CpsTerminator::Return(value) => {
1432 let v = read_value(function, &values, *value)?;
1436 trace_cps(
1437 "Return",
1438 format!(
1439 "fn={} cont={:?} value={} return_frames.len={} initial={}",
1440 function.name,
1441 current,
1442 summarize_cps_value(&v),
1443 return_frames.len(),
1444 initial_frame_count,
1445 ),
1446 );
1447 if return_frames.len() <= initial_frame_count {
1448 return Ok(v);
1449 }
1450 if let CpsRuntimeValue::Thunk(thunk) = &v {
1472 let top_index = return_frames.len() - 1;
1473 let top_frame = &return_frames[top_index];
1474 if return_frame_immediately_forces_param(module, top_frame)?
1475 && top_index >= initial_frame_count
1476 {
1477 let top_frame = top_frame.clone();
1478 trace_cps(
1479 "PreForceResumeTopFrame",
1480 format!(
1481 "top_owner={} top_owner_eval={} top_cont={:?} retained_frames_len={}",
1482 top_frame.owner_function,
1483 top_frame.owner_eval_id.0,
1484 top_frame.continuation,
1485 return_frames.len(),
1486 ),
1487 );
1488 let forced = force_returned_thunk_before_frame_consumption(
1489 module,
1490 thunk.clone(),
1491 &top_frame,
1492 return_frames.clone(),
1493 initial_frame_count,
1494 )?;
1495 if matches!(forced, CpsRuntimeValue::ScopeReturn { .. }) {
1496 return Ok(forced);
1497 }
1498 return continue_return_frames(module, forced, &return_frames, &[]);
1499 }
1500 }
1501 return continue_return_frames(module, v, &return_frames, &[]);
1509 }
1510 CpsTerminator::Continue { target, args: next } => {
1511 args = next
1512 .iter()
1513 .map(|id| read_value(function, &values, *id))
1514 .collect::<CpsEvalResult<Vec<_>>>()?;
1515 current = *target;
1516 }
1517 CpsTerminator::Branch {
1518 cond,
1519 then_cont,
1520 else_cont,
1521 } => {
1522 let cond = read_plain_value(function, &values, *cond)?;
1523 current = if bool_value(typed_ir::PrimitiveOp::BoolNot, &cond)? {
1524 *then_cont
1525 } else {
1526 *else_cont
1527 };
1528 }
1529 CpsTerminator::Perform {
1530 effect,
1531 payload,
1532 resume,
1533 handler,
1534 blocked,
1535 } => {
1536 trace_cps(
1537 "Perform",
1538 format!(
1539 "fn={} eval={} cont={:?} effect={:?} return_frames.len={} initial={} active_handlers={}",
1540 function.name,
1541 current_eval_id.0,
1542 current,
1543 effect,
1544 return_frames.len(),
1545 initial_frame_count,
1546 summarize_handler_stack(&active_handlers),
1547 ),
1548 );
1549 let payload = read_plain_value(function, &values, *payload)?;
1550 let blocked = blocked
1551 .map(|blocked| read_effect_id(function, &values, blocked))
1552 .transpose()?
1553 .or_else(|| active_blocked_id(effect, &active_blocked));
1554 let handler_stack =
1555 handler_stack_with_static(&active_handlers, *handler, &guard_stack);
1556 let (handler_arm, frame, handler_body_stack, handler_owner) =
1557 handler_arm_for_stack(module, function, &handler_stack, effect, blocked)?;
1558 let handler_values = values_with_handler_env(
1559 &handler_owner.name,
1560 Vec::new(),
1561 frame,
1562 handler_arm.entry,
1563 );
1564 let frame_prompt = frame.prompt;
1565 let frame_escape = frame.escape;
1566 let frame_in_active = active_handlers.iter().any(|f| f.prompt == frame_prompt);
1567 let handled_anchor = if frame_in_active {
1572 Some(CpsHandlerAnchor {
1573 prompt: frame.prompt,
1574 install_eval_id: frame.install_eval_id,
1575 })
1576 } else {
1577 None
1578 };
1579 if trace_enabled() {
1580 let matched_index = handler_stack.iter().position(|f| f.prompt == frame_prompt);
1581 trace_cps(
1582 "PerformHandlerSearch",
1583 format!(
1584 "fn={} eval={} effect={:?} stack={} matched_index={:?} matched_prompt={} matched_install_eval={} matched_owner={} in_active={}",
1585 function.name,
1586 current_eval_id.0,
1587 effect,
1588 summarize_handler_stack(&handler_stack),
1589 matched_index,
1590 frame.prompt.0,
1591 frame.install_eval_id.0,
1592 if frame.escape_owner_function.is_empty() {
1593 "<synth>"
1594 } else {
1595 frame.escape_owner_function.as_str()
1596 },
1597 frame_in_active,
1598 ),
1599 );
1600 }
1601 let (resumption_handlers, resumption_return_frames) = if frame_in_active {
1602 let captured =
1603 capture_continuation_inside_prompt(&handler_stack, &return_frames, frame);
1604 (captured.handlers, captured.return_frames)
1605 } else {
1606 (handler_stack.clone(), return_frames.clone())
1607 };
1608 let resumption = CpsRuntimeValue::Resumption(Rc::new(CpsResumption {
1609 owner_function: function.name.clone(),
1610 target: *resume,
1611 values: Rc::new(values.clone()),
1612 handlers: Rc::new(resumption_handlers),
1613 guard_stack: Rc::new(guard_stack.clone()),
1614 active_blocked: Rc::new(active_blocked.clone()),
1615 return_frames: Rc::new(resumption_return_frames),
1616 handled_anchor,
1617 }));
1618 let result = eval_continuations(
1625 module,
1626 handler_owner,
1627 handler_arm.entry,
1628 vec![CpsRuntimeValue::Plain(payload), resumption],
1629 handler_values,
1630 handler_body_stack,
1631 guard_stack.clone(),
1632 Vec::new(),
1633 active_blocked.clone(),
1634 0,
1635 )?;
1636 if !frame_in_active {
1637 return Ok(result);
1641 }
1642 let arm_already_reached_escape = handler_arm_continues_to_installed_escape(
1643 handler_owner,
1644 frame.handler,
1645 handler_arm.entry,
1646 frame_escape,
1647 );
1648 if arm_already_reached_escape
1649 && !matches!(result, CpsRuntimeValue::ScopeReturn { .. })
1650 && frame.install_eval_id == current_eval_id
1651 {
1652 let mut frames = return_frames.clone();
1653 if frames.len() > frame.return_frame_threshold {
1654 frames.truncate(frame.return_frame_threshold);
1655 }
1656 return continue_return_frames(module, result, &frames, &[]);
1657 }
1658 if !matches!(result, CpsRuntimeValue::ScopeReturn { .. })
1659 && frame.install_eval_id != current_eval_id
1660 && handler_arm_uses_resume_with_handler(
1661 handler_owner,
1662 frame.handler,
1663 handler_arm.entry,
1664 )
1665 {
1666 return Ok(result);
1667 }
1668 let scope_return = match result {
1672 CpsRuntimeValue::ScopeReturn { .. } => result,
1673 other => CpsRuntimeValue::ScopeReturn {
1674 prompt: frame_prompt,
1675 target: frame_escape,
1676 value: Box::new(other),
1677 },
1678 };
1679 match handle_scope_return(
1686 scope_return,
1687 &mut active_handlers,
1688 &return_frames,
1689 &function.name,
1690 current_eval_id,
1691 ) {
1692 ScopeReturnAction::Value(v) => {
1693 return Ok(v);
1695 }
1696 ScopeReturnAction::Propagate(v) => {
1697 if let Some(routed) = try_route_scope_return_through_return_frames(
1700 module,
1701 &v,
1702 &return_frames,
1703 initial_frame_count,
1704 )? {
1705 return Ok(routed);
1706 }
1707 return Ok(v);
1708 }
1709 ScopeReturnAction::JumpOrExit {
1710 target,
1711 value,
1712 return_frame_threshold,
1713 } => {
1714 if return_frames.len() > return_frame_threshold {
1715 return_frames.truncate(return_frame_threshold);
1716 }
1717 if target == EXIT_RWH_TARGET {
1718 return Ok(value);
1719 }
1720 current = target;
1721 args = vec![value];
1722 continue 'cont;
1723 }
1724 }
1725 }
1726 CpsTerminator::EffectfulCall {
1727 target,
1728 args: arg_ids,
1729 resume,
1730 } => {
1731 let target_function = function_by_name(module, target)?;
1740 let call_args = arg_ids
1741 .iter()
1742 .map(|id| read_value(function, &values, *id))
1743 .collect::<CpsEvalResult<Vec<_>>>()?;
1744 let pre_push_count = return_frames.len();
1745 let frame = CpsReturnFrame {
1746 prompt_exit: None,
1747 owner_function: function.name.clone(),
1748 continuation: *resume,
1749 values: Rc::new(values.clone()),
1750 active_handlers: active_handlers.clone(),
1751 guard_stack: guard_stack.clone(),
1752 active_blocked: active_blocked.clone(),
1753 owner_initial_frame_count: initial_frame_count,
1754 owner_eval_id: current_eval_id,
1755 };
1756 let mut new_frames = return_frames.clone();
1757 new_frames.push(frame);
1758 return eval_function_with_context(
1759 module,
1760 target_function,
1761 call_args,
1762 active_handlers.clone(),
1763 guard_stack.clone(),
1764 new_frames,
1765 active_blocked.clone(),
1766 pre_push_count,
1767 );
1768 }
1769 CpsTerminator::EffectfulForce { thunk, resume } => {
1770 let value = read_value(function, &values, *thunk)?;
1774 match value {
1775 CpsRuntimeValue::Thunk(thunk_rc) => {
1776 let pre_push_count = return_frames.len();
1777 let frame = CpsReturnFrame {
1778 prompt_exit: None,
1779 owner_function: function.name.clone(),
1780 continuation: *resume,
1781 values: Rc::new(values.clone()),
1782 active_handlers: active_handlers.clone(),
1783 guard_stack: guard_stack.clone(),
1784 active_blocked: active_blocked.clone(),
1785 owner_initial_frame_count: initial_frame_count,
1786 owner_eval_id: current_eval_id,
1787 };
1788 let mut new_frames = return_frames.clone();
1789 new_frames.push(frame);
1790 let owner = function_by_name(module, &thunk_rc.owner_function)?;
1791 let handlers = if !active_handlers.is_empty() {
1792 active_handlers.clone()
1793 } else {
1794 thunk_rc.handlers.as_ref().clone()
1795 };
1796 let guards = if !guard_stack.is_empty() {
1797 guard_stack.clone()
1798 } else {
1799 thunk_rc.guard_stack.as_ref().clone()
1800 };
1801 return eval_continuations(
1802 module,
1803 owner,
1804 thunk_rc.entry,
1805 Vec::new(),
1806 thunk_rc.values.as_ref().clone(),
1807 handlers,
1808 guards,
1809 new_frames,
1810 active_blocked_for_thunk(&active_blocked, &thunk_rc),
1811 pre_push_count,
1812 );
1813 }
1814 other => {
1815 current = *resume;
1819 args = vec![other];
1820 continue 'cont;
1821 }
1822 }
1823 }
1824 CpsTerminator::EffectfulApply {
1825 closure,
1826 arg,
1827 resume,
1828 } => {
1829 let callable = read_value(function, &values, *closure)?;
1832 let arg_preview = read_value(function, &values, *arg)
1833 .ok()
1834 .as_ref()
1835 .map(summarize_cps_value)
1836 .unwrap_or_else(|| "?".to_string());
1837 trace_cps(
1838 "EffectfulApply",
1839 format!(
1840 "fn={} cont={:?} callable={} arg={} resume={:?} return_frames.len={} initial={}",
1841 function.name,
1842 current,
1843 summarize_cps_value(&callable),
1844 arg_preview,
1845 resume,
1846 return_frames.len(),
1847 initial_frame_count,
1848 ),
1849 );
1850 let pre_push_count = return_frames.len();
1851 let frame = CpsReturnFrame {
1852 prompt_exit: None,
1853 owner_function: function.name.clone(),
1854 continuation: *resume,
1855 values: Rc::new(values.clone()),
1856 active_handlers: active_handlers.clone(),
1857 guard_stack: guard_stack.clone(),
1858 active_blocked: active_blocked.clone(),
1859 owner_initial_frame_count: initial_frame_count,
1860 owner_eval_id: current_eval_id,
1861 };
1862 let mut new_frames = return_frames.clone();
1863 new_frames.push(frame);
1864 match callable {
1865 CpsRuntimeValue::Closure(closure) => {
1866 let arg = read_value(function, &values, *arg)?;
1867 let owner = function_by_name(module, &closure.owner_function)?;
1868 let mut closure_values = closure.values.as_ref().clone();
1869 if let Some(self_id) = closure.recursive_self {
1870 write_value(
1871 &mut closure_values,
1872 self_id,
1873 CpsRuntimeValue::Closure(closure.clone()),
1874 );
1875 }
1876 return eval_continuations(
1877 module,
1878 owner,
1879 closure.entry,
1880 vec![arg],
1881 closure_values,
1882 active_handlers.clone(),
1883 guard_stack.clone(),
1884 new_frames,
1885 active_blocked.clone(),
1886 pre_push_count,
1887 );
1888 }
1889 CpsRuntimeValue::Resumption(resumption) => {
1890 let arg = read_plain_value(function, &values, *arg)?;
1900 let owner = function_by_name(module, &resumption.owner_function)?;
1901 let anchor = resumption.handled_anchor;
1902 let resumed_handlers = merge_resumption_handlers(
1903 resumption.handlers.as_ref(),
1904 &active_handlers,
1905 anchor,
1906 );
1907 let adjusted_res = merge_extras_into_frames(
1908 resumption.return_frames.as_ref(),
1909 &active_handlers,
1910 anchor,
1911 );
1912 trace_cps(
1913 "ResumeHandlerMerge",
1914 format!(
1915 "site=EffectfulApply(Resumption) fn={} eval={} anchor={:?} captured={} current={} merged={}",
1916 function.name,
1917 current_eval_id.0,
1918 anchor.map(|a| (a.prompt.0, a.install_eval_id.0)),
1919 summarize_handler_stack(resumption.handlers.as_ref()),
1920 summarize_handler_stack(&active_handlers),
1921 summarize_handler_stack(&resumed_handlers),
1922 ),
1923 );
1924 let mut combined_frames = new_frames;
1927 combined_frames.extend(adjusted_res);
1928 return eval_continuations(
1931 module,
1932 owner,
1933 resumption.target,
1934 vec![CpsRuntimeValue::Plain(arg)],
1935 resumption.values.as_ref().clone(),
1936 resumed_handlers,
1937 resumption.guard_stack.as_ref().clone(),
1938 combined_frames,
1939 resumption.active_blocked.as_ref().clone(),
1940 0,
1941 );
1942 }
1943 _ => {
1944 return Err(CpsEvalError::ExpectedPlainValue {
1945 function: function.name.clone(),
1946 id: *closure,
1947 });
1948 }
1949 }
1950 }
1951 }
1952 }
1953}
1954
1955fn return_frame_immediately_forces_param(
1961 module: &CpsModule,
1962 frame: &CpsReturnFrame,
1963) -> CpsEvalResult<bool> {
1964 let function = function_by_name(module, &frame.owner_function)?;
1965 let Some(continuation) = function
1966 .continuations
1967 .iter()
1968 .find(|c| c.id == frame.continuation)
1969 else {
1970 return Ok(false);
1971 };
1972 let Some(&first_param) = continuation.params.first() else {
1973 return Ok(false);
1974 };
1975 Ok(matches!(
1976 continuation.stmts.first(),
1977 Some(CpsStmt::ForceThunk { thunk, .. }) if *thunk == first_param
1978 ))
1979}
1980
1981#[allow(dead_code)]
1996fn force_returned_thunk_before_frame_consumption(
1997 module: &CpsModule,
1998 mut thunk: Rc<CpsThunk>,
1999 top_frame: &CpsReturnFrame,
2000 return_frames: Vec<CpsReturnFrame>,
2001 initial_frame_count: usize,
2002) -> CpsEvalResult<CpsRuntimeValue> {
2003 loop {
2004 let owner = function_by_name(module, &thunk.owner_function)?;
2005 let result = resume_continuation(
2011 module,
2012 owner,
2013 thunk.entry,
2014 Vec::new(),
2015 thunk.values.as_ref().clone(),
2016 top_frame.active_handlers.clone(),
2017 top_frame.guard_stack.clone(),
2018 return_frames.clone(),
2019 active_blocked_for_thunk(&top_frame.active_blocked, &thunk),
2020 initial_frame_count,
2021 top_frame.owner_eval_id,
2024 )?;
2025 match result {
2026 CpsRuntimeValue::Thunk(next) => {
2027 thunk = next;
2028 continue;
2029 }
2030 other => return Ok(other),
2031 }
2032 }
2033}
2034
2035fn summarize_handler_stack(stack: &[CpsHandlerFrame]) -> String {
2038 let parts: Vec<String> = stack
2039 .iter()
2040 .map(|h| {
2041 format!(
2042 "(p={},inh={},eval={},owner={},thr={})",
2043 h.prompt.0,
2044 if h.inherited { "T" } else { "F" },
2045 h.install_eval_id.0,
2046 if h.escape_owner_function.is_empty() {
2047 "<synth>"
2048 } else {
2049 h.escape_owner_function.as_str()
2050 },
2051 h.return_frame_threshold,
2052 )
2053 })
2054 .collect();
2055 format!("[{}]", parts.join(","))
2056}
2057
2058fn same_handler_frame(a: &CpsHandlerFrame, b: &CpsHandlerFrame) -> bool {
2060 a.prompt == b.prompt && a.install_eval_id == b.install_eval_id
2061}
2062
2063fn capture_continuation_inside_prompt(
2064 handlers: &[CpsHandlerFrame],
2065 return_frames: &[CpsReturnFrame],
2066 handled: &CpsHandlerFrame,
2067) -> CapturedPromptContinuation {
2068 let start = captured_prompt_frame_start(return_frames, handled);
2069 let return_frames = return_frames[start..]
2070 .iter()
2071 .cloned()
2072 .map(|frame| rebase_captured_return_frame(frame, start, handled))
2073 .collect();
2074 CapturedPromptContinuation {
2075 handlers: handlers
2076 .iter()
2077 .cloned()
2078 .map(|handler| rebase_captured_handler_frame(handler, start, handled))
2079 .collect(),
2080 return_frames: own_captured_return_frames(return_frames),
2081 }
2082}
2083
2084fn captured_prompt_frame_start(
2085 return_frames: &[CpsReturnFrame],
2086 handled: &CpsHandlerFrame,
2087) -> usize {
2088 let start = return_frames
2089 .iter()
2090 .rposition(|frame| {
2091 frame
2092 .prompt_exit
2093 .as_ref()
2094 .is_some_and(|exit| exit.prompt == handled.prompt)
2095 })
2096 .map(|index| index + 1)
2097 .unwrap_or(0)
2102 .min(return_frames.len());
2103 start
2104}
2105
2106fn rebase_captured_return_frame(
2107 mut frame: CpsReturnFrame,
2108 dropped_frames: usize,
2109 handled: &CpsHandlerFrame,
2110) -> CpsReturnFrame {
2111 frame.owner_initial_frame_count = frame
2112 .owner_initial_frame_count
2113 .saturating_sub(dropped_frames);
2114 frame.active_handlers = frame
2115 .active_handlers
2116 .into_iter()
2117 .map(|handler| rebase_captured_handler_frame(handler, dropped_frames, handled))
2118 .collect();
2119 frame
2120}
2121
2122fn rebase_captured_handler_frame(
2123 mut handler: CpsHandlerFrame,
2124 dropped_frames: usize,
2125 handled: &CpsHandlerFrame,
2126) -> CpsHandlerFrame {
2127 if handler.install_eval_id.0 >= handled.install_eval_id.0 {
2128 handler.return_frame_threshold = handler
2129 .return_frame_threshold
2130 .saturating_sub(dropped_frames);
2131 }
2132 handler
2133}
2134
2135fn merge_resumption_handlers(
2161 captured: &[CpsHandlerFrame],
2162 current: &[CpsHandlerFrame],
2163 anchor: Option<CpsHandlerAnchor>,
2164) -> Vec<CpsHandlerFrame> {
2165 let is_anchor = |frame: &CpsHandlerFrame, anchor: CpsHandlerAnchor| {
2166 frame.prompt == anchor.prompt && frame.install_eval_id == anchor.install_eval_id
2167 };
2168
2169 if let Some(anchor) = anchor {
2170 if let Some(anchor_index) = captured.iter().position(|f| is_anchor(f, anchor)) {
2171 let mut merged = Vec::with_capacity(captured.len() + current.len());
2172
2173 merged.extend(captured[..=anchor_index].iter().cloned());
2176
2177 for frame in current {
2182 let in_prefix = merged.iter().any(|m| same_handler_frame(m, frame));
2183 let in_tail = captured[anchor_index + 1..]
2184 .iter()
2185 .any(|c| same_handler_frame(c, frame));
2186 if !in_prefix && !in_tail {
2187 merged.push(frame.clone());
2188 }
2189 }
2190
2191 merged.extend(captured[anchor_index + 1..].iter().cloned());
2193 return merged;
2194 }
2195 }
2196
2197 let mut shared = 0;
2201 while shared < captured.len()
2202 && shared < current.len()
2203 && same_handler_frame(&captured[shared], ¤t[shared])
2204 {
2205 shared += 1;
2206 }
2207
2208 let mut merged = Vec::with_capacity(captured.len() + current.len());
2209 merged.extend(captured[..shared].iter().cloned());
2210
2211 for frame in ¤t[shared..] {
2212 if !captured.iter().any(|c| same_handler_frame(c, frame)) {
2213 merged.push(frame.clone());
2214 }
2215 }
2216
2217 merged.extend(captured[shared..].iter().cloned());
2218 merged
2219}
2220
2221fn append_resume_with_handler_frames(
2227 frames: &[CpsReturnFrame],
2228 extra: &[CpsHandlerFrame],
2229) -> Vec<CpsReturnFrame> {
2230 if extra.is_empty() {
2231 return frames.to_vec();
2232 }
2233 frames
2234 .iter()
2235 .map(|frame| {
2236 let mut adjusted = frame.clone();
2237 for handler in extra {
2238 if !adjusted
2239 .active_handlers
2240 .iter()
2241 .any(|h| h.prompt == handler.prompt)
2242 {
2243 adjusted.active_handlers.push(handler.clone());
2244 }
2245 }
2246 adjusted
2247 })
2248 .collect()
2249}
2250
2251fn merge_extras_into_frames(
2257 frames: &[CpsReturnFrame],
2258 current: &[CpsHandlerFrame],
2259 anchor: Option<CpsHandlerAnchor>,
2260) -> Vec<CpsReturnFrame> {
2261 frames
2262 .iter()
2263 .map(|frame| {
2264 let merged =
2265 merge_resumption_handlers(&frame.active_handlers, current, anchor);
2266 if trace_enabled() && merged != frame.active_handlers {
2267 trace_cps(
2268 "InjectExtraHandlers",
2269 format!(
2270 "frame_owner={} frame_owner_eval={} before={} current={} anchor={:?} after={}",
2271 frame.owner_function,
2272 frame.owner_eval_id.0,
2273 summarize_handler_stack(&frame.active_handlers),
2274 summarize_handler_stack(current),
2275 anchor.map(|a| (a.prompt.0, a.install_eval_id.0)),
2276 summarize_handler_stack(&merged),
2277 ),
2278 );
2279 }
2280 let mut adjusted = frame.clone();
2281 adjusted.active_handlers = merged;
2282 adjusted
2283 })
2284 .collect()
2285}
2286
2287fn own_captured_return_frames(mut frames: Vec<CpsReturnFrame>) -> Vec<CpsReturnFrame> {
2292 for frame in &mut frames {
2293 frame.owner_initial_frame_count = 0;
2294 }
2295 frames
2296}
2297
2298fn continue_return_frames(
2306 module: &CpsModule,
2307 value: CpsRuntimeValue,
2308 frames: &[CpsReturnFrame],
2309 extra_handlers: &[CpsHandlerFrame],
2310) -> CpsEvalResult<CpsRuntimeValue> {
2311 if frames.is_empty() {
2312 return Ok(value);
2313 }
2314 if matches!(value, CpsRuntimeValue::ScopeReturn { .. })
2315 || matches!(value, CpsRuntimeValue::Aborted(_))
2316 || matches!(value, CpsRuntimeValue::RoutedJump(_))
2317 {
2318 return Ok(value);
2322 }
2323 let (frame, rest) = frames.split_last().expect("non-empty");
2324 trace_cps(
2325 "ContinueReturnFrames",
2326 format!(
2327 "pop owner={} cont={:?} owner_eval={} rest.len={} owner_initial={}",
2328 frame.owner_function,
2329 frame.continuation,
2330 frame.owner_eval_id.0,
2331 rest.len(),
2332 frame.owner_initial_frame_count,
2333 ),
2334 );
2335 let function = function_by_name(module, &frame.owner_function)?;
2336 let mut combined = frame.active_handlers.clone();
2337 for extra in extra_handlers {
2338 if !combined.iter().any(|f| f.prompt == extra.prompt) {
2339 combined.push(extra.clone());
2340 }
2341 }
2342 let owner_initial = frame.owner_initial_frame_count.min(rest.len());
2353 resume_continuation(
2354 module,
2355 function,
2356 frame.continuation,
2357 vec![value],
2358 frame.values.as_ref().clone(),
2359 combined,
2360 frame.guard_stack.clone(),
2361 rest.to_vec(),
2362 frame.active_blocked.clone(),
2363 owner_initial,
2364 frame.owner_eval_id,
2365 )
2366}
2367
2368fn try_route_scope_return_through_return_frames(
2393 module: &CpsModule,
2394 scope_return: &CpsRuntimeValue,
2395 return_frames: &[CpsReturnFrame],
2396 initial_frame_count: usize,
2397) -> CpsEvalResult<Option<CpsRuntimeValue>> {
2398 let CpsRuntimeValue::ScopeReturn {
2399 prompt,
2400 target,
2401 value,
2402 } = scope_return
2403 else {
2404 return Ok(None);
2405 };
2406 let prompt = *prompt;
2407 let target = *target;
2408 if target == EXIT_RWH_TARGET {
2409 return Ok(None);
2413 }
2414 if return_frames.is_empty() {
2415 return Ok(None);
2416 }
2417 trace_cps(
2418 "ScopeReturnFrameWalk",
2419 format!(
2420 "prompt={} target={:?} frames.len={} initial={}",
2421 prompt.0,
2422 target,
2423 return_frames.len(),
2424 initial_frame_count,
2425 ),
2426 );
2427 for frame_index in (0..return_frames.len()).rev() {
2428 let frame = &return_frames[frame_index];
2429 let frame_eval_id = frame.owner_eval_id;
2430 let frame_owner = &frame.owner_function;
2431 if trace_enabled() {
2432 trace_cps(
2433 "ScopeReturnFrameWalk",
2434 format!(
2435 " check frame_index={} owner={} owner_eval={} handlers={}",
2436 frame_index,
2437 frame.owner_function,
2438 frame.owner_eval_id.0,
2439 summarize_handler_stack(&frame.active_handlers),
2440 ),
2441 );
2442 }
2443 let Some(handler_index) = frame.active_handlers.iter().rposition(|handler| {
2446 handler.prompt == prompt && handler.install_eval_id == frame_eval_id
2447 }) else {
2448 continue;
2449 };
2450 let matched_handler = frame.active_handlers[handler_index].clone();
2451 if matched_handler.escape_owner_function != *frame_owner {
2455 continue;
2456 }
2457 debug_assert_eq!(
2458 matched_handler.install_eval_id, frame.owner_eval_id,
2459 "matched handler's install eval must equal frame.owner_eval_id"
2460 );
2461 let mut post_handlers = frame.active_handlers.clone();
2471 post_handlers.truncate(handler_index);
2472 let mut rest_frames: Vec<CpsReturnFrame> = return_frames[..frame_index].to_vec();
2479 let truncate_at = matched_handler.return_frame_threshold;
2480 let rest_before = rest_frames.len();
2481 if rest_frames.len() > truncate_at {
2482 rest_frames.truncate(truncate_at);
2483 }
2484 let rest_after = rest_frames.len();
2485 trace_cps(
2486 "FrameWalkMatch",
2487 format!(
2488 "frame_owner={} frame_owner_eval={} handler_prompt={} handler_install_eval={} handlers_before={} handlers_after={} rest_before={} truncate_at={} rest_after={}",
2489 frame.owner_function,
2490 frame.owner_eval_id.0,
2491 matched_handler.prompt.0,
2492 matched_handler.install_eval_id.0,
2493 summarize_handler_stack(&frame.active_handlers),
2494 summarize_handler_stack(&post_handlers),
2495 rest_before,
2496 truncate_at,
2497 rest_after,
2498 ),
2499 );
2500 let owner_initial = frame.owner_initial_frame_count.min(rest_frames.len());
2501 let routed_jump = CpsRoutedJump {
2502 value: value.clone(),
2503 return_frame_threshold: truncate_at,
2504 owner_function: frame.owner_function.clone(),
2505 target: matched_handler.escape,
2506 values: frame.values.clone(),
2507 active_handlers: post_handlers,
2508 guard_stack: frame.guard_stack.clone(),
2509 return_frames: rest_frames,
2510 active_blocked: frame.active_blocked.clone(),
2511 initial_frame_count: owner_initial,
2512 eval_id: frame.owner_eval_id,
2513 };
2514 if frame_index < initial_frame_count {
2519 return Ok(Some(CpsRuntimeValue::RoutedJump(Box::new(routed_jump))));
2520 }
2521 let owner = function_by_name(module, &routed_jump.owner_function)?;
2522 let result = resume_continuation(
2523 module,
2524 owner,
2525 routed_jump.target,
2526 vec![*value.clone()],
2527 routed_jump.values.as_ref().clone(),
2528 routed_jump.active_handlers,
2529 routed_jump.guard_stack,
2530 routed_jump.return_frames,
2531 routed_jump.active_blocked,
2532 routed_jump.initial_frame_count,
2533 routed_jump.eval_id,
2534 )?;
2535 return Ok(Some(result));
2536 }
2537 Ok(None)
2538}
2539
2540fn resolve_routed_jump(
2541 module: &CpsModule,
2542 value: CpsRuntimeValue,
2543 current_return_frames: &[CpsReturnFrame],
2544) -> CpsEvalResult<CpsRuntimeValue> {
2545 let CpsRuntimeValue::RoutedJump(jump) = value else {
2546 return Ok(value);
2547 };
2548 if current_return_frames.len() > jump.return_frame_threshold {
2549 return Ok(CpsRuntimeValue::RoutedJump(jump));
2550 }
2551 let owner = function_by_name(module, &jump.owner_function)?;
2552 resume_continuation(
2553 module,
2554 owner,
2555 jump.target,
2556 vec![*jump.value],
2557 jump.values.as_ref().clone(),
2558 jump.active_handlers,
2559 jump.guard_stack,
2560 jump.return_frames,
2561 jump.active_blocked,
2562 jump.initial_frame_count,
2563 jump.eval_id,
2564 )
2565}
2566
2567fn function_by_name<'a>(module: &'a CpsModule, name: &str) -> CpsEvalResult<&'a CpsFunction> {
2568 module
2569 .functions
2570 .iter()
2571 .chain(module.roots.iter())
2572 .find(|function| function.name == name)
2573 .ok_or_else(|| CpsEvalError::MissingFunction {
2574 name: name.to_string(),
2575 })
2576}
2577
2578fn continuation_by_id(
2579 function: &CpsFunction,
2580 id: CpsContinuationId,
2581) -> CpsEvalResult<&CpsContinuation> {
2582 function
2583 .continuations
2584 .iter()
2585 .find(|continuation| continuation.id == id)
2586 .ok_or_else(|| CpsEvalError::MissingContinuation {
2587 function: function.name.clone(),
2588 id,
2589 })
2590}
2591
2592fn handler_arm_continues_to_installed_escape(
2593 function: &CpsFunction,
2594 handler: CpsHandlerId,
2595 entry: CpsContinuationId,
2596 escape: CpsContinuationId,
2597) -> bool {
2598 let escape_is_installed_for_handler = function.continuations.iter().any(|continuation| {
2599 continuation.stmts.iter().any(|stmt| {
2600 matches!(
2601 stmt,
2602 CpsStmt::InstallHandler {
2603 handler: id,
2604 escape: installed_escape,
2605 ..
2606 } if *id == handler && *installed_escape == escape
2607 )
2608 })
2609 });
2610 if !escape_is_installed_for_handler {
2611 return false;
2612 }
2613 handler_arm_continue_chain_reaches_escape(function, handler, entry, escape)
2614}
2615
2616fn handler_arm_continue_chain_reaches_escape(
2617 function: &CpsFunction,
2618 handler: CpsHandlerId,
2619 entry: CpsContinuationId,
2620 escape: CpsContinuationId,
2621) -> bool {
2622 let mut current = entry;
2623 let mut saw_uninstall = false;
2624 let mut visited = HashSet::new();
2625 while visited.insert(current) {
2626 let Some(continuation) = function
2627 .continuations
2628 .iter()
2629 .find(|continuation| continuation.id == current)
2630 else {
2631 return false;
2632 };
2633 saw_uninstall |= continuation.stmts.iter().any(
2634 |stmt| matches!(stmt, CpsStmt::UninstallHandler { handler: id } if *id == handler),
2635 );
2636 let CpsTerminator::Continue { target, .. } = &continuation.terminator else {
2637 return saw_uninstall && current == escape;
2638 };
2639 if *target == escape {
2640 return saw_uninstall;
2641 }
2642 current = *target;
2643 }
2644 false
2645}
2646
2647fn handler_arm_uses_resume_with_handler(
2648 function: &CpsFunction,
2649 handler: CpsHandlerId,
2650 entry: CpsContinuationId,
2651) -> bool {
2652 let mut current = entry;
2653 let mut visited = HashSet::new();
2654 while visited.insert(current) {
2655 let Some(continuation) = function
2656 .continuations
2657 .iter()
2658 .find(|continuation| continuation.id == current)
2659 else {
2660 return false;
2661 };
2662 if continuation.stmts.iter().any(
2663 |stmt| matches!(stmt, CpsStmt::ResumeWithHandler { handler: id, .. } if *id == handler),
2664 ) {
2665 return true;
2666 }
2667 let CpsTerminator::Continue { target, .. } = &continuation.terminator else {
2668 return false;
2669 };
2670 current = *target;
2671 }
2672 false
2673}
2674
2675fn handler_arm_for_stack<'a>(
2676 module: &'a CpsModule,
2677 current_function: &'a CpsFunction,
2678 stack: &'a [CpsHandlerFrame],
2679 effect: &typed_ir::Path,
2680 blocked: Option<u64>,
2681) -> CpsEvalResult<(
2682 &'a CpsHandlerArm,
2683 &'a CpsHandlerFrame,
2684 Vec<CpsHandlerFrame>,
2685 &'a CpsFunction,
2686)> {
2687 for (index, frame) in stack.iter().enumerate().rev() {
2688 if let Some(blocked) = blocked
2689 && frame.guard_stack.iter().any(|entry| entry.id == blocked)
2690 {
2691 trace_cps(
2692 "PerformHandlerSkip",
2693 format!(
2694 "fn={} effect={:?} index={} prompt={} blocked={}",
2695 current_function.name, effect, index, frame.prompt.0, blocked
2696 ),
2697 );
2698 continue;
2699 }
2700 for owner in module.functions.iter().chain(module.roots.iter()) {
2701 if let Some(arm) = owner
2702 .handlers
2703 .iter()
2704 .find(|handler| handler.id == frame.handler)
2705 .and_then(|handler| {
2706 handler
2707 .arms
2708 .iter()
2709 .find(|arm| effect_matches(&arm.effect, effect))
2710 })
2711 {
2712 return Ok((arm, frame, stack[..index].to_vec(), owner));
2713 }
2714 }
2715 }
2716 Err(CpsEvalError::MissingHandler {
2717 function: current_function.name.clone(),
2718 id: stack.last().expect("handler stack is non-empty").handler,
2719 })
2720}
2721
2722fn handler_stack_with_static(
2723 active_handlers: &[CpsHandlerFrame],
2724 fallback: CpsHandlerId,
2725 guard_stack: &[CpsGuardEntry],
2726) -> Vec<CpsHandlerFrame> {
2727 if active_handlers.is_empty() {
2728 vec![CpsHandlerFrame {
2735 prompt: fresh_prompt(),
2736 handler: fallback,
2737 guard_stack: guard_stack.to_vec(),
2738 envs: Vec::new(),
2739 escape: EXIT_RWH_TARGET,
2740 escape_owner_function: String::new(),
2741 inherited: false,
2742 return_frame_threshold: 0,
2743 install_eval_id: SYNTHETIC_EVAL_ID,
2744 }]
2745 } else {
2746 active_handlers.to_vec()
2747 }
2748}
2749
2750#[allow(dead_code)]
2751fn handler_stack_with_pushed(
2752 active_handlers: &[CpsHandlerFrame],
2753 handler: CpsHandlerId,
2754 guard_stack: &[CpsGuardEntry],
2755 envs: Vec<CpsEvaluatedHandlerEnv>,
2756) -> Vec<CpsHandlerFrame> {
2757 let mut stack = active_handlers.to_vec();
2758 stack.push(CpsHandlerFrame {
2759 prompt: fresh_prompt(),
2760 handler,
2761 guard_stack: guard_stack.to_vec(),
2762 envs,
2763 escape: EXIT_RWH_TARGET,
2766 escape_owner_function: String::new(),
2767 inherited: false,
2768 return_frame_threshold: 0,
2769 install_eval_id: SYNTHETIC_EVAL_ID,
2772 });
2773 stack
2774}
2775
2776fn capture_handler_envs(
2777 function: &CpsFunction,
2778 values: &[Option<CpsRuntimeValue>],
2779 envs: &[CpsHandlerEnv],
2780) -> CpsEvalResult<Vec<CpsEvaluatedHandlerEnv>> {
2781 envs.iter()
2782 .map(|env| {
2783 let mut values_by_id = Vec::new();
2784 for (index, value) in env.values.iter().enumerate() {
2785 let target = env.targets.get(index).copied().unwrap_or(*value);
2786 values_by_id.push((target, read_value(function, values, *value)?));
2787 }
2788 Ok(CpsEvaluatedHandlerEnv {
2789 entry: env.entry,
2790 values: values_by_id,
2791 })
2792 })
2793 .collect()
2794}
2795
2796fn overlay_handler_envs_in_frames(
2797 function: &str,
2798 frames: &mut [CpsReturnFrame],
2799 handler: CpsHandlerId,
2800 updates: &[CpsEvaluatedHandlerEnv],
2801 remember_latest: bool,
2802) {
2803 for frame in frames {
2804 overlay_handler_envs_in_stack(
2805 function,
2806 &mut frame.active_handlers,
2807 handler,
2808 updates,
2809 remember_latest,
2810 );
2811 }
2812}
2813
2814fn overlay_handler_envs_in_stack(
2815 function: &str,
2816 stack: &mut [CpsHandlerFrame],
2817 handler: CpsHandlerId,
2818 updates: &[CpsEvaluatedHandlerEnv],
2819 remember_latest: bool,
2820) {
2821 if updates.is_empty() {
2822 return;
2823 }
2824 for frame in stack.iter_mut().filter(|frame| frame.handler == handler) {
2825 overlay_handler_envs(&mut frame.envs, updates);
2826 }
2827 if remember_latest {
2828 remember_latest_handler_envs(handler, updates);
2829 }
2830 push_cps_frame_trace_event(CpsFrameTraceEvent::HandlerEnvOverlay {
2831 layer: CpsFrameTraceLayer::CpsEval,
2832 function: function.to_string(),
2833 handler: handler.0,
2834 entries: updates.iter().map(|env| env.entry.0).collect(),
2835 values: trace_cps_handler_env_slots(updates),
2836 });
2837}
2838
2839fn overlay_handler_envs(
2840 envs: &mut Vec<CpsEvaluatedHandlerEnv>,
2841 updates: &[CpsEvaluatedHandlerEnv],
2842) {
2843 for update in updates {
2844 let Some(existing) = envs.iter_mut().find(|env| env.entry == update.entry) else {
2845 envs.push(update.clone());
2846 continue;
2847 };
2848 for (target, value) in &update.values {
2849 if let Some((_, existing_value)) = existing
2850 .values
2851 .iter_mut()
2852 .find(|(existing_target, _)| existing_target == target)
2853 {
2854 *existing_value = value.clone();
2855 } else {
2856 existing.values.push((*target, value.clone()));
2857 }
2858 }
2859 }
2860}
2861
2862fn remember_latest_handler_envs(handler: CpsHandlerId, updates: &[CpsEvaluatedHandlerEnv]) {
2863 LATEST_HANDLER_ENVS.with(|latest| {
2864 let mut latest = latest.borrow_mut();
2865 for update in updates {
2866 for (target, value) in &update.values {
2867 if let Some(existing) = latest.iter_mut().find(|existing| {
2868 existing.handler == handler
2869 && existing.entry == update.entry
2870 && existing.target == *target
2871 }) {
2872 existing.value = value.clone();
2873 } else {
2874 latest.push(CpsLatestHandlerEnv {
2875 handler,
2876 entry: update.entry,
2877 target: *target,
2878 value: value.clone(),
2879 });
2880 }
2881 }
2882 }
2883 });
2884}
2885
2886fn latest_handler_env_value(
2887 handler: CpsHandlerId,
2888 entry: CpsContinuationId,
2889 target: CpsValueId,
2890) -> Option<CpsRuntimeValue> {
2891 LATEST_HANDLER_ENVS.with(|latest| {
2892 latest
2893 .borrow()
2894 .iter()
2895 .rev()
2896 .find(|latest| {
2897 latest.handler == handler && latest.entry == entry && latest.target == target
2898 })
2899 .map(|latest| latest.value.clone())
2900 })
2901}
2902
2903fn values_with_handler_env(
2904 function: &str,
2905 mut values: Vec<Option<CpsRuntimeValue>>,
2906 frame: &CpsHandlerFrame,
2907 entry: CpsContinuationId,
2908) -> Vec<Option<CpsRuntimeValue>> {
2909 let Some(env) = frame.envs.iter().find(|env| env.entry == entry) else {
2910 return values;
2911 };
2912 let effective_values = env
2913 .values
2914 .iter()
2915 .map(|(id, value)| {
2916 (
2917 *id,
2918 latest_handler_env_value(frame.handler, entry, *id)
2919 .unwrap_or_else(|| value.clone()),
2920 )
2921 })
2922 .collect::<Vec<_>>();
2923 push_cps_frame_trace_event(CpsFrameTraceEvent::HandlerEnvRead {
2924 layer: CpsFrameTraceLayer::CpsEval,
2925 function: function.to_string(),
2926 handler: frame.handler.0,
2927 entry: entry.0,
2928 values: trace_cps_handler_env_value_slots(&effective_values),
2929 });
2930 for (id, value) in effective_values {
2931 write_value(&mut values, id, value);
2932 }
2933 values
2934}
2935
2936fn trace_cps_handler_env_slots(envs: &[CpsEvaluatedHandlerEnv]) -> Vec<CpsFrameTraceSlot> {
2937 envs.iter()
2938 .flat_map(|env| {
2939 env.values.iter().map(|(target, value)| CpsFrameTraceSlot {
2940 target: target.0,
2941 value: summarize_cps_value(value),
2942 })
2943 })
2944 .collect()
2945}
2946
2947fn trace_cps_handler_env_value_slots(
2948 values: &[(CpsValueId, CpsRuntimeValue)],
2949) -> Vec<CpsFrameTraceSlot> {
2950 values
2951 .iter()
2952 .map(|(target, value)| CpsFrameTraceSlot {
2953 target: target.0,
2954 value: summarize_cps_value(value),
2955 })
2956 .collect()
2957}
2958
2959fn add_thunk_boundary(
2960 value: CpsRuntimeValue,
2961 guard_id: u64,
2962 allowed: typed_ir::Type,
2963 active: bool,
2964) -> CpsRuntimeValue {
2965 let CpsRuntimeValue::Thunk(thunk) = value else {
2966 return value;
2967 };
2968 let mut blocked = thunk.blocked.as_ref().clone();
2969 blocked.push(CpsBlockedEffect {
2970 guard_id,
2971 allowed,
2972 active,
2973 });
2974 CpsRuntimeValue::Thunk(Rc::new(CpsThunk {
2975 owner_function: thunk.owner_function.clone(),
2976 entry: thunk.entry,
2977 values: thunk.values.clone(),
2978 handlers: thunk.handlers.clone(),
2979 guard_stack: thunk.guard_stack.clone(),
2980 blocked: Rc::new(blocked),
2981 }))
2982}
2983
2984fn active_blocked_for_thunk(
2985 current: &[CpsBlockedEffect],
2986 thunk: &CpsThunk,
2987) -> Vec<CpsBlockedEffect> {
2988 let mut active = current.to_vec();
2989 active.extend(
2990 thunk
2991 .blocked
2992 .iter()
2993 .filter(|blocked| blocked.active)
2994 .cloned(),
2995 );
2996 active
2997}
2998
2999fn active_blocked_id(effect: &typed_ir::Path, active: &[CpsBlockedEffect]) -> Option<u64> {
3000 active
3001 .iter()
3002 .rev()
3003 .find(|blocked| !effect_allowed_by_type(&blocked.allowed, effect))
3004 .map(|blocked| blocked.guard_id)
3005}
3006
3007fn effect_allowed_by_type(allowed: &typed_ir::Type, effect: &typed_ir::Path) -> bool {
3008 match allowed {
3009 typed_ir::Type::Any => true,
3010 typed_ir::Type::Never => false,
3011 typed_ir::Type::Named { path, .. } => effect_path_matches_allowed(path, effect),
3012 typed_ir::Type::Row { items, tail } => {
3013 items
3014 .iter()
3015 .any(|item| effect_allowed_by_type(item, effect))
3016 || matches!(tail.as_ref(), typed_ir::Type::Any)
3017 }
3018 _ => false,
3019 }
3020}
3021
3022fn effect_path_matches_allowed(allowed: &typed_ir::Path, effect: &typed_ir::Path) -> bool {
3023 if effect.segments.starts_with(&allowed.segments) {
3024 return true;
3025 }
3026 if allowed.segments.len() > 1
3027 && effect.segments.len() == allowed.segments.len()
3028 && effect.segments[..effect.segments.len() - 1]
3029 == allowed.segments[..allowed.segments.len() - 1]
3030 && effect_segment_matches_allowed(
3031 &allowed.segments[allowed.segments.len() - 1],
3032 &effect.segments[effect.segments.len() - 1],
3033 )
3034 {
3035 return true;
3036 }
3037 effect
3038 .segments
3039 .iter()
3040 .enumerate()
3041 .skip(1)
3042 .any(|(index, _)| effect.segments[index..].starts_with(&allowed.segments))
3043}
3044
3045fn effect_segment_matches_allowed(allowed: &typed_ir::Name, effect: &typed_ir::Name) -> bool {
3046 allowed == effect
3047 || effect
3048 .0
3049 .strip_suffix("#effect")
3050 .is_some_and(|base| base == allowed.0)
3051}
3052
3053fn effect_matches(expected: &typed_ir::Path, actual: &typed_ir::Path) -> bool {
3054 effect_path_matches_allowed(expected, actual)
3055}
3056
3057fn assign_continuation_args(
3058 values: &mut Vec<Option<CpsRuntimeValue>>,
3059 function: &CpsFunction,
3060 continuation: &CpsContinuation,
3061 args: Vec<CpsRuntimeValue>,
3062) -> CpsEvalResult<()> {
3063 if continuation.params.len() != args.len() {
3064 return Err(CpsEvalError::ContinuationArgumentMismatch {
3065 function: function.name.clone(),
3066 id: continuation.id,
3067 expected: continuation.params.len(),
3068 actual: args.len(),
3069 });
3070 }
3071 for (param, value) in continuation.params.iter().copied().zip(args) {
3072 write_value(values, param, value);
3073 }
3074 Ok(())
3075}
3076
3077fn write_value(values: &mut Vec<Option<CpsRuntimeValue>>, id: CpsValueId, value: CpsRuntimeValue) {
3078 if values.len() <= id.0 {
3079 values.resize_with(id.0 + 1, || None);
3080 }
3081 values[id.0] = Some(value);
3082}
3083
3084fn read_value(
3085 function: &CpsFunction,
3086 values: &[Option<CpsRuntimeValue>],
3087 id: CpsValueId,
3088) -> CpsEvalResult<CpsRuntimeValue> {
3089 values
3090 .get(id.0)
3091 .and_then(Clone::clone)
3092 .ok_or_else(|| CpsEvalError::MissingValue {
3093 function: function.name.clone(),
3094 id,
3095 })
3096}
3097
3098fn read_plain_value(
3099 function: &CpsFunction,
3100 values: &[Option<CpsRuntimeValue>],
3101 id: CpsValueId,
3102) -> CpsEvalResult<runtime::VmValue> {
3103 into_plain_value(function, id, read_value(function, values, id)?)
3104}
3105
3106fn read_effect_id(
3107 function: &CpsFunction,
3108 values: &[Option<CpsRuntimeValue>],
3109 id: CpsValueId,
3110) -> CpsEvalResult<u64> {
3111 match read_plain_value(function, values, id)? {
3112 runtime::VmValue::EffectId(value_id) => Ok(value_id),
3113 value => Err(CpsEvalError::ExpectedGuard {
3114 function: function.name.clone(),
3115 id,
3116 value,
3117 }),
3118 }
3119}
3120
3121fn into_plain_value(
3122 function: &CpsFunction,
3123 id: CpsValueId,
3124 value: CpsRuntimeValue,
3125) -> CpsEvalResult<runtime::VmValue> {
3126 cps_value_to_vm(value).ok_or_else(|| CpsEvalError::ExpectedPlainValue {
3127 function: function.name.clone(),
3128 id,
3129 })
3130}
3131
3132fn read_resumption(
3133 function: &CpsFunction,
3134 values: &[Option<CpsRuntimeValue>],
3135 id: CpsValueId,
3136) -> CpsEvalResult<Rc<CpsResumption>> {
3137 match read_value(function, values, id)? {
3138 CpsRuntimeValue::Resumption(resumption) => Ok(resumption),
3139 _ => Err(CpsEvalError::ExpectedResumption {
3140 function: function.name.clone(),
3141 id,
3142 }),
3143 }
3144}
3145
3146#[allow(dead_code)]
3147fn read_closure(
3148 function: &CpsFunction,
3149 values: &[Option<CpsRuntimeValue>],
3150 id: CpsValueId,
3151) -> CpsEvalResult<Rc<CpsClosure>> {
3152 match read_value(function, values, id)? {
3153 CpsRuntimeValue::Closure(closure) => Ok(closure),
3154 _ => Err(CpsEvalError::ExpectedPlainValue {
3155 function: function.name.clone(),
3156 id,
3157 }),
3158 }
3159}
3160
3161#[derive(Debug, Clone, PartialEq)]
3162enum CpsRuntimeValue {
3163 Plain(runtime::VmValue),
3164 Resumption(Rc<CpsResumption>),
3165 Thunk(Rc<CpsThunk>),
3166 Closure(Rc<CpsClosure>),
3167 List(Rc<Vec<CpsRuntimeValue>>),
3172 Tuple(Vec<CpsRuntimeValue>),
3173 Record(BTreeMap<typed_ir::Name, CpsRuntimeValue>),
3174 Variant {
3175 tag: typed_ir::Name,
3176 value: Option<Box<CpsRuntimeValue>>,
3177 },
3178 RoutedJump(Box<CpsRoutedJump>),
3182 #[allow(dead_code)]
3185 Aborted(Box<CpsRuntimeValue>),
3186 ScopeReturn {
3199 prompt: CpsPromptId,
3200 target: CpsContinuationId,
3201 value: Box<CpsRuntimeValue>,
3202 },
3203}
3204
3205#[derive(Debug, Clone, PartialEq)]
3206struct CpsRoutedJump {
3207 value: Box<CpsRuntimeValue>,
3208 return_frame_threshold: usize,
3209 owner_function: String,
3210 target: CpsContinuationId,
3211 values: Rc<Vec<Option<CpsRuntimeValue>>>,
3212 active_handlers: Vec<CpsHandlerFrame>,
3213 guard_stack: Vec<CpsGuardEntry>,
3214 return_frames: Vec<CpsReturnFrame>,
3215 active_blocked: Vec<CpsBlockedEffect>,
3216 initial_frame_count: usize,
3217 eval_id: CpsEvalId,
3218}
3219
3220const EXIT_RWH_TARGET: CpsContinuationId = CpsContinuationId(usize::MAX);
3225
3226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3227struct CpsPromptId(u64);
3228
3229thread_local! {
3230 static PROMPT_COUNTER: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
3231}
3232
3233fn fresh_prompt() -> CpsPromptId {
3234 PROMPT_COUNTER.with(|cell| {
3235 let id = cell.get();
3236 cell.set(id + 1);
3237 CpsPromptId(id)
3238 })
3239}
3240
3241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3246struct CpsEvalId(u64);
3247
3248thread_local! {
3249 static EVAL_ID_COUNTER: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
3250}
3251
3252fn fresh_eval_id() -> CpsEvalId {
3253 EVAL_ID_COUNTER.with(|cell| {
3254 let id = cell.get();
3255 cell.set(id + 1);
3256 CpsEvalId(id)
3257 })
3258}
3259
3260const SYNTHETIC_EVAL_ID: CpsEvalId = CpsEvalId(u64::MAX);
3265
3266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3273struct CpsHandlerAnchor {
3274 prompt: CpsPromptId,
3275 install_eval_id: CpsEvalId,
3276}
3277
3278#[derive(Debug, Clone, PartialEq)]
3279struct CpsResumption {
3280 owner_function: String,
3281 target: CpsContinuationId,
3282 values: Rc<Vec<Option<CpsRuntimeValue>>>,
3283 handlers: Rc<Vec<CpsHandlerFrame>>,
3284 guard_stack: Rc<Vec<CpsGuardEntry>>,
3285 active_blocked: Rc<Vec<CpsBlockedEffect>>,
3286 return_frames: Rc<Vec<CpsReturnFrame>>,
3291 handled_anchor: Option<CpsHandlerAnchor>,
3297}
3298
3299struct CapturedPromptContinuation {
3300 handlers: Vec<CpsHandlerFrame>,
3301 return_frames: Vec<CpsReturnFrame>,
3302}
3303
3304#[derive(Debug, Clone, PartialEq)]
3310struct CpsReturnFrame {
3311 prompt_exit: Option<CpsPromptExitFrame>,
3312 owner_function: String,
3313 continuation: CpsContinuationId,
3314 values: Rc<Vec<Option<CpsRuntimeValue>>>,
3315 active_handlers: Vec<CpsHandlerFrame>,
3319 guard_stack: Vec<CpsGuardEntry>,
3320 active_blocked: Vec<CpsBlockedEffect>,
3321 owner_initial_frame_count: usize,
3327 owner_eval_id: CpsEvalId,
3333}
3334
3335#[derive(Debug, Clone, PartialEq)]
3336struct CpsPromptExitFrame {
3337 prompt: CpsPromptId,
3338}
3339
3340#[derive(Debug, Clone, PartialEq)]
3341struct CpsThunk {
3342 owner_function: String,
3343 entry: CpsContinuationId,
3344 values: Rc<Vec<Option<CpsRuntimeValue>>>,
3345 handlers: Rc<Vec<CpsHandlerFrame>>,
3346 guard_stack: Rc<Vec<CpsGuardEntry>>,
3347 blocked: Rc<Vec<CpsBlockedEffect>>,
3348}
3349
3350#[derive(Debug, Clone, PartialEq)]
3351struct CpsBlockedEffect {
3352 guard_id: u64,
3353 allowed: typed_ir::Type,
3354 active: bool,
3355}
3356
3357#[derive(Debug, Clone, PartialEq)]
3358struct CpsClosure {
3359 owner_function: String,
3360 entry: CpsContinuationId,
3361 values: Rc<Vec<Option<CpsRuntimeValue>>>,
3362 recursive_self: Option<CpsValueId>,
3363}
3364
3365#[derive(Debug, Clone, PartialEq)]
3366struct CpsHandlerFrame {
3367 prompt: CpsPromptId,
3371 handler: CpsHandlerId,
3372 guard_stack: Vec<CpsGuardEntry>,
3373 envs: Vec<CpsEvaluatedHandlerEnv>,
3374 escape: CpsContinuationId,
3378 escape_owner_function: String,
3382 return_frame_threshold: usize,
3389 inherited: bool,
3395 install_eval_id: CpsEvalId,
3402}
3403
3404#[derive(Debug, Clone, PartialEq)]
3405struct CpsEvaluatedHandlerEnv {
3406 entry: CpsContinuationId,
3407 values: Vec<(CpsValueId, CpsRuntimeValue)>,
3408}
3409
3410#[derive(Debug, Clone, PartialEq)]
3411struct CpsLatestHandlerEnv {
3412 handler: CpsHandlerId,
3413 entry: CpsContinuationId,
3414 target: CpsValueId,
3415 value: CpsRuntimeValue,
3416}
3417
3418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3419struct CpsGuardEntry {
3420 var: runtime::EffectIdVar,
3421 id: u64,
3422}
3423
3424fn eval_literal(lit: &CpsLiteral) -> runtime::VmValue {
3425 match lit {
3426 CpsLiteral::Int(value) => runtime::VmValue::Int(value.clone()),
3427 CpsLiteral::Float(value) => runtime::VmValue::Float(value.clone()),
3428 CpsLiteral::String(value) => {
3429 runtime::VmValue::String(runtime::runtime::string_tree::StringTree::from_str(value))
3430 }
3431 CpsLiteral::Bool(value) => runtime::VmValue::Bool(*value),
3432 CpsLiteral::Unit => runtime::VmValue::Unit,
3433 }
3434}
3435
3436fn eval_cps_primitive(
3442 op: typed_ir::PrimitiveOp,
3443 args: Vec<CpsRuntimeValue>,
3444) -> CpsEvalResult<CpsRuntimeValue> {
3445 use typed_ir::PrimitiveOp;
3446 match op {
3447 PrimitiveOp::ListEmpty => {
3448 if args.len() > 1 {
3452 return Err(CpsEvalError::InvalidPrimitiveArity {
3453 op,
3454 expected: 1,
3455 actual: args.len(),
3456 });
3457 }
3458 Ok(CpsRuntimeValue::List(Rc::new(Vec::new())))
3459 }
3460 PrimitiveOp::ListSingleton => {
3461 if args.len() != 1 {
3462 return Err(CpsEvalError::InvalidPrimitiveArity {
3463 op,
3464 expected: 1,
3465 actual: args.len(),
3466 });
3467 }
3468 Ok(CpsRuntimeValue::List(Rc::new(vec![
3469 args.into_iter().next().unwrap(),
3470 ])))
3471 }
3472 PrimitiveOp::ListMerge => {
3473 if args.len() != 2 {
3474 return Err(CpsEvalError::InvalidPrimitiveArity {
3475 op,
3476 expected: 2,
3477 actual: args.len(),
3478 });
3479 }
3480 let mut iter = args.into_iter();
3481 let left = iter.next().unwrap();
3482 let right = iter.next().unwrap();
3483 let mut merged = control_list_items(op, left)?;
3484 merged.extend(control_list_items(op, right)?);
3485 Ok(CpsRuntimeValue::List(Rc::new(merged)))
3486 }
3487 PrimitiveOp::ListLen => {
3488 if args.len() != 1 {
3489 return Err(CpsEvalError::InvalidPrimitiveArity {
3490 op,
3491 expected: 1,
3492 actual: args.len(),
3493 });
3494 }
3495 let items = control_list_items(op, args.into_iter().next().unwrap())?;
3496 Ok(CpsRuntimeValue::Plain(runtime::VmValue::Int(
3497 items.len().to_string(),
3498 )))
3499 }
3500 PrimitiveOp::ListIndex => {
3501 if args.len() != 2 {
3502 return Err(CpsEvalError::InvalidPrimitiveArity {
3503 op,
3504 expected: 2,
3505 actual: args.len(),
3506 });
3507 }
3508 let mut iter = args.into_iter();
3509 let list = iter.next().unwrap();
3510 let idx_val = iter.next().unwrap();
3511 let items = control_list_items(op, list)?;
3512 let idx = cps_value_to_usize(op, idx_val)?;
3513 items
3514 .into_iter()
3515 .nth(idx)
3516 .ok_or(CpsEvalError::UnsupportedPrimitive { op })
3517 }
3518 PrimitiveOp::ListIndexRangeRaw => {
3519 if args.len() != 3 {
3520 return Err(CpsEvalError::InvalidPrimitiveArity {
3521 op,
3522 expected: 3,
3523 actual: args.len(),
3524 });
3525 }
3526 let mut iter = args.into_iter();
3527 let list = iter.next().unwrap();
3528 let start_val = iter.next().unwrap();
3529 let end_val = iter.next().unwrap();
3530 let items = control_list_items(op, list)?;
3531 let start = cps_value_to_usize(op, start_val)?;
3532 let end = cps_value_to_usize(op, end_val)?;
3533 Ok(CpsRuntimeValue::List(Rc::new(
3534 items.into_iter().skip(start).take(end - start).collect(),
3535 )))
3536 }
3537 _ => {
3538 let plain_args = args
3539 .into_iter()
3540 .map(cps_value_to_vm)
3541 .collect::<Option<Vec<_>>>()
3542 .ok_or(CpsEvalError::UnsupportedPrimitive { op })?;
3543 eval_primitive(op, &plain_args).map(cps_value_from_vm)
3544 }
3545 }
3546}
3547
3548fn cps_value_to_usize(op: typed_ir::PrimitiveOp, value: CpsRuntimeValue) -> CpsEvalResult<usize> {
3549 match value {
3550 CpsRuntimeValue::Plain(runtime::VmValue::Int(s)) => s
3551 .parse::<usize>()
3552 .map_err(|_| CpsEvalError::UnsupportedPrimitive { op }),
3553 _ => Err(CpsEvalError::UnsupportedPrimitive { op }),
3554 }
3555}
3556
3557fn control_list_items(
3558 op: typed_ir::PrimitiveOp,
3559 value: CpsRuntimeValue,
3560) -> CpsEvalResult<Vec<CpsRuntimeValue>> {
3561 match value {
3562 CpsRuntimeValue::List(items) => Ok(items.as_ref().clone()),
3563 CpsRuntimeValue::Plain(plain) => match plain {
3564 runtime::VmValue::List(list) => Ok(list
3565 .to_vec()
3566 .into_iter()
3567 .map(|item| cps_value_from_vm((*item).clone()))
3568 .collect()),
3569 other => Err(CpsEvalError::PrimitiveTypeMismatch { op, value: other }),
3570 },
3571 _ => Err(CpsEvalError::UnsupportedPrimitive { op }),
3572 }
3573}
3574
3575fn eval_primitive(
3576 op: typed_ir::PrimitiveOp,
3577 args: &[runtime::VmValue],
3578) -> CpsEvalResult<runtime::VmValue> {
3579 crate::eval::eval_primitive_for_abi(op, args).map_err(cps_eval_primitive_error)
3580}
3581
3582fn bool_value(op: typed_ir::PrimitiveOp, value: &runtime::VmValue) -> CpsEvalResult<bool> {
3583 match value {
3584 runtime::VmValue::Bool(value) => Ok(*value),
3585 value => Err(CpsEvalError::PrimitiveTypeMismatch {
3586 op,
3587 value: value.clone(),
3588 }),
3589 }
3590}
3591
3592fn cps_eval_primitive_error(error: crate::eval::NativeEvalError) -> CpsEvalError {
3593 match error {
3594 crate::eval::NativeEvalError::InvalidPrimitiveArity {
3595 op,
3596 expected,
3597 actual,
3598 } => CpsEvalError::InvalidPrimitiveArity {
3599 op,
3600 expected,
3601 actual,
3602 },
3603 crate::eval::NativeEvalError::PrimitiveTypeMismatch { op, value } => {
3604 CpsEvalError::PrimitiveTypeMismatch { op, value }
3605 }
3606 crate::eval::NativeEvalError::UnsupportedPrimitive { op } => {
3607 CpsEvalError::UnsupportedPrimitive { op }
3608 }
3609 other => unreachable!("native primitive evaluator returned non-primitive error: {other}"),
3610 }
3611}
3612
3613#[cfg(test)]
3614mod tests {
3615 use std::rc::Rc;
3616
3617 use crate::cps_ir::{
3618 CpsContinuation, CpsContinuationId, CpsFunction, CpsHandler, CpsHandlerArm, CpsHandlerId,
3619 CpsLiteral, CpsModule, CpsShotKind, CpsStmt, CpsTerminator, CpsValueId,
3620 };
3621 use crate::cps_validate::validate_cps_module;
3622
3623 use super::*;
3624
3625 #[test]
3626 fn evaluates_plain_value_primitives_through_native_evaluator() {
3627 let string_root = CpsFunction {
3628 name: "string-root".to_string(),
3629 params: Vec::new(),
3630 entry: CpsContinuationId(0),
3631 handlers: Vec::new(),
3632 continuations: vec![CpsContinuation {
3633 id: CpsContinuationId(0),
3634 params: Vec::new(),
3635 captures: Vec::new(),
3636 shot_kind: CpsShotKind::OneShot,
3637 stmts: vec![
3638 CpsStmt::Literal {
3639 dest: CpsValueId(0),
3640 literal: CpsLiteral::String("aあ🙂z".to_string()),
3641 },
3642 CpsStmt::Literal {
3643 dest: CpsValueId(1),
3644 literal: CpsLiteral::Int("1".to_string()),
3645 },
3646 CpsStmt::Literal {
3647 dest: CpsValueId(2),
3648 literal: CpsLiteral::Int("3".to_string()),
3649 },
3650 CpsStmt::Literal {
3651 dest: CpsValueId(3),
3652 literal: CpsLiteral::String("bc".to_string()),
3653 },
3654 CpsStmt::Primitive {
3655 dest: CpsValueId(4),
3656 op: typed_ir::PrimitiveOp::StringSpliceRaw,
3657 args: vec![CpsValueId(0), CpsValueId(1), CpsValueId(2), CpsValueId(3)],
3658 },
3659 ],
3660 terminator: CpsTerminator::Return(CpsValueId(4)),
3661 }],
3662 };
3663 let list_root = CpsFunction {
3664 name: "list-root".to_string(),
3665 params: Vec::new(),
3666 entry: CpsContinuationId(0),
3667 handlers: Vec::new(),
3668 continuations: vec![CpsContinuation {
3669 id: CpsContinuationId(0),
3670 params: Vec::new(),
3671 captures: Vec::new(),
3672 shot_kind: CpsShotKind::OneShot,
3673 stmts: vec![
3674 CpsStmt::Literal {
3675 dest: CpsValueId(0),
3676 literal: CpsLiteral::Int("1".to_string()),
3677 },
3678 CpsStmt::Literal {
3679 dest: CpsValueId(1),
3680 literal: CpsLiteral::Int("2".to_string()),
3681 },
3682 CpsStmt::Literal {
3683 dest: CpsValueId(2),
3684 literal: CpsLiteral::Int("3".to_string()),
3685 },
3686 CpsStmt::Literal {
3687 dest: CpsValueId(3),
3688 literal: CpsLiteral::Int("4".to_string()),
3689 },
3690 CpsStmt::Primitive {
3691 dest: CpsValueId(4),
3692 op: typed_ir::PrimitiveOp::ListSingleton,
3693 args: vec![CpsValueId(0)],
3694 },
3695 CpsStmt::Primitive {
3696 dest: CpsValueId(5),
3697 op: typed_ir::PrimitiveOp::ListSingleton,
3698 args: vec![CpsValueId(1)],
3699 },
3700 CpsStmt::Primitive {
3701 dest: CpsValueId(6),
3702 op: typed_ir::PrimitiveOp::ListMerge,
3703 args: vec![CpsValueId(4), CpsValueId(5)],
3704 },
3705 CpsStmt::Primitive {
3706 dest: CpsValueId(7),
3707 op: typed_ir::PrimitiveOp::ListSingleton,
3708 args: vec![CpsValueId(2)],
3709 },
3710 CpsStmt::Primitive {
3711 dest: CpsValueId(8),
3712 op: typed_ir::PrimitiveOp::ListMerge,
3713 args: vec![CpsValueId(6), CpsValueId(7)],
3714 },
3715 CpsStmt::Primitive {
3716 dest: CpsValueId(9),
3717 op: typed_ir::PrimitiveOp::ListSingleton,
3718 args: vec![CpsValueId(3)],
3719 },
3720 CpsStmt::Primitive {
3721 dest: CpsValueId(10),
3722 op: typed_ir::PrimitiveOp::ListMerge,
3723 args: vec![CpsValueId(8), CpsValueId(9)],
3724 },
3725 CpsStmt::Literal {
3726 dest: CpsValueId(11),
3727 literal: CpsLiteral::Int("8".to_string()),
3728 },
3729 CpsStmt::Literal {
3730 dest: CpsValueId(12),
3731 literal: CpsLiteral::Int("9".to_string()),
3732 },
3733 CpsStmt::Primitive {
3734 dest: CpsValueId(13),
3735 op: typed_ir::PrimitiveOp::ListSingleton,
3736 args: vec![CpsValueId(11)],
3737 },
3738 CpsStmt::Primitive {
3739 dest: CpsValueId(14),
3740 op: typed_ir::PrimitiveOp::ListSingleton,
3741 args: vec![CpsValueId(12)],
3742 },
3743 CpsStmt::Primitive {
3744 dest: CpsValueId(15),
3745 op: typed_ir::PrimitiveOp::ListMerge,
3746 args: vec![CpsValueId(13), CpsValueId(14)],
3747 },
3748 CpsStmt::Literal {
3749 dest: CpsValueId(16),
3750 literal: CpsLiteral::Int("1".to_string()),
3751 },
3752 CpsStmt::Literal {
3753 dest: CpsValueId(17),
3754 literal: CpsLiteral::Int("3".to_string()),
3755 },
3756 CpsStmt::Primitive {
3757 dest: CpsValueId(18),
3758 op: typed_ir::PrimitiveOp::ListSpliceRaw,
3759 args: vec![
3760 CpsValueId(10),
3761 CpsValueId(16),
3762 CpsValueId(17),
3763 CpsValueId(15),
3764 ],
3765 },
3766 ],
3767 terminator: CpsTerminator::Return(CpsValueId(18)),
3768 }],
3769 };
3770 let module = CpsModule {
3771 functions: Vec::new(),
3772 roots: vec![string_root, list_root],
3773 };
3774
3775 validate_cps_module(&module).expect("valid CPS");
3776 assert_eq!(
3777 eval_cps_module(&module).expect("evaluated"),
3778 vec![
3779 runtime::VmValue::String(runtime::runtime::string_tree::StringTree::from_str(
3780 "abcz",
3781 )),
3782 runtime::VmValue::List(runtime::runtime::list_tree::ListTree::from_items([
3783 Rc::new(runtime::VmValue::Int("1".to_string())),
3784 Rc::new(runtime::VmValue::Int("8".to_string())),
3785 Rc::new(runtime::VmValue::Int("9".to_string())),
3786 Rc::new(runtime::VmValue::Int("4".to_string())),
3787 ])),
3788 ]
3789 );
3790 }
3791
3792 #[test]
3793 fn evaluates_multishot_resumption_with_shared_snapshot() {
3794 let effect = typed_ir::Path::from_name(typed_ir::Name("choose".to_string()));
3795 let module = CpsModule {
3796 functions: Vec::new(),
3797 roots: vec![CpsFunction {
3798 name: "root".to_string(),
3799 params: Vec::new(),
3800 entry: CpsContinuationId(0),
3801 handlers: vec![CpsHandler {
3802 id: CpsHandlerId(0),
3803 arms: vec![CpsHandlerArm {
3804 effect: effect.clone(),
3805 entry: CpsContinuationId(2),
3806 }],
3807 }],
3808 continuations: vec![
3809 CpsContinuation {
3810 id: CpsContinuationId(0),
3811 params: Vec::new(),
3812 captures: Vec::new(),
3813 shot_kind: CpsShotKind::MultiShot,
3814 stmts: vec![CpsStmt::Literal {
3815 dest: CpsValueId(0),
3816 literal: CpsLiteral::Int("1".to_string()),
3817 }],
3818 terminator: CpsTerminator::Perform {
3819 effect,
3820 payload: CpsValueId(0),
3821 resume: CpsContinuationId(1),
3822 handler: CpsHandlerId(0),
3823 blocked: None,
3824 },
3825 },
3826 CpsContinuation {
3827 id: CpsContinuationId(1),
3828 params: vec![CpsValueId(1)],
3829 captures: Vec::new(),
3830 shot_kind: CpsShotKind::MultiShot,
3831 stmts: vec![
3832 CpsStmt::Literal {
3833 dest: CpsValueId(2),
3834 literal: CpsLiteral::Int("10".to_string()),
3835 },
3836 CpsStmt::Primitive {
3837 dest: CpsValueId(3),
3838 op: typed_ir::PrimitiveOp::IntAdd,
3839 args: vec![CpsValueId(1), CpsValueId(2)],
3840 },
3841 ],
3842 terminator: CpsTerminator::Return(CpsValueId(3)),
3843 },
3844 CpsContinuation {
3845 id: CpsContinuationId(2),
3846 params: vec![CpsValueId(4), CpsValueId(5)],
3847 captures: Vec::new(),
3848 shot_kind: CpsShotKind::MultiShot,
3849 stmts: vec![
3850 CpsStmt::Literal {
3851 dest: CpsValueId(6),
3852 literal: CpsLiteral::Int("2".to_string()),
3853 },
3854 CpsStmt::Resume {
3855 dest: CpsValueId(7),
3856 resumption: CpsValueId(5),
3857 arg: CpsValueId(4),
3858 },
3859 CpsStmt::Resume {
3860 dest: CpsValueId(8),
3861 resumption: CpsValueId(5),
3862 arg: CpsValueId(6),
3863 },
3864 CpsStmt::Primitive {
3865 dest: CpsValueId(9),
3866 op: typed_ir::PrimitiveOp::IntAdd,
3867 args: vec![CpsValueId(7), CpsValueId(8)],
3868 },
3869 ],
3870 terminator: CpsTerminator::Return(CpsValueId(9)),
3871 },
3872 ],
3873 }],
3874 };
3875
3876 validate_cps_module(&module).expect("valid CPS");
3877 assert_eq!(
3878 eval_cps_module(&module).expect("evaluated"),
3879 vec![runtime::VmValue::Int("23".to_string())]
3880 );
3881 }
3882
3883 #[test]
3884 fn evaluates_resumption_under_fresh_handler_stack() {
3885 let module = rebased_resumption_module();
3886
3887 validate_cps_module(&module).expect("valid CPS");
3888 assert_eq!(
3889 eval_cps_module(&module).expect("evaluated"),
3890 vec![runtime::VmValue::Int("13".to_string())]
3891 );
3892 }
3893
3894 fn rebased_resumption_module() -> CpsModule {
3895 let effect = typed_ir::Path::from_name(typed_ir::Name("choose".to_string()));
3896 CpsModule {
3897 functions: Vec::new(),
3898 roots: vec![CpsFunction {
3899 name: "root".to_string(),
3900 params: Vec::new(),
3901 entry: CpsContinuationId(0),
3902 handlers: vec![
3903 CpsHandler {
3904 id: CpsHandlerId(0),
3905 arms: vec![CpsHandlerArm {
3906 effect: effect.clone(),
3907 entry: CpsContinuationId(2),
3908 }],
3909 },
3910 CpsHandler {
3911 id: CpsHandlerId(1),
3912 arms: vec![CpsHandlerArm {
3913 effect: effect.clone(),
3914 entry: CpsContinuationId(4),
3915 }],
3916 },
3917 ],
3918 continuations: vec![
3919 CpsContinuation {
3920 id: CpsContinuationId(0),
3921 params: Vec::new(),
3922 captures: Vec::new(),
3923 shot_kind: CpsShotKind::MultiShot,
3924 stmts: vec![CpsStmt::Literal {
3925 dest: CpsValueId(0),
3926 literal: CpsLiteral::Int("1".to_string()),
3927 }],
3928 terminator: CpsTerminator::Perform {
3929 effect: effect.clone(),
3930 payload: CpsValueId(0),
3931 resume: CpsContinuationId(1),
3932 handler: CpsHandlerId(0),
3933 blocked: None,
3934 },
3935 },
3936 CpsContinuation {
3937 id: CpsContinuationId(1),
3938 params: vec![CpsValueId(1)],
3939 captures: Vec::new(),
3940 shot_kind: CpsShotKind::MultiShot,
3941 stmts: vec![CpsStmt::Literal {
3942 dest: CpsValueId(2),
3943 literal: CpsLiteral::Int("2".to_string()),
3944 }],
3945 terminator: CpsTerminator::Perform {
3946 effect: effect.clone(),
3947 payload: CpsValueId(2),
3948 resume: CpsContinuationId(3),
3949 handler: CpsHandlerId(0),
3950 blocked: None,
3951 },
3952 },
3953 CpsContinuation {
3954 id: CpsContinuationId(2),
3955 params: vec![CpsValueId(4), CpsValueId(5)],
3956 captures: Vec::new(),
3957 shot_kind: CpsShotKind::MultiShot,
3958 stmts: vec![CpsStmt::ResumeWithHandler {
3959 dest: CpsValueId(6),
3960 resumption: CpsValueId(5),
3961 arg: CpsValueId(4),
3962 handler: CpsHandlerId(1),
3963 envs: Vec::new(),
3964 }],
3965 terminator: CpsTerminator::Return(CpsValueId(6)),
3966 },
3967 CpsContinuation {
3968 id: CpsContinuationId(3),
3969 params: vec![CpsValueId(9)],
3970 captures: vec![CpsValueId(1)],
3971 shot_kind: CpsShotKind::MultiShot,
3972 stmts: vec![CpsStmt::Primitive {
3973 dest: CpsValueId(13),
3974 op: typed_ir::PrimitiveOp::IntAdd,
3975 args: vec![CpsValueId(1), CpsValueId(9)],
3976 }],
3977 terminator: CpsTerminator::Return(CpsValueId(13)),
3978 },
3979 CpsContinuation {
3980 id: CpsContinuationId(4),
3981 params: vec![CpsValueId(7), CpsValueId(8)],
3982 captures: Vec::new(),
3983 shot_kind: CpsShotKind::MultiShot,
3984 stmts: vec![
3985 CpsStmt::Literal {
3986 dest: CpsValueId(10),
3987 literal: CpsLiteral::Int("10".to_string()),
3988 },
3989 CpsStmt::Primitive {
3990 dest: CpsValueId(11),
3991 op: typed_ir::PrimitiveOp::IntAdd,
3992 args: vec![CpsValueId(7), CpsValueId(10)],
3993 },
3994 CpsStmt::Resume {
3995 dest: CpsValueId(12),
3996 resumption: CpsValueId(8),
3997 arg: CpsValueId(11),
3998 },
3999 ],
4000 terminator: CpsTerminator::Return(CpsValueId(12)),
4001 },
4002 ],
4003 }],
4004 }
4005 }
4006}