Skip to main content

yulang_native/
cps_repr.rs

1use std::cell::RefCell;
2use std::collections::{BTreeMap, HashMap, 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, CpsHandlerEnv, CpsHandlerId, CpsLiteral,
14    CpsModule, CpsShotKind, CpsStmt, CpsTerminator, CpsValueId,
15};
16
17pub type CpsReprEvalResult<T> = Result<T, CpsReprEvalError>;
18
19thread_local! {
20    static LATEST_REPR_HANDLER_ENVS: RefCell<Vec<CpsReprLatestHandlerEnv>> = const { RefCell::new(Vec::new()) };
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum CpsReprValueKind {
25    Plain,
26    Resumption,
27    Unknown,
28}
29
30impl CpsReprValueKind {
31    fn merge(self, other: Self) -> Self {
32        match (self, other) {
33            (left, right) if left == right => left,
34            (Self::Unknown, known) | (known, Self::Unknown) => known,
35            _ => Self::Unknown,
36        }
37    }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct CpsReprValueAnalysis {
42    pub functions: HashMap<String, CpsReprFunctionValueAnalysis>,
43}
44
45impl CpsReprValueAnalysis {
46    pub fn value_kind(&self, function: &str, value: CpsValueId) -> Option<CpsReprValueKind> {
47        self.functions.get(function)?.values.get(&value).copied()
48    }
49
50    pub fn continuation_return_kind(
51        &self,
52        function: &str,
53        continuation: CpsContinuationId,
54    ) -> Option<CpsReprValueKind> {
55        self.functions
56            .get(function)?
57            .continuation_returns
58            .get(&continuation)
59            .copied()
60    }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct CpsReprFunctionValueAnalysis {
65    pub values: HashMap<CpsValueId, CpsReprValueKind>,
66    pub continuation_returns: HashMap<CpsContinuationId, CpsReprValueKind>,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum CpsReprAbiLane {
71    ScalarI64,
72    NativeFloat,
73    RuntimeValuePtr,
74    ClosurePtr,
75    ThunkPtr,
76    ResumptionPtr,
77    OpaqueI64,
78    Conflict,
79    Unknown,
80}
81
82impl CpsReprAbiLane {
83    fn merge(self, other: Self) -> Self {
84        match (self, other) {
85            (left, right) if left == right => left,
86            (Self::Unknown, known) | (known, Self::Unknown) => known,
87            (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
88            (Self::NativeFloat, Self::OpaqueI64) | (Self::OpaqueI64, Self::NativeFloat) => {
89                Self::NativeFloat
90            }
91            (Self::NativeFloat, _) | (_, Self::NativeFloat) => Self::Conflict,
92            (Self::OpaqueI64, _) | (_, Self::OpaqueI64) => Self::OpaqueI64,
93            _ => Self::OpaqueI64,
94        }
95    }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct CpsReprAbiAnalysis {
100    pub functions: HashMap<String, CpsReprFunctionAbiAnalysis>,
101}
102
103impl CpsReprAbiAnalysis {
104    pub fn function_return_lane(&self, function: &str) -> Option<CpsReprAbiLane> {
105        let function = self.functions.get(function)?;
106        function.continuation_returns.get(&function.entry).copied()
107    }
108
109    pub fn value_lane(&self, function: &str, value: CpsValueId) -> Option<CpsReprAbiLane> {
110        self.functions.get(function)?.values.get(&value).copied()
111    }
112
113    pub fn continuation_return_lane(
114        &self,
115        function: &str,
116        continuation: CpsContinuationId,
117    ) -> Option<CpsReprAbiLane> {
118        self.functions
119            .get(function)?
120            .continuation_returns
121            .get(&continuation)
122            .copied()
123    }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct CpsReprFunctionAbiAnalysis {
128    pub entry: CpsContinuationId,
129    pub values: HashMap<CpsValueId, CpsReprAbiLane>,
130    pub continuation_returns: HashMap<CpsContinuationId, CpsReprAbiLane>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct CpsReprModule {
135    pub functions: Vec<CpsReprFunction>,
136    pub roots: Vec<CpsReprFunction>,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct CpsReprFunction {
141    pub name: String,
142    pub params: Vec<CpsValueId>,
143    pub entry: CpsContinuationId,
144    pub continuations: Vec<CpsReprContinuation>,
145    pub handlers: Vec<CpsReprHandler>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct CpsReprContinuation {
150    pub id: CpsContinuationId,
151    pub params: Vec<CpsValueId>,
152    pub environment: Vec<CpsReprEnvironmentSlot>,
153    pub shot_kind: CpsShotKind,
154    pub stmts: Vec<CpsStmt>,
155    pub terminator: CpsTerminator,
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub struct CpsReprEnvironmentSlot {
160    pub index: usize,
161    pub value: CpsValueId,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct CpsReprHandler {
166    pub id: CpsHandlerId,
167    pub arms: Vec<CpsReprHandlerArm>,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct CpsReprHandlerArm {
172    pub effect: typed_ir::Path,
173    pub entry: CpsContinuationId,
174}
175
176#[derive(Debug, Clone, PartialEq)]
177pub enum CpsReprEvalError {
178    MissingFunction {
179        name: String,
180    },
181    MissingContinuation {
182        function: String,
183        id: CpsContinuationId,
184    },
185    MissingHandler {
186        function: String,
187        id: CpsHandlerId,
188    },
189    ContinuationArgumentMismatch {
190        function: String,
191        id: CpsContinuationId,
192        expected: usize,
193        actual: usize,
194    },
195    FunctionArgumentMismatch {
196        function: String,
197        expected: usize,
198        actual: usize,
199    },
200    MissingValue {
201        function: String,
202        id: CpsValueId,
203    },
204    ExpectedPlainValue {
205        function: String,
206        id: CpsValueId,
207    },
208    ExpectedResumption {
209        function: String,
210        id: CpsValueId,
211    },
212    ExpectedGuard {
213        function: String,
214        id: CpsValueId,
215        value: runtime::VmValue,
216    },
217    MissingGuard {
218        function: String,
219    },
220    UnsupportedStmt {
221        function: String,
222        kind: &'static str,
223    },
224    UnsupportedPrimitive {
225        op: typed_ir::PrimitiveOp,
226    },
227    PrimitiveTypeMismatch {
228        op: typed_ir::PrimitiveOp,
229        value: runtime::VmValue,
230    },
231    InvalidPrimitiveArity {
232        op: typed_ir::PrimitiveOp,
233        expected: usize,
234        actual: usize,
235    },
236    ExpectedRecord {
237        function: String,
238        value: runtime::VmValue,
239    },
240    MissingRecordField {
241        function: String,
242        field: typed_ir::Name,
243    },
244}
245
246impl fmt::Display for CpsReprEvalError {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        match self {
249            CpsReprEvalError::MissingFunction { name } => {
250                write!(f, "CPS repr function {name} is missing")
251            }
252            CpsReprEvalError::MissingContinuation { function, id } => {
253                write!(
254                    f,
255                    "CPS repr function {function} is missing continuation {id:?}"
256                )
257            }
258            CpsReprEvalError::MissingHandler { function, id } => {
259                write!(f, "CPS repr function {function} is missing handler {id:?}")
260            }
261            CpsReprEvalError::ContinuationArgumentMismatch {
262                function,
263                id,
264                expected,
265                actual,
266            } => write!(
267                f,
268                "CPS repr continuation {function}::{id:?} expected {expected} arguments, got {actual}"
269            ),
270            CpsReprEvalError::FunctionArgumentMismatch {
271                function,
272                expected,
273                actual,
274            } => write!(
275                f,
276                "CPS repr function {function} expected {expected} arguments, got {actual}"
277            ),
278            CpsReprEvalError::MissingValue { function, id } => {
279                write!(
280                    f,
281                    "CPS repr function {function} references missing value {id:?}"
282                )
283            }
284            CpsReprEvalError::ExpectedPlainValue { function, id } => write!(
285                f,
286                "CPS repr function {function} expected plain value {id:?}"
287            ),
288            CpsReprEvalError::ExpectedResumption { function, id } => write!(
289                f,
290                "CPS repr function {function} expected resumption value {id:?}"
291            ),
292            CpsReprEvalError::ExpectedGuard {
293                function,
294                id,
295                value,
296            } => write!(
297                f,
298                "CPS repr function {function} expected guard value {id:?}, got {value:?}"
299            ),
300            CpsReprEvalError::MissingGuard { function } => {
301                write!(f, "CPS repr function {function} has no active guard id")
302            }
303            CpsReprEvalError::UnsupportedStmt { function, kind } => write!(
304                f,
305                "CPS repr evaluator does not support {kind} statements in `{function}` yet"
306            ),
307            CpsReprEvalError::UnsupportedPrimitive { op } => {
308                write!(
309                    f,
310                    "CPS repr evaluator does not support primitive {op:?} yet"
311                )
312            }
313            CpsReprEvalError::PrimitiveTypeMismatch { op, value } => {
314                write!(f, "CPS repr primitive {op:?} cannot accept value {value:?}")
315            }
316            CpsReprEvalError::InvalidPrimitiveArity {
317                op,
318                expected,
319                actual,
320            } => write!(
321                f,
322                "CPS repr primitive {op:?} expected {expected} args, got {actual}"
323            ),
324            CpsReprEvalError::ExpectedRecord { function, value } => write!(
325                f,
326                "CPS repr function {function} expected record value, got {value:?}"
327            ),
328            CpsReprEvalError::MissingRecordField { function, field } => write!(
329                f,
330                "CPS repr function {function} selected missing record field {field:?}"
331            ),
332        }
333    }
334}
335
336impl std::error::Error for CpsReprEvalError {}
337
338pub fn lower_cps_repr_module(module: &CpsModule) -> CpsReprModule {
339    CpsReprModule {
340        functions: module.functions.iter().map(lower_function).collect(),
341        roots: module.roots.iter().map(lower_function).collect(),
342    }
343}
344
345pub fn eval_cps_repr_module(module: &CpsReprModule) -> CpsReprEvalResult<Vec<runtime::VmValue>> {
346    module
347        .roots
348        .iter()
349        .map(|root| {
350            let value =
351                with_fresh_repr_handler_env_overlay(|| eval_function(module, root, Vec::new()))?;
352            let value = resolve_routed_jump_repr(module, value, &[])?;
353            let value = unwrap_aborted_repr(value);
354            into_plain_value(root, CpsValueId(usize::MAX), value)
355        })
356        .collect()
357}
358
359fn with_fresh_repr_handler_env_overlay<T>(f: impl FnOnce() -> T) -> T {
360    let previous = LATEST_REPR_HANDLER_ENVS.with(|envs| envs.replace(Vec::new()));
361    let result = f();
362    LATEST_REPR_HANDLER_ENVS.with(|envs| {
363        envs.replace(previous);
364    });
365    result
366}
367
368fn unwrap_aborted_repr(value: CpsReprRuntimeValue) -> CpsReprRuntimeValue {
369    match value {
370        CpsReprRuntimeValue::Aborted(inner) => unwrap_aborted_repr(*inner),
371        other => other,
372    }
373}
374
375fn cps_repr_value_from_vm(value: runtime::VmValue) -> CpsReprRuntimeValue {
376    match value {
377        runtime::VmValue::Tuple(items) => {
378            CpsReprRuntimeValue::Tuple(items.into_iter().map(cps_repr_value_from_vm).collect())
379        }
380        runtime::VmValue::Variant { tag, value } => CpsReprRuntimeValue::Variant {
381            tag,
382            value: value.map(|v| Box::new(cps_repr_value_from_vm(*v))),
383        },
384        runtime::VmValue::List(list) => {
385            let items = list
386                .to_vec()
387                .into_iter()
388                .map(|item| cps_repr_value_from_vm((*item).clone()))
389                .collect::<Vec<_>>();
390            CpsReprRuntimeValue::List(Rc::new(items))
391        }
392        other => CpsReprRuntimeValue::Plain(other),
393    }
394}
395
396fn cps_repr_value_to_vm(value: CpsReprRuntimeValue) -> Option<runtime::VmValue> {
397    match value {
398        CpsReprRuntimeValue::Plain(value) => Some(value),
399        CpsReprRuntimeValue::Aborted(inner) => cps_repr_value_to_vm(*inner),
400        CpsReprRuntimeValue::RoutedJump(_) => None,
401        CpsReprRuntimeValue::Tuple(items) => Some(runtime::VmValue::Tuple(
402            items
403                .into_iter()
404                .map(cps_repr_value_to_vm)
405                .collect::<Option<Vec<_>>>()?,
406        )),
407        CpsReprRuntimeValue::Record(fields) => Some(runtime::VmValue::Record(
408            fields
409                .into_iter()
410                .map(|(name, value)| Some((name, cps_repr_value_to_vm(value)?)))
411                .collect::<Option<BTreeMap<_, _>>>()?,
412        )),
413        CpsReprRuntimeValue::Variant { tag, value } => Some(runtime::VmValue::Variant {
414            tag,
415            value: match value {
416                Some(value) => Some(Box::new(cps_repr_value_to_vm(*value)?)),
417                None => None,
418            },
419        }),
420        CpsReprRuntimeValue::List(items) => {
421            let vm_items = items
422                .iter()
423                .cloned()
424                .map(cps_repr_value_to_vm)
425                .collect::<Option<Vec<_>>>()?;
426            let mut tree = runtime::runtime::list_tree::ListTree::empty();
427            for item in vm_items {
428                tree = runtime::runtime::list_tree::ListTree::concat(
429                    tree,
430                    runtime::runtime::list_tree::ListTree::singleton(Rc::new(item)),
431                );
432            }
433            Some(runtime::VmValue::List(tree))
434        }
435        CpsReprRuntimeValue::Resumption(_)
436        | CpsReprRuntimeValue::Thunk(_)
437        | CpsReprRuntimeValue::Closure(_)
438        // write26: ScopeReturn must be matched against an installed
439        // handler before reaching root. If it does, that's a lowering
440        // bug — surface as None so `into_plain_value` reports it.
441        | CpsReprRuntimeValue::ScopeReturn { .. } => None,
442    }
443}
444
445fn eval_cps_repr_primitive(
446    op: typed_ir::PrimitiveOp,
447    args: Vec<CpsReprRuntimeValue>,
448) -> CpsReprEvalResult<CpsReprRuntimeValue> {
449    use typed_ir::PrimitiveOp;
450    match op {
451        PrimitiveOp::ListEmpty => {
452            if args.len() > 1 {
453                return Err(CpsReprEvalError::InvalidPrimitiveArity {
454                    op,
455                    expected: 1,
456                    actual: args.len(),
457                });
458            }
459            Ok(CpsReprRuntimeValue::List(Rc::new(Vec::new())))
460        }
461        PrimitiveOp::ListSingleton => {
462            if args.len() != 1 {
463                return Err(CpsReprEvalError::InvalidPrimitiveArity {
464                    op,
465                    expected: 1,
466                    actual: args.len(),
467                });
468            }
469            Ok(CpsReprRuntimeValue::List(Rc::new(vec![
470                args.into_iter().next().unwrap(),
471            ])))
472        }
473        PrimitiveOp::ListMerge => {
474            if args.len() != 2 {
475                return Err(CpsReprEvalError::InvalidPrimitiveArity {
476                    op,
477                    expected: 2,
478                    actual: args.len(),
479                });
480            }
481            let mut iter = args.into_iter();
482            let left = iter.next().unwrap();
483            let right = iter.next().unwrap();
484            let mut merged = control_repr_list_items(op, left)?;
485            merged.extend(control_repr_list_items(op, right)?);
486            Ok(CpsReprRuntimeValue::List(Rc::new(merged)))
487        }
488        PrimitiveOp::ListLen => {
489            if args.len() != 1 {
490                return Err(CpsReprEvalError::InvalidPrimitiveArity {
491                    op,
492                    expected: 1,
493                    actual: args.len(),
494                });
495            }
496            let items = control_repr_list_items(op, args.into_iter().next().unwrap())?;
497            Ok(CpsReprRuntimeValue::Plain(runtime::VmValue::Int(
498                items.len().to_string(),
499            )))
500        }
501        PrimitiveOp::ListIndex => {
502            if args.len() != 2 {
503                return Err(CpsReprEvalError::InvalidPrimitiveArity {
504                    op,
505                    expected: 2,
506                    actual: args.len(),
507                });
508            }
509            let mut iter = args.into_iter();
510            let list = iter.next().unwrap();
511            let idx_val = iter.next().unwrap();
512            let items = control_repr_list_items(op, list)?;
513            let idx = cps_repr_value_to_usize(op, idx_val)?;
514            items
515                .into_iter()
516                .nth(idx)
517                .ok_or(CpsReprEvalError::UnsupportedPrimitive { op })
518        }
519        PrimitiveOp::ListIndexRangeRaw => {
520            if args.len() != 3 {
521                return Err(CpsReprEvalError::InvalidPrimitiveArity {
522                    op,
523                    expected: 3,
524                    actual: args.len(),
525                });
526            }
527            let mut iter = args.into_iter();
528            let list = iter.next().unwrap();
529            let start_val = iter.next().unwrap();
530            let end_val = iter.next().unwrap();
531            let items = control_repr_list_items(op, list)?;
532            let start = cps_repr_value_to_usize(op, start_val)?;
533            let end = cps_repr_value_to_usize(op, end_val)?;
534            Ok(CpsReprRuntimeValue::List(Rc::new(
535                items.into_iter().skip(start).take(end - start).collect(),
536            )))
537        }
538        _ => {
539            let plain_args = args
540                .into_iter()
541                .map(cps_repr_value_to_vm)
542                .collect::<Option<Vec<_>>>()
543                .ok_or(CpsReprEvalError::UnsupportedPrimitive { op })?;
544            eval_primitive(op, &plain_args).map(cps_repr_value_from_vm)
545        }
546    }
547}
548
549fn cps_repr_value_to_usize(
550    op: typed_ir::PrimitiveOp,
551    value: CpsReprRuntimeValue,
552) -> CpsReprEvalResult<usize> {
553    match value {
554        CpsReprRuntimeValue::Plain(runtime::VmValue::Int(s)) => s
555            .parse::<usize>()
556            .map_err(|_| CpsReprEvalError::UnsupportedPrimitive { op }),
557        _ => Err(CpsReprEvalError::UnsupportedPrimitive { op }),
558    }
559}
560
561fn control_repr_list_items(
562    op: typed_ir::PrimitiveOp,
563    value: CpsReprRuntimeValue,
564) -> CpsReprEvalResult<Vec<CpsReprRuntimeValue>> {
565    match value {
566        CpsReprRuntimeValue::List(items) => Ok(items.as_ref().clone()),
567        CpsReprRuntimeValue::Plain(plain) => match plain {
568            runtime::VmValue::List(list) => Ok(list
569                .to_vec()
570                .into_iter()
571                .map(|item| cps_repr_value_from_vm((*item).clone()))
572                .collect()),
573            other => Err(CpsReprEvalError::PrimitiveTypeMismatch { op, value: other }),
574        },
575        _ => Err(CpsReprEvalError::UnsupportedPrimitive { op }),
576    }
577}
578
579fn function_by_name_repr<'a>(
580    module: &'a CpsReprModule,
581    name: &str,
582) -> CpsReprEvalResult<&'a CpsReprFunction> {
583    module
584        .functions
585        .iter()
586        .chain(module.roots.iter())
587        .find(|function| function.name == name)
588        .ok_or_else(|| CpsReprEvalError::MissingFunction {
589            name: name.to_string(),
590        })
591}
592
593pub fn analyze_cps_repr_values(module: &CpsReprModule) -> CpsReprValueAnalysis {
594    CpsReprValueAnalysis {
595        functions: module
596            .functions
597            .iter()
598            .chain(&module.roots)
599            .map(|function| (function.name.clone(), analyze_function_values(function)))
600            .collect(),
601    }
602}
603
604fn propagate_direct_call_argument_lanes(
605    module: &CpsReprModule,
606    analysis: &mut CpsReprAbiAnalysis,
607) -> bool {
608    let function_params = module
609        .functions
610        .iter()
611        .chain(&module.roots)
612        .map(|function| (function.name.as_str(), function.params.as_slice()))
613        .collect::<HashMap<_, _>>();
614    let mut changed = false;
615    for function in module.functions.iter().chain(&module.roots) {
616        for continuation in &function.continuations {
617            for stmt in &continuation.stmts {
618                let CpsStmt::DirectCall { target, args, .. } = stmt else {
619                    continue;
620                };
621                let Some(params) = function_params.get(target.as_str()).copied() else {
622                    continue;
623                };
624                for (param, arg) in params.iter().zip(args) {
625                    let lane = analysis
626                        .value_lane(&function.name, *arg)
627                        .unwrap_or(CpsReprAbiLane::Unknown);
628                    if let Some(target_analysis) = analysis.functions.get_mut(target) {
629                        changed |= merge_abi_lane(&mut target_analysis.values, *param, lane);
630                    }
631                }
632            }
633        }
634    }
635    changed
636}
637
638pub fn analyze_cps_repr_abi_lanes(module: &CpsReprModule) -> CpsReprAbiAnalysis {
639    let mut analysis = CpsReprAbiAnalysis {
640        functions: module
641            .functions
642            .iter()
643            .chain(&module.roots)
644            .map(|function| {
645                (
646                    function.name.clone(),
647                    CpsReprFunctionAbiAnalysis {
648                        entry: function.entry,
649                        values: HashMap::new(),
650                        continuation_returns: HashMap::new(),
651                    },
652                )
653            })
654            .collect(),
655    };
656    loop {
657        let mut changed = false;
658        for function in module.functions.iter().chain(&module.roots) {
659            let function_analysis = analyze_function_abi_lanes(function, &analysis);
660            if analysis.functions.get(&function.name) != Some(&function_analysis) {
661                analysis
662                    .functions
663                    .insert(function.name.clone(), function_analysis);
664                changed = true;
665            }
666        }
667        changed |= propagate_direct_call_argument_lanes(module, &mut analysis);
668        if !changed {
669            return analysis;
670        }
671    }
672}
673
674fn lower_function(function: &CpsFunction) -> CpsReprFunction {
675    CpsReprFunction {
676        name: function.name.clone(),
677        params: function.params.clone(),
678        entry: function.entry,
679        continuations: function
680            .continuations
681            .iter()
682            .map(lower_continuation)
683            .collect(),
684        handlers: function
685            .handlers
686            .iter()
687            .map(|handler| CpsReprHandler {
688                id: handler.id,
689                arms: handler
690                    .arms
691                    .iter()
692                    .map(|arm| CpsReprHandlerArm {
693                        effect: arm.effect.clone(),
694                        entry: arm.entry,
695                    })
696                    .collect(),
697            })
698            .collect(),
699    }
700}
701
702fn lower_continuation(continuation: &CpsContinuation) -> CpsReprContinuation {
703    CpsReprContinuation {
704        id: continuation.id,
705        params: continuation.params.clone(),
706        environment: continuation
707            .captures
708            .iter()
709            .copied()
710            .enumerate()
711            .map(|(index, value)| CpsReprEnvironmentSlot { index, value })
712            .collect(),
713        shot_kind: continuation.shot_kind,
714        stmts: continuation.stmts.clone(),
715        terminator: continuation.terminator.clone(),
716    }
717}
718
719fn make_thunk_entries(function: &CpsReprFunction) -> HashMap<CpsValueId, CpsContinuationId> {
720    let mut entries = HashMap::new();
721    for continuation in &function.continuations {
722        for stmt in &continuation.stmts {
723            if let CpsStmt::MakeThunk { dest, entry } = stmt {
724                entries.insert(*dest, *entry);
725            }
726        }
727    }
728    entries
729}
730
731fn analyze_function_abi_lanes(
732    function: &CpsReprFunction,
733    module_analysis: &CpsReprAbiAnalysis,
734) -> CpsReprFunctionAbiAnalysis {
735    let mut values = HashMap::new();
736    let mut continuation_returns = HashMap::new();
737    let thunk_entries = make_thunk_entries(function);
738    for param in &function.params {
739        values.insert(
740            *param,
741            module_analysis
742                .value_lane(&function.name, *param)
743                .unwrap_or(CpsReprAbiLane::Unknown),
744        );
745    }
746    for handler in &function.handlers {
747        for arm in &handler.arms {
748            if let Some(entry) = continuation_by_id_opt(function, arm.entry) {
749                if let Some(payload) = entry.params.first() {
750                    values.insert(*payload, CpsReprAbiLane::Unknown);
751                }
752                if let Some(resumption) = entry.params.get(1) {
753                    values.insert(*resumption, CpsReprAbiLane::ResumptionPtr);
754                }
755            }
756        }
757    }
758
759    loop {
760        let mut changed = false;
761        for continuation in &function.continuations {
762            for param in &continuation.params {
763                values.entry(*param).or_insert(CpsReprAbiLane::Unknown);
764            }
765            for slot in &continuation.environment {
766                values.entry(slot.value).or_insert(CpsReprAbiLane::Unknown);
767            }
768            for stmt in &continuation.stmts {
769                let lane = match stmt {
770                    CpsStmt::Literal { literal, .. } => literal_lane(literal),
771                    CpsStmt::FreshGuard { .. }
772                    | CpsStmt::PeekGuard { .. }
773                    | CpsStmt::FindGuard { .. } => CpsReprAbiLane::ScalarI64,
774                    CpsStmt::MakeThunk { .. } | CpsStmt::AddThunkBoundary { .. } => {
775                        CpsReprAbiLane::ThunkPtr
776                    }
777                    CpsStmt::MakeClosure { .. } | CpsStmt::MakeRecursiveClosure { .. } => {
778                        CpsReprAbiLane::ClosurePtr
779                    }
780                    CpsStmt::Tuple { .. }
781                    | CpsStmt::Record { .. }
782                    | CpsStmt::RecordWithoutFields { .. }
783                    | CpsStmt::Variant { .. }
784                    | CpsStmt::Select { .. }
785                    | CpsStmt::SelectWithDefault { .. } => CpsReprAbiLane::RuntimeValuePtr,
786                    CpsStmt::RecordHasField { .. } => CpsReprAbiLane::ScalarI64,
787                    CpsStmt::TupleGet { .. } | CpsStmt::VariantPayload { .. } => {
788                        CpsReprAbiLane::Unknown
789                    }
790                    CpsStmt::VariantTagEq { .. } => CpsReprAbiLane::ScalarI64,
791                    CpsStmt::Primitive { op, .. } => primitive_result_lane(*op),
792                    CpsStmt::DirectCall { target, .. } => module_analysis
793                        .function_return_lane(target)
794                        .unwrap_or(CpsReprAbiLane::Unknown),
795                    CpsStmt::ApplyClosure { .. } => CpsReprAbiLane::Unknown,
796                    CpsStmt::CloneContinuation { source, .. } => abi_lane(&values, *source),
797                    CpsStmt::ForceThunk { thunk, .. } => thunk_entries
798                        .get(thunk)
799                        .and_then(|entry| continuation_returns.get(entry).copied())
800                        .unwrap_or(CpsReprAbiLane::OpaqueI64),
801                    CpsStmt::Resume { resumption, .. }
802                    | CpsStmt::ResumeWithHandler { resumption, .. } => {
803                        resumption_target_return_lane(
804                            function,
805                            &values,
806                            &continuation_returns,
807                            *resumption,
808                        )
809                    }
810                    CpsStmt::InstallHandler { .. } | CpsStmt::UninstallHandler { .. } => continue,
811                };
812                if let Some(dest) = stmt_dest(stmt) {
813                    if merge_abi_lane(&mut values, dest, lane) {
814                        changed = true;
815                    }
816                }
817            }
818            if propagate_terminator_argument_lanes(function, &mut values, &continuation.terminator)
819            {
820                changed = true;
821            }
822            let return_lane =
823                terminator_return_lane(function, &values, &continuation_returns, continuation);
824            if continuation_returns.get(&continuation.id) != Some(&return_lane) {
825                continuation_returns.insert(continuation.id, return_lane);
826                changed = true;
827            }
828        }
829        if !changed {
830            return CpsReprFunctionAbiAnalysis {
831                entry: function.entry,
832                values,
833                continuation_returns,
834            };
835        }
836    }
837}
838
839fn propagate_terminator_argument_lanes(
840    function: &CpsReprFunction,
841    values: &mut HashMap<CpsValueId, CpsReprAbiLane>,
842    terminator: &CpsTerminator,
843) -> bool {
844    match terminator {
845        CpsTerminator::Continue { target, args } => {
846            let Some(target) = continuation_by_id_opt(function, *target) else {
847                return false;
848            };
849            merge_param_lanes(values, &target.params, args)
850        }
851        CpsTerminator::Perform {
852            effect,
853            payload,
854            resume,
855            handler,
856            blocked,
857        } => {
858            let mut changed = false;
859            if let Some(blocked) = blocked {
860                changed |= merge_abi_lane(values, *blocked, CpsReprAbiLane::ScalarI64);
861            }
862            if let Some(arm) = handler_arm_for_effect(function, *handler, effect)
863                && let Some(entry) = continuation_by_id_opt(function, arm.entry)
864                && let Some(param) = entry.params.first()
865            {
866                let lane = abi_lane(values, *payload);
867                changed |= merge_abi_lane(values, *param, lane);
868            }
869            if let Some(resume) = continuation_by_id_opt(function, *resume)
870                && let Some(param) = resume.params.first()
871            {
872                changed |= merge_abi_lane(values, *param, abi_lane(values, *payload));
873            }
874            changed
875        }
876        CpsTerminator::EffectfulCall { args, resume, .. } => {
877            // Propagate result lane to the resume continuation's parameter.
878            if let Some(resume) = continuation_by_id_opt(function, *resume)
879                && let Some(_param) = resume.params.first()
880            {
881                // Arg lanes flow through the call; result lane is unknown
882                // statically, so just touch them to keep the fixpoint going.
883                let _ = args;
884            }
885            false
886        }
887        CpsTerminator::EffectfulApply { resume, .. }
888        | CpsTerminator::EffectfulForce { resume, .. } => {
889            let _ = continuation_by_id_opt(function, *resume);
890            false
891        }
892        CpsTerminator::Return(_) | CpsTerminator::Branch { .. } => false,
893    }
894}
895
896fn merge_param_lanes(
897    values: &mut HashMap<CpsValueId, CpsReprAbiLane>,
898    params: &[CpsValueId],
899    args: &[CpsValueId],
900) -> bool {
901    let mut changed = false;
902    for (param, arg) in params.iter().zip(args) {
903        changed |= merge_abi_lane(values, *param, abi_lane(values, *arg));
904    }
905    changed
906}
907
908fn analyze_function_values(function: &CpsReprFunction) -> CpsReprFunctionValueAnalysis {
909    let mut values = HashMap::new();
910    let mut continuation_returns = HashMap::new();
911    let thunk_entries = make_thunk_entries(function);
912    for param in &function.params {
913        values.insert(*param, CpsReprValueKind::Plain);
914    }
915    for handler in &function.handlers {
916        for arm in &handler.arms {
917            if let Some(entry) = function
918                .continuations
919                .iter()
920                .find(|continuation| continuation.id == arm.entry)
921            {
922                if let Some(payload) = entry.params.first() {
923                    values.insert(*payload, CpsReprValueKind::Plain);
924                }
925                if let Some(resumption) = entry.params.get(1) {
926                    values.insert(*resumption, CpsReprValueKind::Resumption);
927                }
928            }
929        }
930    }
931
932    loop {
933        let mut changed = false;
934        for continuation in &function.continuations {
935            for param in &continuation.params {
936                values.entry(*param).or_insert(CpsReprValueKind::Unknown);
937            }
938            for slot in &continuation.environment {
939                values
940                    .entry(slot.value)
941                    .or_insert(CpsReprValueKind::Unknown);
942            }
943            for stmt in &continuation.stmts {
944                let kind = match stmt {
945                    CpsStmt::Literal { .. }
946                    | CpsStmt::FreshGuard { .. }
947                    | CpsStmt::PeekGuard { .. }
948                    | CpsStmt::FindGuard { .. }
949                    | CpsStmt::MakeThunk { .. }
950                    | CpsStmt::AddThunkBoundary { .. }
951                    | CpsStmt::MakeClosure { .. }
952                    | CpsStmt::MakeRecursiveClosure { .. }
953                    | CpsStmt::Tuple { .. }
954                    | CpsStmt::Record { .. }
955                    | CpsStmt::RecordWithoutFields { .. }
956                    | CpsStmt::Variant { .. }
957                    | CpsStmt::Select { .. }
958                    | CpsStmt::SelectWithDefault { .. }
959                    | CpsStmt::RecordHasField { .. }
960                    | CpsStmt::TupleGet { .. }
961                    | CpsStmt::VariantTagEq { .. }
962                    | CpsStmt::VariantPayload { .. }
963                    | CpsStmt::Primitive { .. }
964                    | CpsStmt::DirectCall { .. }
965                    | CpsStmt::ApplyClosure { .. } => CpsReprValueKind::Plain,
966                    CpsStmt::ForceThunk { thunk, .. } => thunk_entries
967                        .get(thunk)
968                        .and_then(|entry| continuation_returns.get(entry).copied())
969                        .unwrap_or(CpsReprValueKind::Plain),
970                    CpsStmt::CloneContinuation { source, .. } => value_kind(&values, *source),
971                    CpsStmt::Resume { .. } | CpsStmt::ResumeWithHandler { .. } => {
972                        CpsReprValueKind::Plain
973                    }
974                    CpsStmt::InstallHandler { .. } | CpsStmt::UninstallHandler { .. } => continue,
975                };
976                if let Some(dest) = stmt_dest(stmt) {
977                    if merge_value_kind(&mut values, dest, kind) {
978                        changed = true;
979                    }
980                }
981            }
982            let return_kind = match &continuation.terminator {
983                CpsTerminator::Return(value) => value_kind(&values, *value),
984                CpsTerminator::Continue { target, .. } => continuation_returns
985                    .get(target)
986                    .copied()
987                    .unwrap_or(CpsReprValueKind::Unknown),
988                CpsTerminator::Branch {
989                    then_cont,
990                    else_cont,
991                    ..
992                } => continuation_returns
993                    .get(then_cont)
994                    .copied()
995                    .unwrap_or(CpsReprValueKind::Unknown)
996                    .merge(
997                        continuation_returns
998                            .get(else_cont)
999                            .copied()
1000                            .unwrap_or(CpsReprValueKind::Unknown),
1001                    ),
1002                CpsTerminator::Perform {
1003                    effect, handler, ..
1004                } => handler_arm_for_effect(function, *handler, effect)
1005                    .and_then(|arm| continuation_returns.get(&arm.entry))
1006                    .copied()
1007                    .unwrap_or(CpsReprValueKind::Unknown),
1008                CpsTerminator::EffectfulCall { resume, .. }
1009                | CpsTerminator::EffectfulApply { resume, .. }
1010                | CpsTerminator::EffectfulForce { resume, .. } => continuation_returns
1011                    .get(resume)
1012                    .copied()
1013                    .unwrap_or(CpsReprValueKind::Unknown),
1014            };
1015            if continuation_returns.get(&continuation.id) != Some(&return_kind) {
1016                continuation_returns.insert(continuation.id, return_kind);
1017                changed = true;
1018            }
1019        }
1020        if !changed {
1021            return CpsReprFunctionValueAnalysis {
1022                values,
1023                continuation_returns,
1024            };
1025        }
1026    }
1027}
1028
1029fn literal_lane(literal: &CpsLiteral) -> CpsReprAbiLane {
1030    match literal {
1031        CpsLiteral::Int(_) | CpsLiteral::Bool(_) | CpsLiteral::Unit => CpsReprAbiLane::ScalarI64,
1032        CpsLiteral::Float(_) => CpsReprAbiLane::NativeFloat,
1033        CpsLiteral::String(_) => CpsReprAbiLane::RuntimeValuePtr,
1034    }
1035}
1036
1037fn primitive_result_lane(op: typed_ir::PrimitiveOp) -> CpsReprAbiLane {
1038    use typed_ir::PrimitiveOp;
1039    match op {
1040        PrimitiveOp::BoolNot
1041        | PrimitiveOp::BoolEq
1042        | PrimitiveOp::IntEq
1043        | PrimitiveOp::IntLt
1044        | PrimitiveOp::IntLe
1045        | PrimitiveOp::IntGt
1046        | PrimitiveOp::IntGe
1047        | PrimitiveOp::IntAdd
1048        | PrimitiveOp::IntSub
1049        | PrimitiveOp::IntMul
1050        | PrimitiveOp::IntDiv
1051        | PrimitiveOp::ListLen
1052        | PrimitiveOp::StringLen
1053        | PrimitiveOp::BytesLen
1054        | PrimitiveOp::BytesIndex => CpsReprAbiLane::ScalarI64,
1055        PrimitiveOp::FloatEq
1056        | PrimitiveOp::FloatLt
1057        | PrimitiveOp::FloatLe
1058        | PrimitiveOp::FloatGt
1059        | PrimitiveOp::FloatGe
1060        | PrimitiveOp::StringEq
1061        | PrimitiveOp::BytesEq => CpsReprAbiLane::ScalarI64,
1062        PrimitiveOp::FloatAdd
1063        | PrimitiveOp::FloatSub
1064        | PrimitiveOp::FloatMul
1065        | PrimitiveOp::FloatDiv => CpsReprAbiLane::NativeFloat,
1066        PrimitiveOp::ListEmpty
1067        | PrimitiveOp::ListSingleton
1068        | PrimitiveOp::ListMerge
1069        | PrimitiveOp::ListIndexRange
1070        | PrimitiveOp::ListSplice
1071        | PrimitiveOp::ListIndexRangeRaw
1072        | PrimitiveOp::ListSpliceRaw
1073        | PrimitiveOp::ListViewRaw
1074        | PrimitiveOp::StringIndex
1075        | PrimitiveOp::StringIndexRange
1076        | PrimitiveOp::StringSplice
1077        | PrimitiveOp::StringIndexRangeRaw
1078        | PrimitiveOp::StringSpliceRaw
1079        | PrimitiveOp::StringConcat
1080        | PrimitiveOp::StringToBytes
1081        | PrimitiveOp::BytesConcat
1082        | PrimitiveOp::BytesIndexRange
1083        | PrimitiveOp::BytesToUtf8Raw
1084        | PrimitiveOp::BytesToPath
1085        | PrimitiveOp::PathToBytes
1086        | PrimitiveOp::IntToString
1087        | PrimitiveOp::IntToHex
1088        | PrimitiveOp::IntToUpperHex
1089        | PrimitiveOp::FloatToString
1090        | PrimitiveOp::BoolToString => CpsReprAbiLane::RuntimeValuePtr,
1091        PrimitiveOp::ListIndex => CpsReprAbiLane::Unknown,
1092    }
1093}
1094
1095fn terminator_return_lane(
1096    function: &CpsReprFunction,
1097    values: &HashMap<CpsValueId, CpsReprAbiLane>,
1098    continuation_returns: &HashMap<CpsContinuationId, CpsReprAbiLane>,
1099    continuation: &CpsReprContinuation,
1100) -> CpsReprAbiLane {
1101    match &continuation.terminator {
1102        CpsTerminator::Return(value) => abi_lane(values, *value),
1103        CpsTerminator::Continue { target, .. } => continuation_returns
1104            .get(target)
1105            .copied()
1106            .unwrap_or(CpsReprAbiLane::Unknown),
1107        CpsTerminator::Branch {
1108            then_cont,
1109            else_cont,
1110            ..
1111        } => continuation_returns
1112            .get(then_cont)
1113            .copied()
1114            .unwrap_or(CpsReprAbiLane::Unknown)
1115            .merge(
1116                continuation_returns
1117                    .get(else_cont)
1118                    .copied()
1119                    .unwrap_or(CpsReprAbiLane::Unknown),
1120            ),
1121        CpsTerminator::Perform {
1122            effect,
1123            handler,
1124            resume,
1125            ..
1126        } => {
1127            let handler_return = handler_arm_for_effect(function, *handler, effect)
1128                .and_then(|arm| continuation_returns.get(&arm.entry))
1129                .copied();
1130            handler_return
1131                .or_else(|| {
1132                    host_console_effect_kind(effect)
1133                        .and_then(|_| continuation_returns.get(resume).copied())
1134                })
1135                .unwrap_or(CpsReprAbiLane::Unknown)
1136        }
1137        CpsTerminator::EffectfulCall { resume, .. }
1138        | CpsTerminator::EffectfulApply { resume, .. }
1139        | CpsTerminator::EffectfulForce { resume, .. } => continuation_returns
1140            .get(resume)
1141            .copied()
1142            .unwrap_or(CpsReprAbiLane::Unknown),
1143    }
1144}
1145
1146fn resumption_target_return_lane(
1147    function: &CpsReprFunction,
1148    values: &HashMap<CpsValueId, CpsReprAbiLane>,
1149    continuation_returns: &HashMap<CpsContinuationId, CpsReprAbiLane>,
1150    resumption: CpsValueId,
1151) -> CpsReprAbiLane {
1152    if abi_lane(values, resumption) != CpsReprAbiLane::ResumptionPtr {
1153        return CpsReprAbiLane::Unknown;
1154    }
1155    function
1156        .handlers
1157        .iter()
1158        .flat_map(|handler| handler.arms.iter().map(move |arm| (handler.id, arm)))
1159        .filter(|(_, arm)| {
1160            continuation_by_id_opt(function, arm.entry).and_then(|entry| entry.params.get(1))
1161                == Some(&resumption)
1162        })
1163        .filter_map(|(handler_id, arm)| {
1164            function
1165                .continuations
1166                .iter()
1167                .filter_map(|continuation| match continuation.terminator {
1168                    CpsTerminator::Perform {
1169                        handler: used,
1170                        ref effect,
1171                        resume,
1172                        ..
1173                    } if used == handler_id && effect_matches(&arm.effect, effect) => {
1174                        continuation_returns.get(&resume).copied()
1175                    }
1176                    _ => None,
1177                })
1178                .fold(None, |current, lane| {
1179                    Some(current.map_or(lane, |current: CpsReprAbiLane| current.merge(lane)))
1180                })
1181        })
1182        .fold(None, |current, lane| {
1183            Some(current.map_or(lane, |current: CpsReprAbiLane| current.merge(lane)))
1184        })
1185        .unwrap_or(CpsReprAbiLane::Unknown)
1186}
1187
1188fn handler_arm_for_effect<'a>(
1189    function: &'a CpsReprFunction,
1190    id: CpsHandlerId,
1191    effect: &typed_ir::Path,
1192) -> Option<&'a CpsReprHandlerArm> {
1193    function
1194        .handlers
1195        .iter()
1196        .find(|handler| handler.id == id)?
1197        .arms
1198        .iter()
1199        .find(|arm| effect_matches(&arm.effect, effect))
1200}
1201
1202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1203enum HostConsoleEffect {
1204    OutWrite,
1205    ErrWrite,
1206    WarnWrite,
1207    DieDie,
1208}
1209
1210fn host_console_effect_kind(effect: &typed_ir::Path) -> Option<HostConsoleEffect> {
1211    let [std, module_seg, act_seg, operation] = effect.segments.as_slice() else {
1212        return None;
1213    };
1214    if std.0 != "std" || module_seg.0 != "out" {
1215        return None;
1216    }
1217    match (act_seg.0.as_str(), operation.0.as_str()) {
1218        ("out", "write") => Some(HostConsoleEffect::OutWrite),
1219        ("err", "write") => Some(HostConsoleEffect::ErrWrite),
1220        ("warn", "warn") => Some(HostConsoleEffect::WarnWrite),
1221        ("die", "die") => Some(HostConsoleEffect::DieDie),
1222        _ => None,
1223    }
1224}
1225
1226fn handler_arm_for_effect_in_module<'a>(
1227    module: &'a CpsReprModule,
1228    id: CpsHandlerId,
1229    effect: &typed_ir::Path,
1230) -> Option<(&'a CpsReprHandlerArm, &'a CpsReprFunction)> {
1231    for owner in module.functions.iter().chain(module.roots.iter()) {
1232        if let Some(arm) = handler_arm_for_effect(owner, id, effect) {
1233            return Some((arm, owner));
1234        }
1235    }
1236    None
1237}
1238
1239fn handler_arm_for_stack<'a>(
1240    module: &'a CpsReprModule,
1241    current_function: &'a CpsReprFunction,
1242    stack: &'a [CpsReprHandlerFrame],
1243    effect: &typed_ir::Path,
1244    blocked: Option<u64>,
1245) -> CpsReprEvalResult<(
1246    &'a CpsReprHandlerArm,
1247    &'a CpsReprHandlerFrame,
1248    Vec<CpsReprHandlerFrame>,
1249    &'a CpsReprFunction,
1250)> {
1251    for (index, frame) in stack.iter().enumerate().rev() {
1252        if blocked.is_some_and(|blocked| frame.guard_stack.iter().any(|entry| entry.id == blocked))
1253        {
1254            continue;
1255        }
1256        if let Some((arm, owner)) = handler_arm_for_effect_in_module(module, frame.handler, effect)
1257        {
1258            return Ok((arm, frame, stack[..index].to_vec(), owner));
1259        }
1260    }
1261    Err(CpsReprEvalError::MissingHandler {
1262        function: current_function.name.clone(),
1263        id: stack.last().expect("handler stack is non-empty").handler,
1264    })
1265}
1266
1267fn handler_stack_with_static(
1268    active_handlers: &[CpsReprHandlerFrame],
1269    fallback: CpsHandlerId,
1270    guard_stack: &[CpsReprGuardEntry],
1271) -> Vec<CpsReprHandlerFrame> {
1272    if active_handlers.is_empty() {
1273        vec![CpsReprHandlerFrame {
1274            prompt: fresh_repr_prompt(),
1275            handler: fallback,
1276            guard_stack: guard_stack.to_vec(),
1277            envs: Vec::new(),
1278            escape: REPR_EXIT_RWH_TARGET,
1279            escape_owner_function: String::new(),
1280            return_frame_threshold: 0,
1281            inherited: false,
1282            install_eval_id: REPR_SYNTHETIC_EVAL_ID,
1283        }]
1284    } else {
1285        active_handlers.to_vec()
1286    }
1287}
1288
1289#[allow(dead_code)]
1290fn handler_stack_with_pushed(
1291    active_handlers: &[CpsReprHandlerFrame],
1292    handler: CpsHandlerId,
1293    guard_stack: &[CpsReprGuardEntry],
1294    envs: Vec<CpsReprEvaluatedHandlerEnv>,
1295) -> Vec<CpsReprHandlerFrame> {
1296    let mut stack = active_handlers.to_vec();
1297    stack.push(CpsReprHandlerFrame {
1298        prompt: fresh_repr_prompt(),
1299        handler,
1300        guard_stack: guard_stack.to_vec(),
1301        envs,
1302        escape: REPR_EXIT_RWH_TARGET,
1303        escape_owner_function: String::new(),
1304        return_frame_threshold: 0,
1305        inherited: false,
1306        install_eval_id: REPR_SYNTHETIC_EVAL_ID,
1307    });
1308    stack
1309}
1310
1311fn capture_handler_envs(
1312    function: &CpsReprFunction,
1313    values: &HashMap<CpsValueId, CpsReprRuntimeValue>,
1314    envs: &[CpsHandlerEnv],
1315) -> CpsReprEvalResult<Vec<CpsReprEvaluatedHandlerEnv>> {
1316    envs.iter()
1317        .map(|env| {
1318            let mut values_by_id = Vec::new();
1319            for (index, value) in env.values.iter().enumerate() {
1320                let target = env.targets.get(index).copied().unwrap_or(*value);
1321                values_by_id.push((target, read_value(function, values, *value)?));
1322            }
1323            Ok(CpsReprEvaluatedHandlerEnv {
1324                entry: env.entry,
1325                values: values_by_id,
1326            })
1327        })
1328        .collect()
1329}
1330
1331fn overlay_handler_envs_in_frames_repr(
1332    function: &str,
1333    frames: &mut [CpsReprReturnFrame],
1334    handler: CpsHandlerId,
1335    updates: &[CpsReprEvaluatedHandlerEnv],
1336    remember_latest: bool,
1337) {
1338    for frame in frames {
1339        overlay_handler_envs_in_stack_repr(
1340            function,
1341            &mut frame.active_handlers,
1342            handler,
1343            updates,
1344            remember_latest,
1345        );
1346    }
1347}
1348
1349fn overlay_handler_envs_in_stack_repr(
1350    function: &str,
1351    stack: &mut [CpsReprHandlerFrame],
1352    handler: CpsHandlerId,
1353    updates: &[CpsReprEvaluatedHandlerEnv],
1354    remember_latest: bool,
1355) {
1356    if updates.is_empty() {
1357        return;
1358    }
1359    for frame in stack.iter_mut().filter(|frame| frame.handler == handler) {
1360        overlay_handler_envs_repr(&mut frame.envs, updates);
1361    }
1362    if remember_latest {
1363        remember_latest_repr_handler_envs(handler, updates);
1364    }
1365    push_cps_frame_trace_event(CpsFrameTraceEvent::HandlerEnvOverlay {
1366        layer: CpsFrameTraceLayer::CpsRepr,
1367        function: function.to_string(),
1368        handler: handler.0,
1369        entries: updates.iter().map(|env| env.entry.0).collect(),
1370        values: trace_cps_repr_handler_env_slots(updates),
1371    });
1372}
1373
1374fn overlay_handler_envs_repr(
1375    envs: &mut Vec<CpsReprEvaluatedHandlerEnv>,
1376    updates: &[CpsReprEvaluatedHandlerEnv],
1377) {
1378    for update in updates {
1379        let Some(existing) = envs.iter_mut().find(|env| env.entry == update.entry) else {
1380            envs.push(update.clone());
1381            continue;
1382        };
1383        for (target, value) in &update.values {
1384            if let Some((_, existing_value)) = existing
1385                .values
1386                .iter_mut()
1387                .find(|(existing_target, _)| existing_target == target)
1388            {
1389                *existing_value = value.clone();
1390            } else {
1391                existing.values.push((*target, value.clone()));
1392            }
1393        }
1394    }
1395}
1396
1397fn remember_latest_repr_handler_envs(
1398    handler: CpsHandlerId,
1399    updates: &[CpsReprEvaluatedHandlerEnv],
1400) {
1401    LATEST_REPR_HANDLER_ENVS.with(|latest| {
1402        let mut latest = latest.borrow_mut();
1403        for update in updates {
1404            for (target, value) in &update.values {
1405                if let Some(existing) = latest.iter_mut().find(|existing| {
1406                    existing.handler == handler
1407                        && existing.entry == update.entry
1408                        && existing.target == *target
1409                }) {
1410                    existing.value = value.clone();
1411                } else {
1412                    latest.push(CpsReprLatestHandlerEnv {
1413                        handler,
1414                        entry: update.entry,
1415                        target: *target,
1416                        value: value.clone(),
1417                    });
1418                }
1419            }
1420        }
1421    });
1422}
1423
1424fn latest_repr_handler_env_value(
1425    handler: CpsHandlerId,
1426    entry: CpsContinuationId,
1427    target: CpsValueId,
1428) -> Option<CpsReprRuntimeValue> {
1429    LATEST_REPR_HANDLER_ENVS.with(|latest| {
1430        latest
1431            .borrow()
1432            .iter()
1433            .rev()
1434            .find(|latest| {
1435                latest.handler == handler && latest.entry == entry && latest.target == target
1436            })
1437            .map(|latest| latest.value.clone())
1438    })
1439}
1440
1441fn values_with_handler_env(
1442    function: &str,
1443    mut values: HashMap<CpsValueId, CpsReprRuntimeValue>,
1444    frame: &CpsReprHandlerFrame,
1445    entry: CpsContinuationId,
1446) -> HashMap<CpsValueId, CpsReprRuntimeValue> {
1447    let Some(env) = frame.envs.iter().find(|env| env.entry == entry) else {
1448        return values;
1449    };
1450    let effective_values = env
1451        .values
1452        .iter()
1453        .map(|(id, value)| {
1454            (
1455                *id,
1456                latest_repr_handler_env_value(frame.handler, entry, *id)
1457                    .unwrap_or_else(|| value.clone()),
1458            )
1459        })
1460        .collect::<Vec<_>>();
1461    push_cps_frame_trace_event(CpsFrameTraceEvent::HandlerEnvRead {
1462        layer: CpsFrameTraceLayer::CpsRepr,
1463        function: function.to_string(),
1464        handler: frame.handler.0,
1465        entry: entry.0,
1466        values: trace_cps_repr_handler_env_value_slots(&effective_values),
1467    });
1468    for (id, value) in effective_values {
1469        values.insert(id, value);
1470    }
1471    values
1472}
1473
1474fn trace_cps_repr_handler_env_slots(envs: &[CpsReprEvaluatedHandlerEnv]) -> Vec<CpsFrameTraceSlot> {
1475    envs.iter()
1476        .flat_map(|env| {
1477            env.values.iter().map(|(target, value)| CpsFrameTraceSlot {
1478                target: target.0,
1479                value: summarize_cps_repr_value(value),
1480            })
1481        })
1482        .collect()
1483}
1484
1485fn trace_cps_repr_handler_env_value_slots(
1486    values: &[(CpsValueId, CpsReprRuntimeValue)],
1487) -> Vec<CpsFrameTraceSlot> {
1488    values
1489        .iter()
1490        .map(|(target, value)| CpsFrameTraceSlot {
1491            target: target.0,
1492            value: summarize_cps_repr_value(value),
1493        })
1494        .collect()
1495}
1496
1497fn add_thunk_boundary_repr(
1498    value: CpsReprRuntimeValue,
1499    guard_id: u64,
1500    allowed: typed_ir::Type,
1501    active: bool,
1502) -> CpsReprRuntimeValue {
1503    let CpsReprRuntimeValue::Thunk(mut thunk) = value else {
1504        return value;
1505    };
1506    thunk.blocked.push(CpsReprBlockedEffect {
1507        guard_id,
1508        allowed,
1509        active,
1510    });
1511    CpsReprRuntimeValue::Thunk(thunk)
1512}
1513
1514fn active_blocked_for_thunk_repr(
1515    current: &[CpsReprBlockedEffect],
1516    thunk: &CpsReprThunk,
1517) -> Vec<CpsReprBlockedEffect> {
1518    let mut active = current.to_vec();
1519    active.extend(
1520        thunk
1521            .blocked
1522            .iter()
1523            .filter(|blocked| blocked.active)
1524            .cloned(),
1525    );
1526    active
1527}
1528
1529fn active_blocked_id_repr(effect: &typed_ir::Path, active: &[CpsReprBlockedEffect]) -> Option<u64> {
1530    active
1531        .iter()
1532        .rev()
1533        .find(|blocked| !effect_allowed_by_type_repr(&blocked.allowed, effect))
1534        .map(|blocked| blocked.guard_id)
1535}
1536
1537fn effect_allowed_by_type_repr(allowed: &typed_ir::Type, effect: &typed_ir::Path) -> bool {
1538    match allowed {
1539        typed_ir::Type::Any => true,
1540        typed_ir::Type::Never => false,
1541        typed_ir::Type::Named { path, .. } => effect_path_matches_allowed_repr(path, effect),
1542        typed_ir::Type::Row { items, tail } => {
1543            items
1544                .iter()
1545                .any(|item| effect_allowed_by_type_repr(item, effect))
1546                || matches!(tail.as_ref(), typed_ir::Type::Any)
1547        }
1548        _ => false,
1549    }
1550}
1551
1552fn effect_path_matches_allowed_repr(allowed: &typed_ir::Path, effect: &typed_ir::Path) -> bool {
1553    if effect.segments.starts_with(&allowed.segments) {
1554        return true;
1555    }
1556    if allowed.segments.len() > 1
1557        && effect.segments.len() == allowed.segments.len()
1558        && effect.segments[..effect.segments.len() - 1]
1559            == allowed.segments[..allowed.segments.len() - 1]
1560        && effect_segment_matches_allowed_repr(
1561            &allowed.segments[allowed.segments.len() - 1],
1562            &effect.segments[effect.segments.len() - 1],
1563        )
1564    {
1565        return true;
1566    }
1567    effect
1568        .segments
1569        .iter()
1570        .enumerate()
1571        .skip(1)
1572        .any(|(index, _)| effect.segments[index..].starts_with(&allowed.segments))
1573}
1574
1575fn effect_segment_matches_allowed_repr(allowed: &typed_ir::Name, effect: &typed_ir::Name) -> bool {
1576    allowed == effect
1577        || effect
1578            .0
1579            .strip_suffix("#effect")
1580            .is_some_and(|base| base == allowed.0)
1581}
1582
1583fn effect_matches(expected: &typed_ir::Path, actual: &typed_ir::Path) -> bool {
1584    effect_path_matches_allowed_repr(expected, actual)
1585}
1586
1587fn abi_lane(values: &HashMap<CpsValueId, CpsReprAbiLane>, value: CpsValueId) -> CpsReprAbiLane {
1588    values
1589        .get(&value)
1590        .copied()
1591        .unwrap_or(CpsReprAbiLane::Unknown)
1592}
1593
1594fn merge_abi_lane(
1595    values: &mut HashMap<CpsValueId, CpsReprAbiLane>,
1596    value: CpsValueId,
1597    lane: CpsReprAbiLane,
1598) -> bool {
1599    let merged = values
1600        .get(&value)
1601        .copied()
1602        .map(|current| current.merge(lane))
1603        .unwrap_or(lane);
1604    if values.get(&value) == Some(&merged) {
1605        false
1606    } else {
1607        values.insert(value, merged);
1608        true
1609    }
1610}
1611
1612fn stmt_dest(stmt: &CpsStmt) -> Option<CpsValueId> {
1613    match stmt {
1614        CpsStmt::Literal { dest, .. }
1615        | CpsStmt::FreshGuard { dest, .. }
1616        | CpsStmt::PeekGuard { dest }
1617        | CpsStmt::FindGuard { dest, .. }
1618        | CpsStmt::MakeThunk { dest, .. }
1619        | CpsStmt::AddThunkBoundary { dest, .. }
1620        | CpsStmt::MakeClosure { dest, .. }
1621        | CpsStmt::MakeRecursiveClosure { dest, .. }
1622        | CpsStmt::ForceThunk { dest, .. }
1623        | CpsStmt::Tuple { dest, .. }
1624        | CpsStmt::Record { dest, .. }
1625        | CpsStmt::RecordWithoutFields { dest, .. }
1626        | CpsStmt::Variant { dest, .. }
1627        | CpsStmt::Select { dest, .. }
1628        | CpsStmt::SelectWithDefault { dest, .. }
1629        | CpsStmt::RecordHasField { dest, .. }
1630        | CpsStmt::TupleGet { dest, .. }
1631        | CpsStmt::VariantTagEq { dest, .. }
1632        | CpsStmt::VariantPayload { dest, .. }
1633        | CpsStmt::Primitive { dest, .. }
1634        | CpsStmt::DirectCall { dest, .. }
1635        | CpsStmt::ApplyClosure { dest, .. }
1636        | CpsStmt::CloneContinuation { dest, .. }
1637        | CpsStmt::Resume { dest, .. }
1638        | CpsStmt::ResumeWithHandler { dest, .. } => Some(*dest),
1639        CpsStmt::InstallHandler { .. } | CpsStmt::UninstallHandler { .. } => None,
1640    }
1641}
1642
1643fn value_kind(
1644    values: &HashMap<CpsValueId, CpsReprValueKind>,
1645    value: CpsValueId,
1646) -> CpsReprValueKind {
1647    values
1648        .get(&value)
1649        .copied()
1650        .unwrap_or(CpsReprValueKind::Unknown)
1651}
1652
1653fn merge_value_kind(
1654    values: &mut HashMap<CpsValueId, CpsReprValueKind>,
1655    value: CpsValueId,
1656    kind: CpsReprValueKind,
1657) -> bool {
1658    let merged = values
1659        .get(&value)
1660        .copied()
1661        .map(|current| current.merge(kind))
1662        .unwrap_or(kind);
1663    if values.get(&value) == Some(&merged) {
1664        false
1665    } else {
1666        values.insert(value, merged);
1667        true
1668    }
1669}
1670
1671fn eval_function(
1672    module: &CpsReprModule,
1673    function: &CpsReprFunction,
1674    args: Vec<runtime::VmValue>,
1675) -> CpsReprEvalResult<CpsReprRuntimeValue> {
1676    eval_function_with_context(
1677        module,
1678        function,
1679        args.into_iter().map(CpsReprRuntimeValue::Plain).collect(),
1680        Vec::new(),
1681        Vec::new(),
1682        Vec::new(),
1683        Vec::new(),
1684        0,
1685    )
1686}
1687
1688fn eval_function_with_context(
1689    module: &CpsReprModule,
1690    function: &CpsReprFunction,
1691    args: Vec<CpsReprRuntimeValue>,
1692    active_handlers: Vec<CpsReprHandlerFrame>,
1693    guard_stack: Vec<CpsReprGuardEntry>,
1694    return_frames: Vec<CpsReprReturnFrame>,
1695    active_blocked: Vec<CpsReprBlockedEffect>,
1696    initial_frame_count: usize,
1697) -> CpsReprEvalResult<CpsReprRuntimeValue> {
1698    if function.params.len() != args.len() {
1699        return Err(CpsReprEvalError::FunctionArgumentMismatch {
1700            function: function.name.clone(),
1701            expected: function.params.len(),
1702            actual: args.len(),
1703        });
1704    }
1705    eval_continuations(
1706        module,
1707        function,
1708        function.entry,
1709        args,
1710        HashMap::new(),
1711        active_handlers,
1712        guard_stack,
1713        return_frames,
1714        active_blocked,
1715        initial_frame_count,
1716    )
1717}
1718
1719fn trace_repr_enabled() -> bool {
1720    std::env::var_os("YULANG_CPS_REPR_TRACE_FRAMES").is_some()
1721}
1722
1723fn trace_repr(label: &str, message: impl std::fmt::Display) {
1724    if trace_repr_enabled() {
1725        eprintln!("[cps-repr-trace] {label}: {message}");
1726    }
1727}
1728
1729fn summarize_repr_handler_stack(stack: &[CpsReprHandlerFrame]) -> String {
1730    let items = stack
1731        .iter()
1732        .map(|frame| {
1733            format!(
1734                "(p={},inh={},eval={},owner={},thr={})",
1735                frame.prompt.0,
1736                if frame.inherited { "T" } else { "F" },
1737                frame.install_eval_id.0,
1738                frame.escape_owner_function,
1739                frame.return_frame_threshold
1740            )
1741        })
1742        .collect::<Vec<_>>();
1743    format!("[{}]", items.join(","))
1744}
1745
1746fn summarize_cps_repr_value(value: &CpsReprRuntimeValue) -> String {
1747    match value {
1748        CpsReprRuntimeValue::Plain(value) => format!("Plain({value:?})"),
1749        CpsReprRuntimeValue::Resumption(resumption) => format!(
1750            "Resumption(owner={}, target={:?})",
1751            resumption.owner_function, resumption.target,
1752        ),
1753        CpsReprRuntimeValue::Thunk(thunk) => {
1754            format!(
1755                "Thunk(owner={}, entry={:?})",
1756                thunk.owner_function, thunk.entry
1757            )
1758        }
1759        CpsReprRuntimeValue::Closure(closure) => format!(
1760            "Closure(owner={}, entry={:?}, recursive_self={:?})",
1761            closure.owner_function, closure.entry, closure.recursive_self,
1762        ),
1763        CpsReprRuntimeValue::List(items) => format!("List(len={})", items.len()),
1764        CpsReprRuntimeValue::Tuple(items) => format!("Tuple(len={})", items.len()),
1765        CpsReprRuntimeValue::Record(fields) => format!("Record(len={})", fields.len()),
1766        CpsReprRuntimeValue::Variant { tag, value } => {
1767            format!("Variant(tag={tag:?}, has_value={})", value.is_some())
1768        }
1769        CpsReprRuntimeValue::Aborted(value) => {
1770            format!("Aborted({})", summarize_cps_repr_value(value))
1771        }
1772        CpsReprRuntimeValue::RoutedJump(jump) => format!(
1773            "RoutedJump(owner={}, target={:?}, value={}, threshold={})",
1774            jump.owner_function,
1775            jump.target,
1776            summarize_cps_repr_value(&jump.value),
1777            jump.return_frame_threshold,
1778        ),
1779        CpsReprRuntimeValue::ScopeReturn {
1780            prompt,
1781            target,
1782            value,
1783        } => format!(
1784            "ScopeReturn(prompt={}, target={:?}, value={})",
1785            prompt.0,
1786            target,
1787            summarize_cps_repr_value(value)
1788        ),
1789    }
1790}
1791
1792/// write26: entry point that issues a fresh `CpsReprEvalId`. Mirrors
1793/// `cps_eval::eval_continuations` (which calls `into_inherited` before
1794/// `resume_continuation`).
1795fn eval_continuations(
1796    module: &CpsReprModule,
1797    function: &CpsReprFunction,
1798    entry: CpsContinuationId,
1799    args: Vec<CpsReprRuntimeValue>,
1800    values: HashMap<CpsValueId, CpsReprRuntimeValue>,
1801    active_handlers: Vec<CpsReprHandlerFrame>,
1802    guard_stack: Vec<CpsReprGuardEntry>,
1803    return_frames: Vec<CpsReprReturnFrame>,
1804    active_blocked: Vec<CpsReprBlockedEffect>,
1805    initial_frame_count: usize,
1806) -> CpsReprEvalResult<CpsReprRuntimeValue> {
1807    let current_eval_id = fresh_repr_eval_id();
1808    resume_continuation(
1809        module,
1810        function,
1811        entry,
1812        args,
1813        values,
1814        into_inherited_repr(active_handlers),
1815        guard_stack,
1816        return_frames,
1817        active_blocked,
1818        initial_frame_count,
1819        current_eval_id,
1820    )
1821}
1822
1823/// write26: mark every handler frame as inherited so a fresh eval frame
1824/// does not resolve a `ScopeReturn` against handlers whose install eval
1825/// lives in a parent eval. Mirrors `cps_eval::into_inherited`.
1826fn into_inherited_repr(mut handlers: Vec<CpsReprHandlerFrame>) -> Vec<CpsReprHandlerFrame> {
1827    for frame in &mut handlers {
1828        frame.inherited = true;
1829    }
1830    handlers
1831}
1832
1833/// write26 port of `cps_eval::ScopeReturnAction`.
1834enum ScopeReturnActionRepr {
1835    Value(CpsReprRuntimeValue),
1836    JumpOrExit {
1837        target: CpsContinuationId,
1838        value: CpsReprRuntimeValue,
1839        return_frame_threshold: usize,
1840    },
1841    Propagate(CpsReprRuntimeValue),
1842}
1843
1844/// write26 port of `cps_eval::handle_scope_return`.
1845fn handle_scope_return_repr(
1846    result: CpsReprRuntimeValue,
1847    active_handlers: &mut Vec<CpsReprHandlerFrame>,
1848    _return_frames: &[CpsReprReturnFrame],
1849    current_function: &str,
1850    current_eval_id: CpsReprEvalId,
1851) -> ScopeReturnActionRepr {
1852    match result {
1853        CpsReprRuntimeValue::ScopeReturn {
1854            prompt,
1855            target,
1856            value,
1857        } => {
1858            if let Some(index) = active_handlers.iter().rposition(|frame| {
1859                frame.prompt == prompt && frame.install_eval_id == current_eval_id
1860            }) {
1861                let frame = &active_handlers[index];
1862                let frame_owner_match = target == REPR_EXIT_RWH_TARGET
1863                    || frame.escape_owner_function == current_function;
1864                let truncate_at = frame.return_frame_threshold;
1865                if !frame_owner_match {
1866                    return ScopeReturnActionRepr::Propagate(CpsReprRuntimeValue::ScopeReturn {
1867                        prompt,
1868                        target,
1869                        value,
1870                    });
1871                }
1872                active_handlers.truncate(index);
1873                ScopeReturnActionRepr::JumpOrExit {
1874                    target,
1875                    value: *value,
1876                    return_frame_threshold: truncate_at,
1877                }
1878            } else {
1879                ScopeReturnActionRepr::Propagate(CpsReprRuntimeValue::ScopeReturn {
1880                    prompt,
1881                    target,
1882                    value,
1883                })
1884            }
1885        }
1886        other => ScopeReturnActionRepr::Value(other),
1887    }
1888}
1889
1890/// write26: core eval loop. Does NOT call `into_inherited_repr` and does
1891/// NOT issue a fresh eval id — both responsibilities lie with the caller
1892/// (`eval_continuations` or `continue_return_frames_repr`).
1893fn resume_continuation(
1894    module: &CpsReprModule,
1895    function: &CpsReprFunction,
1896    entry: CpsContinuationId,
1897    mut args: Vec<CpsReprRuntimeValue>,
1898    mut values: HashMap<CpsValueId, CpsReprRuntimeValue>,
1899    active_handlers: Vec<CpsReprHandlerFrame>,
1900    guard_stack: Vec<CpsReprGuardEntry>,
1901    return_frames: Vec<CpsReprReturnFrame>,
1902    active_blocked: Vec<CpsReprBlockedEffect>,
1903    initial_frame_count: usize,
1904    current_eval_id: CpsReprEvalId,
1905) -> CpsReprEvalResult<CpsReprRuntimeValue> {
1906    let mut current = entry;
1907    let mut guard_stack = guard_stack;
1908    let mut active_handlers = active_handlers;
1909    let mut return_frames = return_frames;
1910    let active_blocked = active_blocked;
1911    let mut next_guard_id = guard_stack
1912        .iter()
1913        .map(|entry| entry.id)
1914        .max()
1915        .map_or(0, |id| id + 1);
1916    // write26: dispatch macro mirrors `cps_eval::dispatch_scope_return!`.
1917    macro_rules! dispatch_scope_return_repr {
1918        ($cont:lifetime, $result:expr, $dest:expr) => {{
1919            let result = resolve_routed_jump_repr(module, $result, &return_frames)?;
1920            if matches!(
1921                result,
1922                CpsReprRuntimeValue::Aborted(_) | CpsReprRuntimeValue::RoutedJump(_)
1923            ) {
1924                return Ok(result);
1925            }
1926            match handle_scope_return_repr(
1927                result,
1928                &mut active_handlers,
1929                &return_frames,
1930                &function.name,
1931                current_eval_id,
1932            ) {
1933                ScopeReturnActionRepr::Value(v) => {
1934                    values.insert(*$dest, v);
1935                }
1936                ScopeReturnActionRepr::JumpOrExit { target, value, return_frame_threshold }
1937                    if target == REPR_EXIT_RWH_TARGET =>
1938                {
1939                    if return_frames.len() > return_frame_threshold {
1940                        return_frames.truncate(return_frame_threshold);
1941                    }
1942                    values.insert(*$dest, value);
1943                }
1944                ScopeReturnActionRepr::JumpOrExit { target, value, return_frame_threshold } => {
1945                    if return_frames.len() > return_frame_threshold {
1946                        return_frames.truncate(return_frame_threshold);
1947                    }
1948                    current = target;
1949                    args = vec![value];
1950                    continue $cont;
1951                }
1952                ScopeReturnActionRepr::Propagate(v) => {
1953                    if let Some(routed) =
1954                        try_route_scope_return_through_return_frames_repr(
1955                            module,
1956                            &v,
1957                            &return_frames,
1958                            initial_frame_count,
1959                        )?
1960                    {
1961                        return Ok(routed);
1962                    }
1963                    return Ok(v);
1964                }
1965            }
1966        }};
1967    }
1968    'cont: loop {
1969        let continuation = continuation_by_id(function, current)?;
1970        if continuation.params.len() != args.len() {
1971            return Err(CpsReprEvalError::ContinuationArgumentMismatch {
1972                function: function.name.clone(),
1973                id: continuation.id,
1974                expected: continuation.params.len(),
1975                actual: args.len(),
1976            });
1977        }
1978        for (param, value) in continuation.params.iter().copied().zip(args) {
1979            values.insert(param, value);
1980        }
1981        args = Vec::new();
1982
1983        for stmt in &continuation.stmts {
1984            match stmt {
1985                CpsStmt::Literal { dest, literal } => {
1986                    values.insert(*dest, CpsReprRuntimeValue::Plain(eval_literal(literal)));
1987                }
1988                CpsStmt::FreshGuard { dest, var } => {
1989                    let id = next_guard_id;
1990                    next_guard_id += 1;
1991                    guard_stack.push(CpsReprGuardEntry { var: *var, id });
1992                    values.insert(
1993                        *dest,
1994                        CpsReprRuntimeValue::Plain(runtime::VmValue::EffectId(id)),
1995                    );
1996                }
1997                CpsStmt::PeekGuard { dest } => {
1998                    let id = guard_stack.last().map(|entry| entry.id).ok_or_else(|| {
1999                        CpsReprEvalError::MissingGuard {
2000                            function: function.name.clone(),
2001                        }
2002                    })?;
2003                    values.insert(
2004                        *dest,
2005                        CpsReprRuntimeValue::Plain(runtime::VmValue::EffectId(id)),
2006                    );
2007                }
2008                CpsStmt::FindGuard { dest, guard } => {
2009                    let guard = read_effect_id(function, &values, *guard)?;
2010                    values.insert(
2011                        *dest,
2012                        CpsReprRuntimeValue::Plain(runtime::VmValue::Bool(
2013                            guard_stack.iter().any(|entry| entry.id == guard),
2014                        )),
2015                    );
2016                }
2017                CpsStmt::MakeThunk { dest, entry } => {
2018                    values.insert(
2019                        *dest,
2020                        CpsReprRuntimeValue::Thunk(CpsReprThunk {
2021                            owner_function: function.name.clone(),
2022                            entry: *entry,
2023                            values: values.clone(),
2024                            handlers: active_handlers.clone(),
2025                            guard_stack: guard_stack.clone(),
2026                            blocked: Vec::new(),
2027                        }),
2028                    );
2029                }
2030                CpsStmt::AddThunkBoundary {
2031                    dest,
2032                    thunk,
2033                    guard,
2034                    allowed,
2035                    active,
2036                } => {
2037                    let guard = read_effect_id(function, &values, *guard)?;
2038                    let value = add_thunk_boundary_repr(
2039                        read_value(function, &values, *thunk)?,
2040                        guard,
2041                        allowed.clone(),
2042                        *active,
2043                    );
2044                    values.insert(*dest, value);
2045                }
2046                CpsStmt::MakeClosure { dest, entry } => {
2047                    values.insert(
2048                        *dest,
2049                        CpsReprRuntimeValue::Closure(CpsReprClosure {
2050                            owner_function: function.name.clone(),
2051                            entry: *entry,
2052                            values: values.clone(),
2053                            recursive_self: None,
2054                        }),
2055                    );
2056                }
2057                CpsStmt::MakeRecursiveClosure { dest, entry } => {
2058                    let closure = CpsReprRuntimeValue::Closure(CpsReprClosure {
2059                        owner_function: function.name.clone(),
2060                        entry: *entry,
2061                        values: values.clone(),
2062                        recursive_self: Some(*dest),
2063                    });
2064                    values.insert(*dest, closure);
2065                }
2066                CpsStmt::ForceThunk { dest, thunk } => {
2067                    // write26 port: loop through nested Thunks, mirroring
2068                    // cps_eval. A function whose body returns a Thunk-wrapped
2069                    // body (e.g. `my work() = { ... }` with effect-typed
2070                    // return) needs to peel through Thunks until a
2071                    // non-Thunk value (or ScopeReturn) is produced.
2072                    let mut result = read_value(function, &values, *thunk)?;
2073                    loop {
2074                        match result {
2075                            CpsReprRuntimeValue::Thunk(thunk) => {
2076                                let handlers = if !active_handlers.is_empty() {
2077                                    active_handlers.clone()
2078                                } else {
2079                                    thunk.handlers.clone()
2080                                };
2081                                let guards = if !guard_stack.is_empty() {
2082                                    guard_stack.clone()
2083                                } else {
2084                                    thunk.guard_stack.clone()
2085                                };
2086                                let owner = function_by_name_repr(module, &thunk.owner_function)?;
2087                                let inherited = return_frames.len();
2088                                result = eval_continuations(
2089                                    module,
2090                                    owner,
2091                                    thunk.entry,
2092                                    Vec::new(),
2093                                    thunk.values.clone(),
2094                                    handlers,
2095                                    guards,
2096                                    return_frames.clone(),
2097                                    active_blocked_for_thunk_repr(&active_blocked, &thunk),
2098                                    inherited,
2099                                )?;
2100                                if matches!(result, CpsReprRuntimeValue::ScopeReturn { .. }) {
2101                                    break;
2102                                }
2103                            }
2104                            _ => break,
2105                        }
2106                    }
2107                    if matches!(result, CpsReprRuntimeValue::Aborted(_)) {
2108                        return Ok(result);
2109                    }
2110                    dispatch_scope_return_repr!('cont, result, dest);
2111                }
2112                CpsStmt::InstallHandler {
2113                    handler,
2114                    envs,
2115                    value,
2116                    escape,
2117                } => {
2118                    let envs = capture_handler_envs(function, &values, envs)?;
2119                    let prompt = fresh_repr_prompt();
2120                    let threshold = return_frames.len();
2121                    active_handlers.push(CpsReprHandlerFrame {
2122                        prompt,
2123                        handler: *handler,
2124                        guard_stack: guard_stack.clone(),
2125                        envs,
2126                        escape: *escape,
2127                        escape_owner_function: function.name.clone(),
2128                        return_frame_threshold: threshold,
2129                        inherited: false,
2130                        install_eval_id: current_eval_id,
2131                    });
2132                    return_frames.push(CpsReprReturnFrame {
2133                        prompt_exit: Some(CpsReprPromptExitFrame { prompt }),
2134                        owner_function: function.name.clone(),
2135                        continuation: *value,
2136                        values: Rc::new(values.clone()),
2137                        active_handlers: active_handlers.clone(),
2138                        guard_stack: guard_stack.clone(),
2139                        active_blocked: active_blocked.clone(),
2140                        owner_initial_frame_count: initial_frame_count,
2141                        owner_eval_id: current_eval_id,
2142                    });
2143                    trace_repr(
2144                        "InstallHandler",
2145                        format!(
2146                            "fn={} eval={} cont={:?} handler={:?} prompt={} value={:?} escape={:?} threshold={} handlers.now={}",
2147                            function.name,
2148                            current_eval_id.0,
2149                            continuation.id,
2150                            handler,
2151                            prompt.0,
2152                            value,
2153                            escape,
2154                            threshold,
2155                            active_handlers.len()
2156                        ),
2157                    );
2158                }
2159                CpsStmt::UninstallHandler { handler } => {
2160                    if let Some(pos) = active_handlers
2161                        .iter()
2162                        .rposition(|frame| frame.handler == *handler)
2163                    {
2164                        active_handlers.remove(pos);
2165                    }
2166                }
2167                CpsStmt::Tuple { dest, items } => {
2168                    let items = items
2169                        .iter()
2170                        .map(|id| read_value(function, &values, *id))
2171                        .collect::<CpsReprEvalResult<Vec<_>>>()?;
2172                    values.insert(*dest, CpsReprRuntimeValue::Tuple(items));
2173                }
2174                CpsStmt::Record { dest, base, fields } => {
2175                    let mut record = match base {
2176                        Some(base) => match read_value(function, &values, *base)? {
2177                            CpsReprRuntimeValue::Record(fields) => fields,
2178                            CpsReprRuntimeValue::Plain(runtime::VmValue::Record(fields)) => fields
2179                                .into_iter()
2180                                .map(|(name, value)| (name, CpsReprRuntimeValue::Plain(value)))
2181                                .collect(),
2182                            value => {
2183                                return Err(CpsReprEvalError::ExpectedRecord {
2184                                    function: function.name.clone(),
2185                                    value: into_plain_value(function, *base, value)?,
2186                                });
2187                            }
2188                        },
2189                        None => BTreeMap::new(),
2190                    };
2191                    for field in fields {
2192                        record.insert(
2193                            field.name.clone(),
2194                            read_value(function, &values, field.value)?,
2195                        );
2196                    }
2197                    values.insert(*dest, CpsReprRuntimeValue::Record(record));
2198                }
2199                CpsStmt::RecordWithoutFields { dest, base, fields } => {
2200                    let mut record = match read_value(function, &values, *base)? {
2201                        CpsReprRuntimeValue::Record(fields) => fields,
2202                        CpsReprRuntimeValue::Plain(runtime::VmValue::Record(fields)) => fields
2203                            .into_iter()
2204                            .map(|(name, value)| (name, CpsReprRuntimeValue::Plain(value)))
2205                            .collect(),
2206                        value => {
2207                            return Err(CpsReprEvalError::ExpectedRecord {
2208                                function: function.name.clone(),
2209                                value: into_plain_value(function, *base, value)?,
2210                            });
2211                        }
2212                    };
2213                    for field in fields {
2214                        record.remove(field);
2215                    }
2216                    values.insert(*dest, CpsReprRuntimeValue::Record(record));
2217                }
2218                CpsStmt::Variant { dest, tag, value } => {
2219                    let value = value
2220                        .map(|id| read_value(function, &values, id))
2221                        .transpose()?
2222                        .map(Box::new);
2223                    values.insert(
2224                        *dest,
2225                        CpsReprRuntimeValue::Variant {
2226                            tag: tag.clone(),
2227                            value,
2228                        },
2229                    );
2230                }
2231                CpsStmt::Select { dest, base, field } => {
2232                    let value = match read_value(function, &values, *base)? {
2233                        CpsReprRuntimeValue::Record(fields) => fields
2234                            .get(field)
2235                            .cloned()
2236                            .ok_or_else(|| CpsReprEvalError::MissingRecordField {
2237                                function: function.name.clone(),
2238                                field: field.clone(),
2239                            })?,
2240                        CpsReprRuntimeValue::Plain(runtime::VmValue::Record(fields)) => fields
2241                            .get(field)
2242                            .cloned()
2243                            .map(CpsReprRuntimeValue::Plain)
2244                            .ok_or_else(|| CpsReprEvalError::MissingRecordField {
2245                                function: function.name.clone(),
2246                                field: field.clone(),
2247                            })?,
2248                        value => {
2249                            return Err(CpsReprEvalError::ExpectedRecord {
2250                                function: function.name.clone(),
2251                                value: into_plain_value(function, *base, value)?,
2252                            });
2253                        }
2254                    };
2255                    values.insert(*dest, value);
2256                }
2257                CpsStmt::TupleGet { dest, tuple, index } => {
2258                    let value = match read_value(function, &values, *tuple)? {
2259                        CpsReprRuntimeValue::Tuple(items) => items
2260                            .get(*index)
2261                            .cloned()
2262                            .ok_or_else(|| CpsReprEvalError::MissingRecordField {
2263                                function: function.name.clone(),
2264                                field: typed_ir::Name(index.to_string()),
2265                            })?,
2266                        CpsReprRuntimeValue::Plain(runtime::VmValue::Tuple(items)) => {
2267                            cps_repr_value_from_vm(items.get(*index).cloned().ok_or_else(|| {
2268                                CpsReprEvalError::MissingRecordField {
2269                                    function: function.name.clone(),
2270                                    field: typed_ir::Name(index.to_string()),
2271                                }
2272                            })?)
2273                        }
2274                        other => other,
2275                    };
2276                    values.insert(*dest, value);
2277                }
2278                CpsStmt::SelectWithDefault {
2279                    dest,
2280                    base,
2281                    field,
2282                    default,
2283                } => {
2284                    let default = read_value(function, &values, *default)?;
2285                    let value = match read_value(function, &values, *base)? {
2286                        CpsReprRuntimeValue::Record(fields) => fields.get(field).cloned(),
2287                        CpsReprRuntimeValue::Plain(runtime::VmValue::Record(fields)) => {
2288                            fields.get(field).cloned().map(CpsReprRuntimeValue::Plain)
2289                        }
2290                        value => {
2291                            return Err(CpsReprEvalError::ExpectedRecord {
2292                                function: function.name.clone(),
2293                                value: into_plain_value(function, *base, value)?,
2294                            });
2295                        }
2296                    }
2297                    .unwrap_or(default);
2298                    values.insert(*dest, value);
2299                }
2300                CpsStmt::RecordHasField { dest, base, field } => {
2301                    let has_field = match read_value(function, &values, *base)? {
2302                        CpsReprRuntimeValue::Record(fields) => fields.contains_key(field),
2303                        CpsReprRuntimeValue::Plain(runtime::VmValue::Record(fields)) => {
2304                            fields.contains_key(field)
2305                        }
2306                        value => {
2307                            return Err(CpsReprEvalError::ExpectedRecord {
2308                                function: function.name.clone(),
2309                                value: into_plain_value(function, *base, value)?,
2310                            });
2311                        }
2312                    };
2313                    values.insert(
2314                        *dest,
2315                        CpsReprRuntimeValue::Plain(runtime::VmValue::Bool(has_field)),
2316                    );
2317                }
2318                CpsStmt::VariantTagEq { dest, variant, tag } => {
2319                    let matches = match read_value(function, &values, *variant)? {
2320                        CpsReprRuntimeValue::Variant { tag: actual, .. } => actual == *tag,
2321                        CpsReprRuntimeValue::Plain(runtime::VmValue::Variant {
2322                            tag: actual,
2323                            ..
2324                        }) => actual == *tag,
2325                        _ => false,
2326                    };
2327                    values.insert(
2328                        *dest,
2329                        CpsReprRuntimeValue::Plain(runtime::VmValue::Bool(matches)),
2330                    );
2331                }
2332                CpsStmt::VariantPayload { dest, variant } => {
2333                    let value = match read_value(function, &values, *variant)? {
2334                        CpsReprRuntimeValue::Variant {
2335                            value: Some(value), ..
2336                        } => *value,
2337                        CpsReprRuntimeValue::Variant { value: None, .. } => {
2338                            CpsReprRuntimeValue::Plain(runtime::VmValue::Unit)
2339                        }
2340                        CpsReprRuntimeValue::Plain(runtime::VmValue::Variant {
2341                            value: Some(value),
2342                            ..
2343                        }) => cps_repr_value_from_vm(*value),
2344                        CpsReprRuntimeValue::Plain(runtime::VmValue::Variant {
2345                            value: None,
2346                            ..
2347                        }) => CpsReprRuntimeValue::Plain(runtime::VmValue::Unit),
2348                        other => other,
2349                    };
2350                    values.insert(*dest, value);
2351                }
2352                CpsStmt::Primitive { dest, op, args } => {
2353                    let args = args
2354                        .iter()
2355                        .map(|id| read_value(function, &values, *id))
2356                        .collect::<CpsReprEvalResult<Vec<_>>>()?;
2357                    let result = eval_cps_repr_primitive(*op, args)?;
2358                    values.insert(*dest, result);
2359                }
2360                CpsStmt::DirectCall { dest, target, args } => {
2361                    let target_function = function_by_name_repr(module, target)?;
2362                    let args = args
2363                        .iter()
2364                        .map(|id| read_value(function, &values, *id))
2365                        .collect::<CpsReprEvalResult<Vec<_>>>()?;
2366                    let inherited = return_frames.len();
2367                    let result = eval_function_with_context(
2368                        module,
2369                        target_function,
2370                        args,
2371                        active_handlers.clone(),
2372                        guard_stack.clone(),
2373                        return_frames.clone(),
2374                        active_blocked.clone(),
2375                        inherited,
2376                    )?;
2377                    if matches!(result, CpsReprRuntimeValue::Aborted(_)) {
2378                        return Ok(result);
2379                    }
2380                    dispatch_scope_return_repr!('cont, result, dest);
2381                }
2382                CpsStmt::ApplyClosure { dest, closure, arg } => {
2383                    let callable = read_value(function, &values, *closure)?;
2384                    let result = match callable {
2385                        CpsReprRuntimeValue::Closure(closure) => {
2386                            let arg = read_value(function, &values, *arg)?;
2387                            let owner = function_by_name_repr(module, &closure.owner_function)?;
2388                            let mut closure_values = closure.values.clone();
2389                            if let Some(self_id) = closure.recursive_self {
2390                                closure_values
2391                                    .insert(self_id, CpsReprRuntimeValue::Closure(closure.clone()));
2392                            }
2393                            let inherited = return_frames.len();
2394                            eval_continuations(
2395                                module,
2396                                owner,
2397                                closure.entry,
2398                                vec![arg],
2399                                closure_values,
2400                                active_handlers.clone(),
2401                                guard_stack.clone(),
2402                                return_frames.clone(),
2403                                active_blocked.clone(),
2404                                inherited,
2405                            )?
2406                        }
2407                        CpsReprRuntimeValue::Resumption(resumption) => {
2408                            let arg = read_plain_value(function, &values, *arg)?;
2409                            let owner = function_by_name_repr(module, &resumption.owner_function)?;
2410                            let anchor = resumption.handled_anchor;
2411                            let resumed_handlers = merge_resumption_handlers_repr(
2412                                &resumption.handlers,
2413                                &active_handlers,
2414                                anchor,
2415                            );
2416                            let adjusted_frames = merge_extras_into_frames_repr(
2417                                &resumption.return_frames,
2418                                &active_handlers,
2419                                anchor,
2420                            );
2421                            eval_continuations(
2422                                module,
2423                                owner,
2424                                resumption.target,
2425                                vec![CpsReprRuntimeValue::Plain(arg)],
2426                                resumption.values.clone(),
2427                                resumed_handlers,
2428                                resumption.guard_stack.clone(),
2429                                adjusted_frames,
2430                                resumption.active_blocked.clone(),
2431                                0,
2432                            )?
2433                        }
2434                        _ => {
2435                            return Err(CpsReprEvalError::ExpectedPlainValue {
2436                                function: function.name.clone(),
2437                                id: *closure,
2438                            });
2439                        }
2440                    };
2441                    if matches!(result, CpsReprRuntimeValue::Aborted(_)) {
2442                        return Ok(result);
2443                    }
2444                    dispatch_scope_return_repr!('cont, result, dest);
2445                }
2446                CpsStmt::CloneContinuation { dest, source } => {
2447                    let value = read_value(function, &values, *source)?;
2448                    values.insert(*dest, value);
2449                }
2450                CpsStmt::Resume {
2451                    dest,
2452                    resumption,
2453                    arg,
2454                } => {
2455                    let resumption = read_resumption(function, &values, *resumption)?;
2456                    let arg = read_plain_value(function, &values, *arg)?;
2457                    let owner = function_by_name_repr(module, &resumption.owner_function)?;
2458                    let anchor = resumption.handled_anchor;
2459                    let resumed_handlers = merge_resumption_handlers_repr(
2460                        &resumption.handlers,
2461                        &active_handlers,
2462                        anchor,
2463                    );
2464                    let adjusted_frames = merge_extras_into_frames_repr(
2465                        &resumption.return_frames,
2466                        &active_handlers,
2467                        anchor,
2468                    );
2469                    let result = eval_continuations(
2470                        module,
2471                        owner,
2472                        resumption.target,
2473                        vec![CpsReprRuntimeValue::Plain(arg)],
2474                        resumption.values.clone(),
2475                        resumed_handlers,
2476                        resumption.guard_stack.clone(),
2477                        adjusted_frames,
2478                        resumption.active_blocked.clone(),
2479                        0,
2480                    )?;
2481                    if matches!(result, CpsReprRuntimeValue::Aborted(_)) {
2482                        return Ok(result);
2483                    }
2484                    dispatch_scope_return_repr!('cont, result, dest);
2485                }
2486                CpsStmt::ResumeWithHandler {
2487                    dest,
2488                    resumption,
2489                    arg,
2490                    handler,
2491                    envs,
2492                } => {
2493                    let resumption = read_resumption(function, &values, *resumption)?;
2494                    let arg = read_plain_value(function, &values, *arg)?;
2495                    let updates_existing_handler_env =
2496                        envs.iter().any(|env| !env.targets.is_empty());
2497                    let envs = capture_handler_envs(function, &values, envs)?;
2498                    let owner = function_by_name_repr(module, &resumption.owner_function)?;
2499                    let rebase_existing_handler_env = updates_existing_handler_env
2500                        && resumption
2501                            .handlers
2502                            .iter()
2503                            .any(|frame| frame.handler == *handler);
2504                    overlay_handler_envs_in_stack_repr(
2505                        &function.name,
2506                        &mut active_handlers,
2507                        *handler,
2508                        &envs,
2509                        true,
2510                    );
2511                    // write26: mirror cps_eval's RWH pattern. Push the RWH
2512                    // frame to local active_handlers with current_eval_id,
2513                    // construct inner_handlers = captured + [RWH inherited],
2514                    // and use the dispatch macro on result.
2515                    let pushed_prompt = fresh_repr_prompt();
2516                    if !rebase_existing_handler_env {
2517                        active_handlers.push(CpsReprHandlerFrame {
2518                            prompt: pushed_prompt,
2519                            handler: *handler,
2520                            guard_stack: guard_stack.clone(),
2521                            envs: envs.clone(),
2522                            escape: REPR_EXIT_RWH_TARGET,
2523                            escape_owner_function: function.name.clone(),
2524                            return_frame_threshold: return_frames.len(),
2525                            inherited: false,
2526                            install_eval_id: current_eval_id,
2527                        });
2528                    }
2529                    let inner_handlers = {
2530                        let mut stack = resumption.handlers.clone();
2531                        overlay_handler_envs_in_stack_repr(
2532                            &function.name,
2533                            &mut stack,
2534                            *handler,
2535                            &envs,
2536                            false,
2537                        );
2538                        if !rebase_existing_handler_env {
2539                            let mut owned = active_handlers
2540                                .last()
2541                                .cloned()
2542                                .expect("just pushed RWH frame");
2543                            owned.inherited = true;
2544                            stack.push(owned);
2545                        }
2546                        stack
2547                    };
2548                    let pushed_extra = if rebase_existing_handler_env {
2549                        Vec::new()
2550                    } else {
2551                        active_handlers
2552                            .iter()
2553                            .filter(|frame| frame.prompt == pushed_prompt)
2554                            .cloned()
2555                            .collect::<Vec<_>>()
2556                    };
2557                    let mut captured_frames = resumption.return_frames.clone();
2558                    overlay_handler_envs_in_frames_repr(
2559                        &function.name,
2560                        &mut captured_frames,
2561                        *handler,
2562                        &envs,
2563                        false,
2564                    );
2565                    let adjusted_frames =
2566                        append_resume_with_handler_frames_repr(&captured_frames, &pushed_extra);
2567                    let adjusted_frames = if rebase_existing_handler_env {
2568                        own_captured_return_frames_repr(adjusted_frames)
2569                    } else {
2570                        adjusted_frames
2571                    };
2572                    trace_repr(
2573                        "ResumeHandlerMerge",
2574                        format!(
2575                            "site=ResumeWithHandler(rebased) fn={} eval={} pushed_prompt={} captured={} pushed_extra={} inner={}",
2576                            function.name,
2577                            current_eval_id.0,
2578                            pushed_prompt.0,
2579                            summarize_repr_handler_stack(&resumption.handlers),
2580                            summarize_repr_handler_stack(&pushed_extra),
2581                            summarize_repr_handler_stack(&inner_handlers)
2582                        ),
2583                    );
2584                    let result = eval_continuations(
2585                        module,
2586                        owner,
2587                        resumption.target,
2588                        vec![CpsReprRuntimeValue::Plain(arg)],
2589                        resumption.values.clone(),
2590                        inner_handlers,
2591                        resumption.guard_stack.clone(),
2592                        adjusted_frames,
2593                        resumption.active_blocked.clone(),
2594                        0,
2595                    )?;
2596                    if matches!(result, CpsReprRuntimeValue::Aborted(_)) {
2597                        return Ok(result);
2598                    }
2599                    dispatch_scope_return_repr!('cont, result, dest);
2600                    // Pop the RWH frame in the value-flow path.
2601                    if let Some(pos) = active_handlers
2602                        .iter()
2603                        .rposition(|f| f.prompt == pushed_prompt)
2604                    {
2605                        active_handlers.truncate(pos);
2606                    }
2607                }
2608            }
2609        }
2610
2611        match &continuation.terminator {
2612            CpsTerminator::Return(value) => {
2613                let v = read_value(function, &values, *value)?;
2614                trace_repr(
2615                    "Return",
2616                    format!(
2617                        "fn={} cont={:?} return_frames.len={} initial={}",
2618                        function.name,
2619                        continuation.id,
2620                        return_frames.len(),
2621                        initial_frame_count
2622                    ),
2623                );
2624                if return_frames.len() <= initial_frame_count {
2625                    return Ok(v);
2626                }
2627                // write26 port of write25 pre-force v2.
2628                if let CpsReprRuntimeValue::Thunk(thunk) = &v {
2629                    let top_index = return_frames.len() - 1;
2630                    let top_frame = &return_frames[top_index];
2631                    if return_frame_immediately_forces_param_repr(module, top_frame)?
2632                        && top_index >= initial_frame_count
2633                    {
2634                        let top_frame = top_frame.clone();
2635                        let forced = force_returned_thunk_before_frame_consumption_repr(
2636                            module,
2637                            thunk.clone(),
2638                            &top_frame,
2639                            return_frames.clone(),
2640                            initial_frame_count,
2641                        )?;
2642                        if matches!(forced, CpsReprRuntimeValue::ScopeReturn { .. }) {
2643                            return Ok(forced);
2644                        }
2645                        return continue_return_frames_repr(module, forced, &return_frames, &[]);
2646                    }
2647                }
2648                return continue_return_frames_repr(module, v, &return_frames, &[]);
2649            }
2650            CpsTerminator::Continue { target, args: next } => {
2651                args = next
2652                    .iter()
2653                    .map(|id| read_value(function, &values, *id))
2654                    .collect::<CpsReprEvalResult<Vec<_>>>()?;
2655                current = *target;
2656            }
2657            CpsTerminator::Branch {
2658                cond,
2659                then_cont,
2660                else_cont,
2661            } => {
2662                let cond = read_plain_value(function, &values, *cond)?;
2663                current = if bool_value(typed_ir::PrimitiveOp::BoolNot, &cond)? {
2664                    *then_cont
2665                } else {
2666                    *else_cont
2667                };
2668            }
2669            CpsTerminator::Perform {
2670                effect,
2671                payload,
2672                resume,
2673                handler,
2674                blocked,
2675            } => {
2676                let payload = read_plain_value(function, &values, *payload)?;
2677                trace_repr(
2678                    "Perform",
2679                    format!(
2680                        "fn={} eval={} cont={:?} effect={:?} return_frames.len={} initial={} active_handlers={}",
2681                        function.name,
2682                        current_eval_id.0,
2683                        continuation.id,
2684                        effect,
2685                        return_frames.len(),
2686                        initial_frame_count,
2687                        summarize_repr_handler_stack(&active_handlers)
2688                    ),
2689                );
2690                let blocked = blocked
2691                    .map(|blocked| read_effect_id(function, &values, blocked))
2692                    .transpose()?
2693                    .or_else(|| active_blocked_id_repr(effect, &active_blocked));
2694                let handler_stack =
2695                    handler_stack_with_static(&active_handlers, *handler, &guard_stack);
2696                let (handler_arm, frame, handler_body_stack, handler_owner) =
2697                    handler_arm_for_stack(module, function, &handler_stack, effect, blocked)?;
2698                trace_repr(
2699                    "PerformHandlerSearch",
2700                    format!(
2701                        "fn={} eval={} effect={:?} stack={} matched_prompt={} matched_install_eval={} matched_owner={}",
2702                        function.name,
2703                        current_eval_id.0,
2704                        effect,
2705                        summarize_repr_handler_stack(&handler_stack),
2706                        frame.prompt.0,
2707                        frame.install_eval_id.0,
2708                        frame.escape_owner_function
2709                    ),
2710                );
2711                let handler_values = values_with_handler_env(
2712                    &handler_owner.name,
2713                    HashMap::new(),
2714                    frame,
2715                    handler_arm.entry,
2716                );
2717                let frame_prompt = frame.prompt;
2718                let frame_escape = frame.escape;
2719                // write26: a synthetic frame (install_eval=SYNTHETIC) is a
2720                // virtual fallback created when no real handler is in scope.
2721                // Even if it ended up in `active_handlers` (via being
2722                // captured in a prior resumption.handlers), it has no real
2723                // install scope to dispatch to. Treat the arm body result as
2724                // this eval frame's value, like the no-handler case.
2725                let frame_in_active = active_handlers.iter().any(|f| f.prompt == frame_prompt)
2726                    && frame.install_eval_id != REPR_SYNTHETIC_EVAL_ID;
2727                let handled_anchor = if frame_in_active {
2728                    Some(CpsReprHandlerAnchor {
2729                        prompt: frame.prompt,
2730                        install_eval_id: frame.install_eval_id,
2731                    })
2732                } else {
2733                    None
2734                };
2735                let (resumption_handlers, resumption_return_frames) = if frame_in_active {
2736                    let captured = capture_continuation_inside_prompt_repr(
2737                        &handler_stack,
2738                        &return_frames,
2739                        frame,
2740                    );
2741                    (captured.handlers, captured.return_frames)
2742                } else {
2743                    (handler_stack.clone(), return_frames.clone())
2744                };
2745                let resumption = CpsReprRuntimeValue::Resumption(CpsReprResumption {
2746                    owner_function: function.name.clone(),
2747                    target: *resume,
2748                    values: values.clone(),
2749                    handlers: resumption_handlers,
2750                    guard_stack: guard_stack.clone(),
2751                    active_blocked: active_blocked.clone(),
2752                    return_frames: resumption_return_frames,
2753                    handled_anchor,
2754                });
2755                let result = eval_continuations(
2756                    module,
2757                    handler_owner,
2758                    handler_arm.entry,
2759                    vec![CpsReprRuntimeValue::Plain(payload), resumption],
2760                    handler_values,
2761                    handler_body_stack,
2762                    guard_stack.clone(),
2763                    Vec::new(),
2764                    active_blocked.clone(),
2765                    0,
2766                )?;
2767                if !frame_in_active {
2768                    // Synthetic fallback frame: no installed handler, so the
2769                    // arm's result is the value of *this* eval frame.
2770                    return Ok(result);
2771                }
2772                let arm_already_reached_escape = handler_arm_continues_to_installed_escape_repr(
2773                    handler_owner,
2774                    frame.handler,
2775                    handler_arm.entry,
2776                    frame_escape,
2777                );
2778                if arm_already_reached_escape
2779                    && !matches!(result, CpsReprRuntimeValue::ScopeReturn { .. })
2780                    && frame.install_eval_id == current_eval_id
2781                {
2782                    let mut frames = return_frames.clone();
2783                    if frames.len() > frame.return_frame_threshold {
2784                        frames.truncate(frame.return_frame_threshold);
2785                    }
2786                    return continue_return_frames_repr(module, result, &frames, &[]);
2787                }
2788                if !matches!(result, CpsReprRuntimeValue::ScopeReturn { .. })
2789                    && frame.install_eval_id != current_eval_id
2790                    && handler_arm_uses_resume_with_handler_repr(
2791                        handler_owner,
2792                        frame.handler,
2793                        handler_arm.entry,
2794                    )
2795                {
2796                    return Ok(result);
2797                }
2798                // write26: wrap arm body's natural Return as ScopeReturn so
2799                // handle_scope_return_repr can route to H_sub.escape /
2800                // walk-based propagation.
2801                let scope_return = match result {
2802                    CpsReprRuntimeValue::ScopeReturn { .. } => result,
2803                    CpsReprRuntimeValue::Aborted(inner) => {
2804                        // Legacy Aborted: treat as a generic non-local
2805                        // return targeted at the current Perform's matched
2806                        // handler scope.
2807                        CpsReprRuntimeValue::ScopeReturn {
2808                            prompt: frame_prompt,
2809                            target: frame_escape,
2810                            value: inner,
2811                        }
2812                    }
2813                    other => CpsReprRuntimeValue::ScopeReturn {
2814                        prompt: frame_prompt,
2815                        target: frame_escape,
2816                        value: Box::new(other),
2817                    },
2818                };
2819                match handle_scope_return_repr(
2820                    scope_return,
2821                    &mut active_handlers,
2822                    &return_frames,
2823                    &function.name,
2824                    current_eval_id,
2825                ) {
2826                    ScopeReturnActionRepr::Value(v) => {
2827                        return Ok(v);
2828                    }
2829                    ScopeReturnActionRepr::Propagate(v) => {
2830                        if let Some(routed) = try_route_scope_return_through_return_frames_repr(
2831                            module,
2832                            &v,
2833                            &return_frames,
2834                            initial_frame_count,
2835                        )? {
2836                            return Ok(routed);
2837                        }
2838                        return Ok(v);
2839                    }
2840                    ScopeReturnActionRepr::JumpOrExit {
2841                        target,
2842                        value,
2843                        return_frame_threshold,
2844                    } => {
2845                        if return_frames.len() > return_frame_threshold {
2846                            return_frames.truncate(return_frame_threshold);
2847                        }
2848                        if target == REPR_EXIT_RWH_TARGET {
2849                            return Ok(value);
2850                        }
2851                        current = target;
2852                        args = vec![value];
2853                        continue 'cont;
2854                    }
2855                }
2856            }
2857            CpsTerminator::EffectfulCall {
2858                target,
2859                args: arg_ids,
2860                resume,
2861            } => {
2862                let target_function = function_by_name_repr(module, target)?;
2863                let call_args = arg_ids
2864                    .iter()
2865                    .map(|id| read_value(function, &values, *id))
2866                    .collect::<CpsReprEvalResult<Vec<_>>>()?;
2867                let pre_push_count = return_frames.len();
2868                let frame = CpsReprReturnFrame {
2869                    prompt_exit: None,
2870                    owner_function: function.name.clone(),
2871                    continuation: *resume,
2872                    values: Rc::new(values.clone()),
2873                    active_handlers: active_handlers.clone(),
2874                    guard_stack: guard_stack.clone(),
2875                    active_blocked: active_blocked.clone(),
2876                    owner_initial_frame_count: initial_frame_count,
2877                    owner_eval_id: current_eval_id,
2878                };
2879                let mut new_frames = return_frames.clone();
2880                new_frames.push(frame);
2881                return eval_function_with_context(
2882                    module,
2883                    target_function,
2884                    call_args,
2885                    active_handlers.clone(),
2886                    guard_stack.clone(),
2887                    new_frames,
2888                    active_blocked.clone(),
2889                    pre_push_count,
2890                );
2891            }
2892            CpsTerminator::EffectfulForce { thunk, resume } => {
2893                let value = read_value(function, &values, *thunk)?;
2894                match value {
2895                    CpsReprRuntimeValue::Thunk(thunk) => {
2896                        let pre_push_count = return_frames.len();
2897                        let frame = CpsReprReturnFrame {
2898                            prompt_exit: None,
2899                            owner_function: function.name.clone(),
2900                            continuation: *resume,
2901                            values: Rc::new(values.clone()),
2902                            active_handlers: active_handlers.clone(),
2903                            guard_stack: guard_stack.clone(),
2904                            active_blocked: active_blocked.clone(),
2905                            owner_initial_frame_count: initial_frame_count,
2906                            owner_eval_id: current_eval_id,
2907                        };
2908                        let mut new_frames = return_frames.clone();
2909                        new_frames.push(frame);
2910                        let owner = function_by_name_repr(module, &thunk.owner_function)?;
2911                        let handlers = if !active_handlers.is_empty() {
2912                            active_handlers.clone()
2913                        } else {
2914                            thunk.handlers.clone()
2915                        };
2916                        let guards = if !guard_stack.is_empty() {
2917                            guard_stack.clone()
2918                        } else {
2919                            thunk.guard_stack.clone()
2920                        };
2921                        return eval_continuations(
2922                            module,
2923                            owner,
2924                            thunk.entry,
2925                            Vec::new(),
2926                            thunk.values.clone(),
2927                            handlers,
2928                            guards,
2929                            new_frames,
2930                            active_blocked_for_thunk_repr(&active_blocked, &thunk),
2931                            pre_push_count,
2932                        );
2933                    }
2934                    other => {
2935                        current = *resume;
2936                        args = vec![other];
2937                        continue;
2938                    }
2939                }
2940            }
2941            CpsTerminator::EffectfulApply {
2942                closure,
2943                arg,
2944                resume,
2945            } => {
2946                let callable = read_value(function, &values, *closure)?;
2947                let pre_push_count = return_frames.len();
2948                let frame = CpsReprReturnFrame {
2949                    prompt_exit: None,
2950                    owner_function: function.name.clone(),
2951                    continuation: *resume,
2952                    values: Rc::new(values.clone()),
2953                    active_handlers: active_handlers.clone(),
2954                    guard_stack: guard_stack.clone(),
2955                    active_blocked: active_blocked.clone(),
2956                    owner_initial_frame_count: initial_frame_count,
2957                    owner_eval_id: current_eval_id,
2958                };
2959                let mut new_frames = return_frames.clone();
2960                new_frames.push(frame);
2961                match callable {
2962                    CpsReprRuntimeValue::Closure(closure) => {
2963                        let arg = read_value(function, &values, *arg)?;
2964                        let owner = function_by_name_repr(module, &closure.owner_function)?;
2965                        let mut closure_values = closure.values.clone();
2966                        if let Some(self_id) = closure.recursive_self {
2967                            closure_values
2968                                .insert(self_id, CpsReprRuntimeValue::Closure(closure.clone()));
2969                        }
2970                        return eval_continuations(
2971                            module,
2972                            owner,
2973                            closure.entry,
2974                            vec![arg],
2975                            closure_values,
2976                            active_handlers.clone(),
2977                            guard_stack.clone(),
2978                            new_frames,
2979                            active_blocked.clone(),
2980                            pre_push_count,
2981                        );
2982                    }
2983                    CpsReprRuntimeValue::Resumption(resumption) => {
2984                        let arg = read_plain_value(function, &values, *arg)?;
2985                        let owner = function_by_name_repr(module, &resumption.owner_function)?;
2986                        let anchor = resumption.handled_anchor;
2987                        let resumed_handlers = merge_resumption_handlers_repr(
2988                            &resumption.handlers,
2989                            &active_handlers,
2990                            anchor,
2991                        );
2992                        let adjusted_res = merge_extras_into_frames_repr(
2993                            &resumption.return_frames,
2994                            &active_handlers,
2995                            anchor,
2996                        );
2997                        let mut combined_frames = new_frames;
2998                        combined_frames.extend(adjusted_res);
2999                        return eval_continuations(
3000                            module,
3001                            owner,
3002                            resumption.target,
3003                            vec![CpsReprRuntimeValue::Plain(arg)],
3004                            resumption.values.clone(),
3005                            resumed_handlers,
3006                            resumption.guard_stack.clone(),
3007                            combined_frames,
3008                            resumption.active_blocked.clone(),
3009                            0,
3010                        );
3011                    }
3012                    _ => {
3013                        return Err(CpsReprEvalError::ExpectedPlainValue {
3014                            function: function.name.clone(),
3015                            id: *closure,
3016                        });
3017                    }
3018                }
3019            }
3020        }
3021    }
3022}
3023
3024/// write26: equality on (prompt, install_eval_id) — same as
3025/// `cps_eval::same_handler_frame`.
3026fn same_handler_frame_repr(a: &CpsReprHandlerFrame, b: &CpsReprHandlerFrame) -> bool {
3027    a.prompt == b.prompt && a.install_eval_id == b.install_eval_id
3028}
3029
3030fn capture_continuation_inside_prompt_repr(
3031    handlers: &[CpsReprHandlerFrame],
3032    return_frames: &[CpsReprReturnFrame],
3033    handled: &CpsReprHandlerFrame,
3034) -> CapturedPromptContinuationRepr {
3035    let start = captured_prompt_frame_start_repr(return_frames, handled);
3036    let return_frames = return_frames[start..]
3037        .iter()
3038        .cloned()
3039        .map(|frame| rebase_captured_return_frame_repr(frame, start, handled))
3040        .collect();
3041    CapturedPromptContinuationRepr {
3042        handlers: handlers
3043            .iter()
3044            .cloned()
3045            .map(|handler| rebase_captured_handler_frame_repr(handler, start, handled))
3046            .collect(),
3047        return_frames: own_captured_return_frames_repr(return_frames),
3048    }
3049}
3050
3051fn captured_prompt_frame_start_repr(
3052    return_frames: &[CpsReprReturnFrame],
3053    handled: &CpsReprHandlerFrame,
3054) -> usize {
3055    let start = return_frames
3056        .iter()
3057        .rposition(|frame| {
3058            frame
3059                .prompt_exit
3060                .as_ref()
3061                .is_some_and(|exit| exit.prompt == handled.prompt)
3062        })
3063        .map(|index| index + 1)
3064        // If the marker is absent, this captured slice is already running
3065        // under an inherited prompt. Keep the whole slice; the handler
3066        // threshold may point at a replay-time post frame that still belongs
3067        // to this continuation.
3068        .unwrap_or(0)
3069        .min(return_frames.len());
3070    start
3071}
3072
3073fn rebase_captured_return_frame_repr(
3074    mut frame: CpsReprReturnFrame,
3075    dropped_frames: usize,
3076    handled: &CpsReprHandlerFrame,
3077) -> CpsReprReturnFrame {
3078    frame.owner_initial_frame_count = frame
3079        .owner_initial_frame_count
3080        .saturating_sub(dropped_frames);
3081    frame.active_handlers = frame
3082        .active_handlers
3083        .into_iter()
3084        .map(|handler| rebase_captured_handler_frame_repr(handler, dropped_frames, handled))
3085        .collect();
3086    frame
3087}
3088
3089fn rebase_captured_handler_frame_repr(
3090    mut handler: CpsReprHandlerFrame,
3091    dropped_frames: usize,
3092    handled: &CpsReprHandlerFrame,
3093) -> CpsReprHandlerFrame {
3094    if handler.install_eval_id.0 >= handled.install_eval_id.0 {
3095        handler.return_frame_threshold = handler
3096            .return_frame_threshold
3097            .saturating_sub(dropped_frames);
3098    }
3099    handler
3100}
3101
3102/// write26 port of `cps_eval::merge_resumption_handlers`. Place
3103/// resume-site siblings between the captured prefix through the anchor
3104/// handler and the captured inner tail.
3105fn merge_resumption_handlers_repr(
3106    captured: &[CpsReprHandlerFrame],
3107    current: &[CpsReprHandlerFrame],
3108    anchor: Option<CpsReprHandlerAnchor>,
3109) -> Vec<CpsReprHandlerFrame> {
3110    let is_anchor = |frame: &CpsReprHandlerFrame, anchor: CpsReprHandlerAnchor| {
3111        frame.prompt == anchor.prompt && frame.install_eval_id == anchor.install_eval_id
3112    };
3113    if let Some(anchor) = anchor {
3114        if let Some(anchor_index) = captured.iter().position(|f| is_anchor(f, anchor)) {
3115            let mut merged = Vec::with_capacity(captured.len() + current.len());
3116            merged.extend(captured[..=anchor_index].iter().cloned());
3117            for frame in current {
3118                let in_prefix = merged.iter().any(|m| same_handler_frame_repr(m, frame));
3119                let in_tail = captured[anchor_index + 1..]
3120                    .iter()
3121                    .any(|c| same_handler_frame_repr(c, frame));
3122                if !in_prefix && !in_tail {
3123                    merged.push(frame.clone());
3124                }
3125            }
3126            merged.extend(captured[anchor_index + 1..].iter().cloned());
3127            return merged;
3128        }
3129    }
3130    // Shared-prefix fallback.
3131    let mut shared = 0;
3132    while shared < captured.len()
3133        && shared < current.len()
3134        && same_handler_frame_repr(&captured[shared], &current[shared])
3135    {
3136        shared += 1;
3137    }
3138    let mut merged = Vec::with_capacity(captured.len() + current.len());
3139    merged.extend(captured[..shared].iter().cloned());
3140    for frame in &current[shared..] {
3141        if !captured.iter().any(|c| same_handler_frame_repr(c, frame)) {
3142            merged.push(frame.clone());
3143        }
3144    }
3145    merged.extend(captured[shared..].iter().cloned());
3146    merged
3147}
3148
3149fn append_resume_with_handler_frames_repr(
3150    frames: &[CpsReprReturnFrame],
3151    extra: &[CpsReprHandlerFrame],
3152) -> Vec<CpsReprReturnFrame> {
3153    if extra.is_empty() {
3154        return frames.to_vec();
3155    }
3156    frames
3157        .iter()
3158        .map(|frame| {
3159            let mut adjusted = frame.clone();
3160            for handler in extra {
3161                if !adjusted
3162                    .active_handlers
3163                    .iter()
3164                    .any(|existing| existing.prompt == handler.prompt)
3165                {
3166                    adjusted.active_handlers.push(handler.clone());
3167                }
3168            }
3169            adjusted
3170        })
3171        .collect()
3172}
3173
3174/// write26 port of `cps_eval::merge_extras_into_frames`.
3175fn merge_extras_into_frames_repr(
3176    frames: &[CpsReprReturnFrame],
3177    current: &[CpsReprHandlerFrame],
3178    anchor: Option<CpsReprHandlerAnchor>,
3179) -> Vec<CpsReprReturnFrame> {
3180    frames
3181        .iter()
3182        .map(|frame| {
3183            let merged = merge_resumption_handlers_repr(&frame.active_handlers, current, anchor);
3184            let mut adjusted = frame.clone();
3185            adjusted.active_handlers = merged;
3186            adjusted
3187        })
3188        .collect()
3189}
3190
3191/// Repr port of `cps_eval::own_captured_return_frames`.
3192fn own_captured_return_frames_repr(mut frames: Vec<CpsReprReturnFrame>) -> Vec<CpsReprReturnFrame> {
3193    for frame in &mut frames {
3194        frame.owner_initial_frame_count = 0;
3195    }
3196    frames
3197}
3198
3199/// write26 port of `cps_eval::continue_return_frames`.
3200fn continue_return_frames_repr(
3201    module: &CpsReprModule,
3202    value: CpsReprRuntimeValue,
3203    frames: &[CpsReprReturnFrame],
3204    extra_handlers: &[CpsReprHandlerFrame],
3205) -> CpsReprEvalResult<CpsReprRuntimeValue> {
3206    if frames.is_empty() {
3207        return Ok(value);
3208    }
3209    if matches!(value, CpsReprRuntimeValue::ScopeReturn { .. })
3210        || matches!(value, CpsReprRuntimeValue::Aborted(_))
3211        || matches!(value, CpsReprRuntimeValue::RoutedJump(_))
3212    {
3213        return Ok(value);
3214    }
3215    let (frame, rest) = frames.split_last().expect("non-empty");
3216    let function = function_by_name_repr(module, &frame.owner_function)?;
3217    let mut combined = frame.active_handlers.clone();
3218    for extra in extra_handlers {
3219        if !combined.iter().any(|f| f.prompt == extra.prompt) {
3220            combined.push(extra.clone());
3221        }
3222    }
3223    let owner_initial = frame.owner_initial_frame_count.min(rest.len());
3224    resume_continuation(
3225        module,
3226        function,
3227        frame.continuation,
3228        vec![value],
3229        frame.values.as_ref().clone(),
3230        combined,
3231        frame.guard_stack.clone(),
3232        rest.to_vec(),
3233        frame.active_blocked.clone(),
3234        owner_initial,
3235        frame.owner_eval_id,
3236    )
3237}
3238
3239/// write26 port of `cps_eval::return_frame_immediately_forces_param`.
3240fn return_frame_immediately_forces_param_repr(
3241    module: &CpsReprModule,
3242    frame: &CpsReprReturnFrame,
3243) -> CpsReprEvalResult<bool> {
3244    let function = function_by_name_repr(module, &frame.owner_function)?;
3245    let Some(continuation) = function
3246        .continuations
3247        .iter()
3248        .find(|c| c.id == frame.continuation)
3249    else {
3250        return Ok(false);
3251    };
3252    let Some(&first_param) = continuation.params.first() else {
3253        return Ok(false);
3254    };
3255    Ok(matches!(
3256        continuation.stmts.first(),
3257        Some(CpsStmt::ForceThunk { thunk, .. }) if *thunk == first_param
3258    ))
3259}
3260
3261fn force_returned_thunk_before_frame_consumption_repr(
3262    module: &CpsReprModule,
3263    mut thunk: CpsReprThunk,
3264    top_frame: &CpsReprReturnFrame,
3265    return_frames: Vec<CpsReprReturnFrame>,
3266    initial_frame_count: usize,
3267) -> CpsReprEvalResult<CpsReprRuntimeValue> {
3268    loop {
3269        let owner = function_by_name_repr(module, &thunk.owner_function)?;
3270        let result = resume_continuation(
3271            module,
3272            owner,
3273            thunk.entry,
3274            Vec::new(),
3275            thunk.values.clone(),
3276            top_frame.active_handlers.clone(),
3277            top_frame.guard_stack.clone(),
3278            return_frames.clone(),
3279            active_blocked_for_thunk_repr(&top_frame.active_blocked, &thunk),
3280            initial_frame_count,
3281            top_frame.owner_eval_id,
3282        )?;
3283        match result {
3284            CpsReprRuntimeValue::Thunk(next) => {
3285                thunk = next;
3286                continue;
3287            }
3288            other => return Ok(other),
3289        }
3290    }
3291}
3292
3293/// write26 port of `cps_eval::try_route_scope_return_through_return_frames`.
3294fn try_route_scope_return_through_return_frames_repr(
3295    module: &CpsReprModule,
3296    scope_return: &CpsReprRuntimeValue,
3297    return_frames: &[CpsReprReturnFrame],
3298    initial_frame_count: usize,
3299) -> CpsReprEvalResult<Option<CpsReprRuntimeValue>> {
3300    let CpsReprRuntimeValue::ScopeReturn {
3301        prompt,
3302        target,
3303        value,
3304    } = scope_return
3305    else {
3306        return Ok(None);
3307    };
3308    let prompt = *prompt;
3309    let target = *target;
3310    if target == REPR_EXIT_RWH_TARGET {
3311        return Ok(None);
3312    }
3313    if return_frames.is_empty() {
3314        return Ok(None);
3315    }
3316    for frame_index in (0..return_frames.len()).rev() {
3317        let frame = &return_frames[frame_index];
3318        let frame_eval_id = frame.owner_eval_id;
3319        let frame_owner = &frame.owner_function;
3320        let Some(handler_index) = frame.active_handlers.iter().rposition(|handler| {
3321            handler.prompt == prompt && handler.install_eval_id == frame_eval_id
3322        }) else {
3323            continue;
3324        };
3325        let matched_handler = frame.active_handlers[handler_index].clone();
3326        if matched_handler.escape_owner_function != *frame_owner {
3327            continue;
3328        }
3329        let mut post_handlers = frame.active_handlers.clone();
3330        post_handlers.truncate(handler_index);
3331        let mut rest_frames: Vec<CpsReprReturnFrame> = return_frames[..frame_index].to_vec();
3332        let truncate_at = matched_handler.return_frame_threshold;
3333        if rest_frames.len() > truncate_at {
3334            rest_frames.truncate(truncate_at);
3335        }
3336        let owner_initial = frame.owner_initial_frame_count.min(rest_frames.len());
3337        let routed_jump = CpsReprRoutedJump {
3338            value: value.clone(),
3339            return_frame_threshold: truncate_at,
3340            owner_function: frame.owner_function.clone(),
3341            target: matched_handler.escape,
3342            values: frame.values.clone(),
3343            active_handlers: post_handlers,
3344            guard_stack: frame.guard_stack.clone(),
3345            return_frames: rest_frames,
3346            active_blocked: frame.active_blocked.clone(),
3347            initial_frame_count: owner_initial,
3348            eval_id: frame.owner_eval_id,
3349        };
3350        // The target lives in an inherited caller activation. Keep this as a
3351        // jump command even when outer handlers remain; collapsing it to a
3352        // plain value would feed the handler result into the skipped caller's
3353        // normal post-call continuation.
3354        if frame_index < initial_frame_count {
3355            return Ok(Some(CpsReprRuntimeValue::RoutedJump(Box::new(routed_jump))));
3356        }
3357        let owner = function_by_name_repr(module, &routed_jump.owner_function)?;
3358        let result = resume_continuation(
3359            module,
3360            owner,
3361            routed_jump.target,
3362            vec![*value.clone()],
3363            routed_jump.values.as_ref().clone(),
3364            routed_jump.active_handlers,
3365            routed_jump.guard_stack,
3366            routed_jump.return_frames,
3367            routed_jump.active_blocked,
3368            routed_jump.initial_frame_count,
3369            routed_jump.eval_id,
3370        )?;
3371        return Ok(Some(result));
3372    }
3373    Ok(None)
3374}
3375
3376fn resolve_routed_jump_repr(
3377    module: &CpsReprModule,
3378    value: CpsReprRuntimeValue,
3379    current_return_frames: &[CpsReprReturnFrame],
3380) -> CpsReprEvalResult<CpsReprRuntimeValue> {
3381    let CpsReprRuntimeValue::RoutedJump(jump) = value else {
3382        return Ok(value);
3383    };
3384    if current_return_frames.len() > jump.return_frame_threshold {
3385        return Ok(CpsReprRuntimeValue::RoutedJump(jump));
3386    }
3387    let owner = function_by_name_repr(module, &jump.owner_function)?;
3388    resume_continuation(
3389        module,
3390        owner,
3391        jump.target,
3392        vec![*jump.value],
3393        jump.values.as_ref().clone(),
3394        jump.active_handlers,
3395        jump.guard_stack,
3396        jump.return_frames,
3397        jump.active_blocked,
3398        jump.initial_frame_count,
3399        jump.eval_id,
3400    )
3401}
3402
3403fn continuation_by_id(
3404    function: &CpsReprFunction,
3405    id: CpsContinuationId,
3406) -> CpsReprEvalResult<&CpsReprContinuation> {
3407    function
3408        .continuations
3409        .iter()
3410        .find(|continuation| continuation.id == id)
3411        .ok_or_else(|| CpsReprEvalError::MissingContinuation {
3412            function: function.name.clone(),
3413            id,
3414        })
3415}
3416
3417fn handler_arm_continues_to_installed_escape_repr(
3418    function: &CpsReprFunction,
3419    handler: CpsHandlerId,
3420    entry: CpsContinuationId,
3421    escape: CpsContinuationId,
3422) -> bool {
3423    let escape_is_installed_for_handler = function.continuations.iter().any(|continuation| {
3424        continuation.stmts.iter().any(|stmt| {
3425            matches!(
3426                stmt,
3427                CpsStmt::InstallHandler {
3428                    handler: id,
3429                    escape: installed_escape,
3430                    ..
3431                } if *id == handler && *installed_escape == escape
3432            )
3433        })
3434    });
3435    if !escape_is_installed_for_handler {
3436        return false;
3437    }
3438    handler_arm_continue_chain_reaches_escape_repr(function, handler, entry, escape)
3439}
3440
3441fn handler_arm_continue_chain_reaches_escape_repr(
3442    function: &CpsReprFunction,
3443    handler: CpsHandlerId,
3444    entry: CpsContinuationId,
3445    escape: CpsContinuationId,
3446) -> bool {
3447    let mut current = entry;
3448    let mut saw_uninstall = false;
3449    let mut visited = HashSet::new();
3450    while visited.insert(current) {
3451        let Some(continuation) = function
3452            .continuations
3453            .iter()
3454            .find(|continuation| continuation.id == current)
3455        else {
3456            return false;
3457        };
3458        saw_uninstall |= continuation.stmts.iter().any(
3459            |stmt| matches!(stmt, CpsStmt::UninstallHandler { handler: id } if *id == handler),
3460        );
3461        let CpsTerminator::Continue { target, .. } = &continuation.terminator else {
3462            return saw_uninstall && current == escape;
3463        };
3464        if *target == escape {
3465            return saw_uninstall;
3466        }
3467        current = *target;
3468    }
3469    false
3470}
3471
3472fn handler_arm_uses_resume_with_handler_repr(
3473    function: &CpsReprFunction,
3474    handler: CpsHandlerId,
3475    entry: CpsContinuationId,
3476) -> bool {
3477    let mut current = entry;
3478    let mut visited = HashSet::new();
3479    while visited.insert(current) {
3480        let Some(continuation) = function
3481            .continuations
3482            .iter()
3483            .find(|continuation| continuation.id == current)
3484        else {
3485            return false;
3486        };
3487        if continuation.stmts.iter().any(
3488            |stmt| matches!(stmt, CpsStmt::ResumeWithHandler { handler: id, .. } if *id == handler),
3489        ) {
3490            return true;
3491        }
3492        let CpsTerminator::Continue { target, .. } = &continuation.terminator else {
3493            return false;
3494        };
3495        current = *target;
3496    }
3497    false
3498}
3499
3500fn continuation_by_id_opt(
3501    function: &CpsReprFunction,
3502    id: CpsContinuationId,
3503) -> Option<&CpsReprContinuation> {
3504    function
3505        .continuations
3506        .iter()
3507        .find(|continuation| continuation.id == id)
3508}
3509
3510fn read_value(
3511    function: &CpsReprFunction,
3512    values: &HashMap<CpsValueId, CpsReprRuntimeValue>,
3513    id: CpsValueId,
3514) -> CpsReprEvalResult<CpsReprRuntimeValue> {
3515    values
3516        .get(&id)
3517        .cloned()
3518        .ok_or_else(|| CpsReprEvalError::MissingValue {
3519            function: function.name.clone(),
3520            id,
3521        })
3522}
3523
3524fn read_plain_value(
3525    function: &CpsReprFunction,
3526    values: &HashMap<CpsValueId, CpsReprRuntimeValue>,
3527    id: CpsValueId,
3528) -> CpsReprEvalResult<runtime::VmValue> {
3529    into_plain_value(function, id, read_value(function, values, id)?)
3530}
3531
3532fn read_effect_id(
3533    function: &CpsReprFunction,
3534    values: &HashMap<CpsValueId, CpsReprRuntimeValue>,
3535    id: CpsValueId,
3536) -> CpsReprEvalResult<u64> {
3537    match read_plain_value(function, values, id)? {
3538        runtime::VmValue::EffectId(value_id) => Ok(value_id),
3539        value => Err(CpsReprEvalError::ExpectedGuard {
3540            function: function.name.clone(),
3541            id,
3542            value,
3543        }),
3544    }
3545}
3546
3547fn read_resumption(
3548    function: &CpsReprFunction,
3549    values: &HashMap<CpsValueId, CpsReprRuntimeValue>,
3550    id: CpsValueId,
3551) -> CpsReprEvalResult<CpsReprResumption> {
3552    match read_value(function, values, id)? {
3553        CpsReprRuntimeValue::Resumption(resumption) => Ok(resumption),
3554        _ => Err(CpsReprEvalError::ExpectedResumption {
3555            function: function.name.clone(),
3556            id,
3557        }),
3558    }
3559}
3560
3561fn into_plain_value(
3562    function: &CpsReprFunction,
3563    id: CpsValueId,
3564    value: CpsReprRuntimeValue,
3565) -> CpsReprEvalResult<runtime::VmValue> {
3566    cps_repr_value_to_vm(value).ok_or_else(|| CpsReprEvalError::ExpectedPlainValue {
3567        function: function.name.clone(),
3568        id,
3569    })
3570}
3571
3572#[derive(Debug, Clone, PartialEq)]
3573enum CpsReprRuntimeValue {
3574    Plain(runtime::VmValue),
3575    Resumption(CpsReprResumption),
3576    Thunk(CpsReprThunk),
3577    Closure(CpsReprClosure),
3578    /// First-class CPS containers whose elements may themselves be
3579    /// resumptions, thunks, or closures. Mirrors `CpsRuntimeValue`
3580    /// in cps_eval and supports `std::undet.once`'s `list<resumption>`
3581    /// queue and `(k, queue)` tuple pattern.
3582    List(Rc<Vec<CpsReprRuntimeValue>>),
3583    Tuple(Vec<CpsReprRuntimeValue>),
3584    Record(BTreeMap<typed_ir::Name, CpsReprRuntimeValue>),
3585    Variant {
3586        tag: typed_ir::Name,
3587        value: Option<Box<CpsReprRuntimeValue>>,
3588    },
3589    /// Carries a value produced by a handler arm body's non-local return.
3590    /// Propagated by every internal call site so the eval_function boundary
3591    /// can unwrap it.
3592    ///
3593    /// Deprecated as of write26: kept only for backward-compatibility paths
3594    /// that have not yet been migrated to `ScopeReturn`. New code should
3595    /// emit and route `ScopeReturn` instead.
3596    #[allow(dead_code)]
3597    Aborted(Box<CpsReprRuntimeValue>),
3598    /// Frame-walk ScopeReturn escape that belongs to an outer live eval
3599    /// frame. Bubbles until that eval frontier, then runs as value flow.
3600    RoutedJump(Box<CpsReprRoutedJump>),
3601    /// write26: prompt-targeted non-local return, mirrors
3602    /// `CpsRuntimeValue::ScopeReturn` in cps_eval. Generated by a
3603    /// `Perform`'s arm body completion and routed by
3604    /// `handle_scope_return_repr` to the matching handler scope.
3605    ScopeReturn {
3606        prompt: CpsReprPromptId,
3607        target: CpsContinuationId,
3608        value: Box<CpsReprRuntimeValue>,
3609    },
3610}
3611
3612#[derive(Debug, Clone, PartialEq)]
3613struct CpsReprRoutedJump {
3614    value: Box<CpsReprRuntimeValue>,
3615    return_frame_threshold: usize,
3616    owner_function: String,
3617    target: CpsContinuationId,
3618    values: Rc<HashMap<CpsValueId, CpsReprRuntimeValue>>,
3619    active_handlers: Vec<CpsReprHandlerFrame>,
3620    guard_stack: Vec<CpsReprGuardEntry>,
3621    return_frames: Vec<CpsReprReturnFrame>,
3622    active_blocked: Vec<CpsReprBlockedEffect>,
3623    initial_frame_count: usize,
3624    eval_id: CpsReprEvalId,
3625}
3626
3627/// Dynamic prompt id, fresh per `InstallHandler` execution. Mirrors
3628/// `cps_eval::CpsPromptId`.
3629#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3630struct CpsReprPromptId(u64);
3631
3632thread_local! {
3633    static REPR_PROMPT_COUNTER: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
3634}
3635
3636fn fresh_repr_prompt() -> CpsReprPromptId {
3637    REPR_PROMPT_COUNTER.with(|cell| {
3638        let id = cell.get();
3639        cell.set(id + 1);
3640        CpsReprPromptId(id)
3641    })
3642}
3643
3644/// Identity of an eval frame instance. Mirrors `cps_eval::CpsEvalId`.
3645#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3646struct CpsReprEvalId(u64);
3647
3648thread_local! {
3649    static REPR_EVAL_ID_COUNTER: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
3650}
3651
3652fn fresh_repr_eval_id() -> CpsReprEvalId {
3653    REPR_EVAL_ID_COUNTER.with(|cell| {
3654        let id = cell.get();
3655        cell.set(id + 1);
3656        CpsReprEvalId(id)
3657    })
3658}
3659
3660/// Synthetic-fallback handler frame sentinel. Mirrors
3661/// `cps_eval::SYNTHETIC_EVAL_ID`.
3662const REPR_SYNTHETIC_EVAL_ID: CpsReprEvalId = CpsReprEvalId(u64::MAX);
3663
3664/// Sentinel `target` for `ResumeWithHandler`-installed frames. Mirrors
3665/// `cps_eval::EXIT_RWH_TARGET`.
3666const REPR_EXIT_RWH_TARGET: CpsContinuationId = CpsContinuationId(usize::MAX);
3667
3668/// Anchor used by `merge_resumption_handlers_repr` to position
3669/// resume-site siblings between captured outer and inner handlers.
3670/// Mirrors `cps_eval::CpsHandlerAnchor`.
3671#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3672struct CpsReprHandlerAnchor {
3673    prompt: CpsReprPromptId,
3674    install_eval_id: CpsReprEvalId,
3675}
3676
3677#[derive(Debug, Clone, PartialEq)]
3678struct CpsReprResumption {
3679    owner_function: String,
3680    target: CpsContinuationId,
3681    values: HashMap<CpsValueId, CpsReprRuntimeValue>,
3682    handlers: Vec<CpsReprHandlerFrame>,
3683    guard_stack: Vec<CpsReprGuardEntry>,
3684    active_blocked: Vec<CpsReprBlockedEffect>,
3685    /// write26: stack of suspended caller continuations captured at
3686    /// `Perform` time. Mirrors `CpsResumption::return_frames`.
3687    return_frames: Vec<CpsReprReturnFrame>,
3688    /// write26: anchor recorded at `Perform` time so resume-site merge
3689    /// can place siblings at the correct stack depth.
3690    handled_anchor: Option<CpsReprHandlerAnchor>,
3691}
3692
3693struct CapturedPromptContinuationRepr {
3694    handlers: Vec<CpsReprHandlerFrame>,
3695    return_frames: Vec<CpsReprReturnFrame>,
3696}
3697
3698#[derive(Debug, Clone, PartialEq)]
3699struct CpsReprThunk {
3700    owner_function: String,
3701    entry: CpsContinuationId,
3702    values: HashMap<CpsValueId, CpsReprRuntimeValue>,
3703    handlers: Vec<CpsReprHandlerFrame>,
3704    guard_stack: Vec<CpsReprGuardEntry>,
3705    blocked: Vec<CpsReprBlockedEffect>,
3706}
3707
3708#[derive(Debug, Clone, PartialEq)]
3709struct CpsReprBlockedEffect {
3710    guard_id: u64,
3711    allowed: typed_ir::Type,
3712    active: bool,
3713}
3714
3715#[derive(Debug, Clone, PartialEq)]
3716struct CpsReprClosure {
3717    owner_function: String,
3718    entry: CpsContinuationId,
3719    values: HashMap<CpsValueId, CpsReprRuntimeValue>,
3720    recursive_self: Option<CpsValueId>,
3721}
3722
3723#[derive(Debug, Clone, PartialEq)]
3724struct CpsReprHandlerFrame {
3725    /// write26: dynamic prompt id, fresh per scope install.
3726    prompt: CpsReprPromptId,
3727    handler: CpsHandlerId,
3728    guard_stack: Vec<CpsReprGuardEntry>,
3729    envs: Vec<CpsReprEvaluatedHandlerEnv>,
3730    /// write26: escape continuation reached when the handler scope
3731    /// completes via a `ScopeReturn`. `REPR_EXIT_RWH_TARGET` for RWH-
3732    /// installed frames.
3733    escape: CpsContinuationId,
3734    /// write26: function name owning the escape continuation.
3735    escape_owner_function: String,
3736    /// write26: `return_frames.len()` at install time. JumpOrExit
3737    /// truncates frames pushed inside this scope.
3738    return_frame_threshold: usize,
3739    /// write26: marks frames inherited via `into_inherited_repr`.
3740    inherited: bool,
3741    /// write26: eval frame that installed this handler. Used by
3742    /// `handle_scope_return_repr` for strict (prompt, install_eval_id)
3743    /// matching.
3744    install_eval_id: CpsReprEvalId,
3745}
3746
3747#[derive(Debug, Clone, PartialEq)]
3748struct CpsReprReturnFrame {
3749    prompt_exit: Option<CpsReprPromptExitFrame>,
3750    owner_function: String,
3751    continuation: CpsContinuationId,
3752    values: Rc<HashMap<CpsValueId, CpsReprRuntimeValue>>,
3753    active_handlers: Vec<CpsReprHandlerFrame>,
3754    guard_stack: Vec<CpsReprGuardEntry>,
3755    active_blocked: Vec<CpsReprBlockedEffect>,
3756    owner_initial_frame_count: usize,
3757    owner_eval_id: CpsReprEvalId,
3758}
3759
3760#[derive(Debug, Clone, PartialEq)]
3761struct CpsReprPromptExitFrame {
3762    prompt: CpsReprPromptId,
3763}
3764
3765#[derive(Debug, Clone, PartialEq)]
3766struct CpsReprEvaluatedHandlerEnv {
3767    entry: CpsContinuationId,
3768    values: Vec<(CpsValueId, CpsReprRuntimeValue)>,
3769}
3770
3771#[derive(Debug, Clone, PartialEq)]
3772struct CpsReprLatestHandlerEnv {
3773    handler: CpsHandlerId,
3774    entry: CpsContinuationId,
3775    target: CpsValueId,
3776    value: CpsReprRuntimeValue,
3777}
3778
3779#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3780struct CpsReprGuardEntry {
3781    var: runtime::EffectIdVar,
3782    id: u64,
3783}
3784
3785fn eval_literal(literal: &CpsLiteral) -> runtime::VmValue {
3786    match literal {
3787        CpsLiteral::Int(value) => runtime::VmValue::Int(value.clone()),
3788        CpsLiteral::Float(value) => runtime::VmValue::Float(value.clone()),
3789        CpsLiteral::String(value) => {
3790            runtime::VmValue::String(runtime::runtime::string_tree::StringTree::from_str(value))
3791        }
3792        CpsLiteral::Bool(value) => runtime::VmValue::Bool(*value),
3793        CpsLiteral::Unit => runtime::VmValue::Unit,
3794    }
3795}
3796
3797fn eval_primitive(
3798    op: typed_ir::PrimitiveOp,
3799    args: &[runtime::VmValue],
3800) -> CpsReprEvalResult<runtime::VmValue> {
3801    crate::eval::eval_primitive_for_abi(op, args).map_err(cps_repr_primitive_error)
3802}
3803
3804fn bool_value(op: typed_ir::PrimitiveOp, value: &runtime::VmValue) -> CpsReprEvalResult<bool> {
3805    match value {
3806        runtime::VmValue::Bool(value) => Ok(*value),
3807        value => Err(CpsReprEvalError::PrimitiveTypeMismatch {
3808            op,
3809            value: value.clone(),
3810        }),
3811    }
3812}
3813
3814fn cps_repr_primitive_error(error: crate::eval::NativeEvalError) -> CpsReprEvalError {
3815    match error {
3816        crate::eval::NativeEvalError::InvalidPrimitiveArity {
3817            op,
3818            expected,
3819            actual,
3820        } => CpsReprEvalError::InvalidPrimitiveArity {
3821            op,
3822            expected,
3823            actual,
3824        },
3825        crate::eval::NativeEvalError::PrimitiveTypeMismatch { op, value } => {
3826            CpsReprEvalError::PrimitiveTypeMismatch { op, value }
3827        }
3828        crate::eval::NativeEvalError::UnsupportedPrimitive { op } => {
3829            CpsReprEvalError::UnsupportedPrimitive { op }
3830        }
3831        other => unreachable!("native primitive evaluator returned non-primitive error: {other}"),
3832    }
3833}
3834
3835#[cfg(test)]
3836mod tests {
3837    use std::rc::Rc;
3838
3839    use super::*;
3840
3841    #[test]
3842    fn lowers_continuations_to_code_with_environment_slots() {
3843        let module = CpsModule {
3844            functions: Vec::new(),
3845            roots: vec![CpsFunction {
3846                name: "root".to_string(),
3847                params: Vec::new(),
3848                entry: CpsContinuationId(0),
3849                handlers: Vec::new(),
3850                continuations: vec![CpsContinuation {
3851                    id: CpsContinuationId(0),
3852                    params: vec![CpsValueId(0)],
3853                    captures: vec![CpsValueId(2), CpsValueId(1)],
3854                    shot_kind: CpsShotKind::MultiShot,
3855                    stmts: Vec::new(),
3856                    terminator: CpsTerminator::Return(CpsValueId(0)),
3857                }],
3858            }],
3859        };
3860
3861        let lowered = lower_cps_repr_module(&module);
3862
3863        assert_eq!(
3864            lowered.roots[0].continuations[0].environment,
3865            vec![
3866                CpsReprEnvironmentSlot {
3867                    index: 0,
3868                    value: CpsValueId(2),
3869                },
3870                CpsReprEnvironmentSlot {
3871                    index: 1,
3872                    value: CpsValueId(1),
3873                },
3874            ]
3875        );
3876    }
3877
3878    #[test]
3879    fn evaluates_pure_continuation_flow() {
3880        let module = CpsModule {
3881            functions: Vec::new(),
3882            roots: vec![CpsFunction {
3883                name: "root".to_string(),
3884                params: Vec::new(),
3885                entry: CpsContinuationId(0),
3886                handlers: Vec::new(),
3887                continuations: vec![
3888                    CpsContinuation {
3889                        id: CpsContinuationId(0),
3890                        params: Vec::new(),
3891                        captures: Vec::new(),
3892                        shot_kind: CpsShotKind::OneShot,
3893                        stmts: vec![
3894                            CpsStmt::Literal {
3895                                dest: CpsValueId(0),
3896                                literal: CpsLiteral::Int("40".to_string()),
3897                            },
3898                            CpsStmt::Literal {
3899                                dest: CpsValueId(1),
3900                                literal: CpsLiteral::Int("2".to_string()),
3901                            },
3902                            CpsStmt::Primitive {
3903                                dest: CpsValueId(2),
3904                                op: typed_ir::PrimitiveOp::IntAdd,
3905                                args: vec![CpsValueId(0), CpsValueId(1)],
3906                            },
3907                        ],
3908                        terminator: CpsTerminator::Continue {
3909                            target: CpsContinuationId(1),
3910                            args: vec![CpsValueId(2)],
3911                        },
3912                    },
3913                    CpsContinuation {
3914                        id: CpsContinuationId(1),
3915                        params: vec![CpsValueId(3)],
3916                        captures: Vec::new(),
3917                        shot_kind: CpsShotKind::OneShot,
3918                        stmts: Vec::new(),
3919                        terminator: CpsTerminator::Return(CpsValueId(3)),
3920                    },
3921                ],
3922            }],
3923        };
3924        let lowered = lower_cps_repr_module(&module);
3925
3926        assert_eq!(
3927            eval_cps_repr_module(&lowered).expect("evaluated"),
3928            vec![runtime::VmValue::Int("42".to_string())]
3929        );
3930    }
3931
3932    #[test]
3933    fn evaluates_plain_value_primitives_through_native_evaluator() {
3934        let module = CpsModule {
3935            functions: Vec::new(),
3936            roots: vec![
3937                CpsFunction {
3938                    name: "string-root".to_string(),
3939                    params: Vec::new(),
3940                    entry: CpsContinuationId(0),
3941                    handlers: Vec::new(),
3942                    continuations: vec![CpsContinuation {
3943                        id: CpsContinuationId(0),
3944                        params: Vec::new(),
3945                        captures: Vec::new(),
3946                        shot_kind: CpsShotKind::OneShot,
3947                        stmts: vec![
3948                            CpsStmt::Literal {
3949                                dest: CpsValueId(0),
3950                                literal: CpsLiteral::String("ać‚šŸ™‚z".to_string()),
3951                            },
3952                            CpsStmt::Literal {
3953                                dest: CpsValueId(1),
3954                                literal: CpsLiteral::Int("1".to_string()),
3955                            },
3956                            CpsStmt::Literal {
3957                                dest: CpsValueId(2),
3958                                literal: CpsLiteral::Int("3".to_string()),
3959                            },
3960                            CpsStmt::Literal {
3961                                dest: CpsValueId(3),
3962                                literal: CpsLiteral::String("bc".to_string()),
3963                            },
3964                            CpsStmt::Primitive {
3965                                dest: CpsValueId(4),
3966                                op: typed_ir::PrimitiveOp::StringSpliceRaw,
3967                                args: vec![
3968                                    CpsValueId(0),
3969                                    CpsValueId(1),
3970                                    CpsValueId(2),
3971                                    CpsValueId(3),
3972                                ],
3973                            },
3974                        ],
3975                        terminator: CpsTerminator::Return(CpsValueId(4)),
3976                    }],
3977                },
3978                CpsFunction {
3979                    name: "list-root".to_string(),
3980                    params: Vec::new(),
3981                    entry: CpsContinuationId(0),
3982                    handlers: Vec::new(),
3983                    continuations: vec![CpsContinuation {
3984                        id: CpsContinuationId(0),
3985                        params: Vec::new(),
3986                        captures: Vec::new(),
3987                        shot_kind: CpsShotKind::OneShot,
3988                        stmts: vec![
3989                            CpsStmt::Literal {
3990                                dest: CpsValueId(0),
3991                                literal: CpsLiteral::Int("1".to_string()),
3992                            },
3993                            CpsStmt::Literal {
3994                                dest: CpsValueId(1),
3995                                literal: CpsLiteral::Int("2".to_string()),
3996                            },
3997                            CpsStmt::Primitive {
3998                                dest: CpsValueId(2),
3999                                op: typed_ir::PrimitiveOp::ListSingleton,
4000                                args: vec![CpsValueId(0)],
4001                            },
4002                            CpsStmt::Primitive {
4003                                dest: CpsValueId(3),
4004                                op: typed_ir::PrimitiveOp::ListSingleton,
4005                                args: vec![CpsValueId(1)],
4006                            },
4007                            CpsStmt::Primitive {
4008                                dest: CpsValueId(4),
4009                                op: typed_ir::PrimitiveOp::ListMerge,
4010                                args: vec![CpsValueId(2), CpsValueId(3)],
4011                            },
4012                            CpsStmt::Primitive {
4013                                dest: CpsValueId(5),
4014                                op: typed_ir::PrimitiveOp::ListViewRaw,
4015                                args: vec![CpsValueId(4)],
4016                            },
4017                        ],
4018                        terminator: CpsTerminator::Return(CpsValueId(5)),
4019                    }],
4020                },
4021            ],
4022        };
4023        let lowered = lower_cps_repr_module(&module);
4024
4025        assert_eq!(
4026            eval_cps_repr_module(&lowered).expect("evaluated"),
4027            vec![
4028                runtime::VmValue::String(runtime::runtime::string_tree::StringTree::from_str(
4029                    "abcz",
4030                )),
4031                runtime::VmValue::Variant {
4032                    tag: typed_ir::Name("node".to_string()),
4033                    value: Some(Box::new(runtime::VmValue::Tuple(vec![
4034                        runtime::VmValue::List(runtime::runtime::list_tree::ListTree::from_items(
4035                            [Rc::new(runtime::VmValue::Int("1".to_string())),]
4036                        )),
4037                        runtime::VmValue::List(runtime::runtime::list_tree::ListTree::from_items(
4038                            [Rc::new(runtime::VmValue::Int("2".to_string())),]
4039                        )),
4040                    ]))),
4041                },
4042            ]
4043        );
4044    }
4045
4046    #[test]
4047    fn evaluates_multishot_resumption_flow() {
4048        let module = multishot_resumption_module();
4049        let lowered = lower_cps_repr_module(&module);
4050
4051        assert_eq!(
4052            eval_cps_repr_module(&lowered).expect("evaluated"),
4053            vec![runtime::VmValue::Int("23".to_string())]
4054        );
4055    }
4056
4057    #[test]
4058    fn evaluates_resumption_under_fresh_handler_stack() {
4059        let module = rebased_resumption_module();
4060        let lowered = lower_cps_repr_module(&module);
4061
4062        assert_eq!(
4063            eval_cps_repr_module(&lowered).expect("evaluated"),
4064            vec![runtime::VmValue::Int("13".to_string())]
4065        );
4066    }
4067
4068    #[test]
4069    fn skips_handler_frame_blocked_by_guard_snapshot() {
4070        let module = blocked_handler_snapshot_module();
4071        let lowered = lower_cps_repr_module(&module);
4072
4073        assert_eq!(
4074            eval_cps_repr_module(&lowered).expect("evaluated"),
4075            vec![runtime::VmValue::Int("100".to_string())]
4076        );
4077    }
4078
4079    #[test]
4080    fn analyzes_resumption_value_kind() {
4081        let lowered = lower_cps_repr_module(&multishot_resumption_module());
4082        let analysis = analyze_cps_repr_values(&lowered);
4083
4084        assert_eq!(
4085            analysis.value_kind("root", CpsValueId(4)),
4086            Some(CpsReprValueKind::Plain)
4087        );
4088        assert_eq!(
4089            analysis.value_kind("root", CpsValueId(5)),
4090            Some(CpsReprValueKind::Resumption)
4091        );
4092        assert_eq!(
4093            analysis.value_kind("root", CpsValueId(10)),
4094            Some(CpsReprValueKind::Resumption)
4095        );
4096        assert_eq!(
4097            analysis.value_kind("root", CpsValueId(7)),
4098            Some(CpsReprValueKind::Plain)
4099        );
4100        assert_eq!(
4101            analysis.continuation_return_kind("root", CpsContinuationId(2)),
4102            Some(CpsReprValueKind::Plain)
4103        );
4104    }
4105
4106    #[test]
4107    fn analyzes_resumption_abi_lane() {
4108        let lowered = lower_cps_repr_module(&multishot_resumption_module());
4109        let analysis = analyze_cps_repr_abi_lanes(&lowered);
4110
4111        assert_eq!(
4112            analysis.value_lane("root", CpsValueId(5)),
4113            Some(CpsReprAbiLane::ResumptionPtr)
4114        );
4115        assert_eq!(
4116            analysis.value_lane("root", CpsValueId(7)),
4117            Some(CpsReprAbiLane::ScalarI64)
4118        );
4119        assert_eq!(
4120            analysis.continuation_return_lane("root", CpsContinuationId(2)),
4121            Some(CpsReprAbiLane::ScalarI64)
4122        );
4123        assert_eq!(
4124            analysis.function_return_lane("root"),
4125            Some(CpsReprAbiLane::ScalarI64)
4126        );
4127    }
4128
4129    #[test]
4130    fn propagates_direct_call_abi_lane() {
4131        let module = CpsModule {
4132            functions: vec![CpsFunction {
4133                name: "make_int".to_string(),
4134                params: Vec::new(),
4135                entry: CpsContinuationId(0),
4136                handlers: Vec::new(),
4137                continuations: vec![CpsContinuation {
4138                    id: CpsContinuationId(0),
4139                    params: Vec::new(),
4140                    captures: Vec::new(),
4141                    shot_kind: CpsShotKind::OneShot,
4142                    stmts: vec![CpsStmt::Literal {
4143                        dest: CpsValueId(0),
4144                        literal: CpsLiteral::Int("42".to_string()),
4145                    }],
4146                    terminator: CpsTerminator::Return(CpsValueId(0)),
4147                }],
4148            }],
4149            roots: vec![CpsFunction {
4150                name: "root".to_string(),
4151                params: Vec::new(),
4152                entry: CpsContinuationId(0),
4153                handlers: Vec::new(),
4154                continuations: vec![CpsContinuation {
4155                    id: CpsContinuationId(0),
4156                    params: Vec::new(),
4157                    captures: Vec::new(),
4158                    shot_kind: CpsShotKind::OneShot,
4159                    stmts: vec![CpsStmt::DirectCall {
4160                        dest: CpsValueId(0),
4161                        target: "make_int".to_string(),
4162                        args: Vec::new(),
4163                    }],
4164                    terminator: CpsTerminator::Return(CpsValueId(0)),
4165                }],
4166            }],
4167        };
4168        let lowered = lower_cps_repr_module(&module);
4169        let analysis = analyze_cps_repr_abi_lanes(&lowered);
4170
4171        assert_eq!(
4172            analysis.value_lane("root", CpsValueId(0)),
4173            Some(CpsReprAbiLane::ScalarI64)
4174        );
4175        assert_eq!(
4176            analysis.function_return_lane("root"),
4177            Some(CpsReprAbiLane::ScalarI64)
4178        );
4179    }
4180
4181    fn multishot_resumption_module() -> CpsModule {
4182        let effect = typed_ir::Path::from_name(typed_ir::Name("choose".to_string()));
4183        CpsModule {
4184            functions: Vec::new(),
4185            roots: vec![CpsFunction {
4186                name: "root".to_string(),
4187                params: Vec::new(),
4188                entry: CpsContinuationId(0),
4189                handlers: vec![crate::cps_ir::CpsHandler {
4190                    id: CpsHandlerId(0),
4191                    arms: vec![crate::cps_ir::CpsHandlerArm {
4192                        effect: effect.clone(),
4193                        entry: CpsContinuationId(2),
4194                    }],
4195                }],
4196                continuations: vec![
4197                    CpsContinuation {
4198                        id: CpsContinuationId(0),
4199                        params: Vec::new(),
4200                        captures: Vec::new(),
4201                        shot_kind: CpsShotKind::MultiShot,
4202                        stmts: vec![CpsStmt::Literal {
4203                            dest: CpsValueId(0),
4204                            literal: CpsLiteral::Int("1".to_string()),
4205                        }],
4206                        terminator: CpsTerminator::Perform {
4207                            effect,
4208                            payload: CpsValueId(0),
4209                            resume: CpsContinuationId(1),
4210                            handler: CpsHandlerId(0),
4211                            blocked: None,
4212                        },
4213                    },
4214                    CpsContinuation {
4215                        id: CpsContinuationId(1),
4216                        params: vec![CpsValueId(1)],
4217                        captures: Vec::new(),
4218                        shot_kind: CpsShotKind::MultiShot,
4219                        stmts: vec![
4220                            CpsStmt::Literal {
4221                                dest: CpsValueId(2),
4222                                literal: CpsLiteral::Int("10".to_string()),
4223                            },
4224                            CpsStmt::Primitive {
4225                                dest: CpsValueId(3),
4226                                op: typed_ir::PrimitiveOp::IntAdd,
4227                                args: vec![CpsValueId(1), CpsValueId(2)],
4228                            },
4229                        ],
4230                        terminator: CpsTerminator::Return(CpsValueId(3)),
4231                    },
4232                    CpsContinuation {
4233                        id: CpsContinuationId(2),
4234                        params: vec![CpsValueId(4), CpsValueId(5)],
4235                        captures: Vec::new(),
4236                        shot_kind: CpsShotKind::MultiShot,
4237                        stmts: vec![
4238                            CpsStmt::CloneContinuation {
4239                                dest: CpsValueId(10),
4240                                source: CpsValueId(5),
4241                            },
4242                            CpsStmt::Literal {
4243                                dest: CpsValueId(6),
4244                                literal: CpsLiteral::Int("2".to_string()),
4245                            },
4246                            CpsStmt::Resume {
4247                                dest: CpsValueId(7),
4248                                resumption: CpsValueId(5),
4249                                arg: CpsValueId(4),
4250                            },
4251                            CpsStmt::Resume {
4252                                dest: CpsValueId(8),
4253                                resumption: CpsValueId(10),
4254                                arg: CpsValueId(6),
4255                            },
4256                            CpsStmt::Primitive {
4257                                dest: CpsValueId(9),
4258                                op: typed_ir::PrimitiveOp::IntAdd,
4259                                args: vec![CpsValueId(7), CpsValueId(8)],
4260                            },
4261                        ],
4262                        terminator: CpsTerminator::Return(CpsValueId(9)),
4263                    },
4264                ],
4265            }],
4266        }
4267    }
4268
4269    fn rebased_resumption_module() -> CpsModule {
4270        let effect = typed_ir::Path::from_name(typed_ir::Name("choose".to_string()));
4271        CpsModule {
4272            functions: Vec::new(),
4273            roots: vec![CpsFunction {
4274                name: "root".to_string(),
4275                params: Vec::new(),
4276                entry: CpsContinuationId(0),
4277                handlers: vec![
4278                    crate::cps_ir::CpsHandler {
4279                        id: CpsHandlerId(0),
4280                        arms: vec![crate::cps_ir::CpsHandlerArm {
4281                            effect: effect.clone(),
4282                            entry: CpsContinuationId(2),
4283                        }],
4284                    },
4285                    crate::cps_ir::CpsHandler {
4286                        id: CpsHandlerId(1),
4287                        arms: vec![crate::cps_ir::CpsHandlerArm {
4288                            effect: effect.clone(),
4289                            entry: CpsContinuationId(4),
4290                        }],
4291                    },
4292                ],
4293                continuations: vec![
4294                    CpsContinuation {
4295                        id: CpsContinuationId(0),
4296                        params: Vec::new(),
4297                        captures: Vec::new(),
4298                        shot_kind: CpsShotKind::MultiShot,
4299                        stmts: vec![CpsStmt::Literal {
4300                            dest: CpsValueId(0),
4301                            literal: CpsLiteral::Int("1".to_string()),
4302                        }],
4303                        terminator: CpsTerminator::Perform {
4304                            effect: effect.clone(),
4305                            payload: CpsValueId(0),
4306                            resume: CpsContinuationId(1),
4307                            handler: CpsHandlerId(0),
4308                            blocked: None,
4309                        },
4310                    },
4311                    CpsContinuation {
4312                        id: CpsContinuationId(1),
4313                        params: vec![CpsValueId(1)],
4314                        captures: Vec::new(),
4315                        shot_kind: CpsShotKind::MultiShot,
4316                        stmts: vec![CpsStmt::Literal {
4317                            dest: CpsValueId(2),
4318                            literal: CpsLiteral::Int("2".to_string()),
4319                        }],
4320                        terminator: CpsTerminator::Perform {
4321                            effect: effect.clone(),
4322                            payload: CpsValueId(2),
4323                            resume: CpsContinuationId(3),
4324                            handler: CpsHandlerId(0),
4325                            blocked: None,
4326                        },
4327                    },
4328                    CpsContinuation {
4329                        id: CpsContinuationId(2),
4330                        params: vec![CpsValueId(4), CpsValueId(5)],
4331                        captures: Vec::new(),
4332                        shot_kind: CpsShotKind::MultiShot,
4333                        stmts: vec![CpsStmt::ResumeWithHandler {
4334                            dest: CpsValueId(6),
4335                            resumption: CpsValueId(5),
4336                            arg: CpsValueId(4),
4337                            handler: CpsHandlerId(1),
4338                            envs: Vec::new(),
4339                        }],
4340                        terminator: CpsTerminator::Return(CpsValueId(6)),
4341                    },
4342                    CpsContinuation {
4343                        id: CpsContinuationId(3),
4344                        params: vec![CpsValueId(9)],
4345                        captures: vec![CpsValueId(1)],
4346                        shot_kind: CpsShotKind::MultiShot,
4347                        stmts: vec![CpsStmt::Primitive {
4348                            dest: CpsValueId(13),
4349                            op: typed_ir::PrimitiveOp::IntAdd,
4350                            args: vec![CpsValueId(1), CpsValueId(9)],
4351                        }],
4352                        terminator: CpsTerminator::Return(CpsValueId(13)),
4353                    },
4354                    CpsContinuation {
4355                        id: CpsContinuationId(4),
4356                        params: vec![CpsValueId(7), CpsValueId(8)],
4357                        captures: Vec::new(),
4358                        shot_kind: CpsShotKind::MultiShot,
4359                        stmts: vec![
4360                            CpsStmt::Literal {
4361                                dest: CpsValueId(10),
4362                                literal: CpsLiteral::Int("10".to_string()),
4363                            },
4364                            CpsStmt::Primitive {
4365                                dest: CpsValueId(11),
4366                                op: typed_ir::PrimitiveOp::IntAdd,
4367                                args: vec![CpsValueId(7), CpsValueId(10)],
4368                            },
4369                            CpsStmt::Resume {
4370                                dest: CpsValueId(12),
4371                                resumption: CpsValueId(8),
4372                                arg: CpsValueId(11),
4373                            },
4374                        ],
4375                        terminator: CpsTerminator::Return(CpsValueId(12)),
4376                    },
4377                ],
4378            }],
4379        }
4380    }
4381
4382    fn blocked_handler_snapshot_module() -> CpsModule {
4383        let start = typed_ir::Path::from_name(typed_ir::Name("start".to_string()));
4384        let choose = typed_ir::Path::from_name(typed_ir::Name("choose".to_string()));
4385        CpsModule {
4386            functions: Vec::new(),
4387            roots: vec![CpsFunction {
4388                name: "root".to_string(),
4389                params: Vec::new(),
4390                entry: CpsContinuationId(0),
4391                handlers: vec![
4392                    crate::cps_ir::CpsHandler {
4393                        id: CpsHandlerId(0),
4394                        arms: vec![
4395                            crate::cps_ir::CpsHandlerArm {
4396                                effect: start.clone(),
4397                                entry: CpsContinuationId(2),
4398                            },
4399                            crate::cps_ir::CpsHandlerArm {
4400                                effect: choose.clone(),
4401                                entry: CpsContinuationId(5),
4402                            },
4403                        ],
4404                    },
4405                    crate::cps_ir::CpsHandler {
4406                        id: CpsHandlerId(1),
4407                        arms: vec![crate::cps_ir::CpsHandlerArm {
4408                            effect: choose.clone(),
4409                            entry: CpsContinuationId(4),
4410                        }],
4411                    },
4412                ],
4413                continuations: vec![
4414                    CpsContinuation {
4415                        id: CpsContinuationId(0),
4416                        params: Vec::new(),
4417                        captures: Vec::new(),
4418                        shot_kind: CpsShotKind::MultiShot,
4419                        stmts: vec![CpsStmt::Literal {
4420                            dest: CpsValueId(0),
4421                            literal: CpsLiteral::Int("1".to_string()),
4422                        }],
4423                        terminator: CpsTerminator::Perform {
4424                            effect: start,
4425                            payload: CpsValueId(0),
4426                            resume: CpsContinuationId(1),
4427                            handler: CpsHandlerId(0),
4428                            blocked: None,
4429                        },
4430                    },
4431                    CpsContinuation {
4432                        id: CpsContinuationId(1),
4433                        params: vec![CpsValueId(1)],
4434                        captures: Vec::new(),
4435                        shot_kind: CpsShotKind::MultiShot,
4436                        stmts: vec![CpsStmt::Literal {
4437                            dest: CpsValueId(2),
4438                            literal: CpsLiteral::Int("0".to_string()),
4439                        }],
4440                        terminator: CpsTerminator::Perform {
4441                            effect: choose.clone(),
4442                            payload: CpsValueId(2),
4443                            resume: CpsContinuationId(3),
4444                            handler: CpsHandlerId(0),
4445                            blocked: Some(CpsValueId(1)),
4446                        },
4447                    },
4448                    CpsContinuation {
4449                        id: CpsContinuationId(2),
4450                        params: vec![CpsValueId(3), CpsValueId(4)],
4451                        captures: Vec::new(),
4452                        shot_kind: CpsShotKind::MultiShot,
4453                        stmts: vec![
4454                            CpsStmt::FreshGuard {
4455                                dest: CpsValueId(5),
4456                                var: yulang_runtime::EffectIdVar(0),
4457                            },
4458                            CpsStmt::ResumeWithHandler {
4459                                dest: CpsValueId(6),
4460                                resumption: CpsValueId(4),
4461                                arg: CpsValueId(5),
4462                                handler: CpsHandlerId(1),
4463                                envs: Vec::new(),
4464                            },
4465                        ],
4466                        terminator: CpsTerminator::Return(CpsValueId(6)),
4467                    },
4468                    CpsContinuation {
4469                        id: CpsContinuationId(3),
4470                        params: vec![CpsValueId(7)],
4471                        captures: Vec::new(),
4472                        shot_kind: CpsShotKind::MultiShot,
4473                        stmts: Vec::new(),
4474                        terminator: CpsTerminator::Return(CpsValueId(7)),
4475                    },
4476                    CpsContinuation {
4477                        id: CpsContinuationId(4),
4478                        params: vec![CpsValueId(8), CpsValueId(9)],
4479                        captures: Vec::new(),
4480                        shot_kind: CpsShotKind::MultiShot,
4481                        stmts: vec![CpsStmt::Literal {
4482                            dest: CpsValueId(10),
4483                            literal: CpsLiteral::Int("200".to_string()),
4484                        }],
4485                        terminator: CpsTerminator::Return(CpsValueId(10)),
4486                    },
4487                    CpsContinuation {
4488                        id: CpsContinuationId(5),
4489                        params: vec![CpsValueId(11), CpsValueId(12)],
4490                        captures: Vec::new(),
4491                        shot_kind: CpsShotKind::MultiShot,
4492                        stmts: vec![CpsStmt::Literal {
4493                            dest: CpsValueId(13),
4494                            literal: CpsLiteral::Int("100".to_string()),
4495                        }],
4496                        terminator: CpsTerminator::Return(CpsValueId(13)),
4497                    },
4498                ],
4499            }],
4500        }
4501    }
4502}