narju/floor/
machine.rs

1use super::host;
2use super::{code, rc_exp, rc_val, Env, EnvChunk, Exp, Prim1, Prim2, RcExp, RcVal, Shared, Val};
3use std::collections::HashSet;
4
5/// Memo of closures already η-expanded in the current scope (the paper's
6/// `findFun`). Without it a recursive closure's lift expands forever.
7type FunTable = Shared<Vec<(FunKey, usize)>>;
8
9#[derive(Clone)]
10struct FunKey {
11    env: Env,
12    body: RcExp,
13}
14
15/// `Val`'s equality compares a closure by body and never descends into its
16/// environment, so this cannot recur through a cyclic value graph.
17fn env_equal(a: &Env, b: &Env) -> bool {
18    Env::ptr_eq(a, b) || (a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x == y))
19}
20
21/// A type error is a `throw` the program may catch, not a second error system.
22/// `Bug` is uncatchable and means an invariant of this file was violated.
23#[derive(Debug, PartialEq)]
24pub enum Fault {
25    Throw(Val),
26    Bug(String),
27}
28
29impl std::fmt::Display for Fault {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Fault::Throw(v) => write!(f, "{v}"),
33            Fault::Bug(s) => write!(f, "bug: {s}"),
34        }
35    }
36}
37
38pub fn throw(tag: &str, detail: Val) -> Fault {
39    Fault::Throw(Val::Pair(rc_val(Val::Sym(tag.into())), rc_val(detail)))
40}
41
42fn wrong_type(op: &str) -> Fault {
43    throw("wrong-type", Val::Sym(op.into()))
44}
45
46/// A scalar is its own constant; a pair, closure or cell would need an
47/// allocation where a constant was expected. Callers apply this only once some
48/// operand is already code, else the whole program would stage.
49fn as_syntax(v: &Val) -> Option<RcExp> {
50    match v {
51        Val::Code(e) => Some(RcExp::clone(e)),
52        Val::Num(n) => Some(rc_exp(Exp::Lit(*n))),
53        Val::Flo(x) => Some(rc_exp(Exp::Flo(*x))),
54        Val::Sym(s) => Some(rc_exp(Exp::Sym(s.clone()))),
55        Val::Atom(a) => Some(rc_exp(Exp::Atom(a.clone()))),
56        Val::Nil => Some(rc_exp(Exp::Nil)),
57        _ => None,
58    }
59}
60
61fn kind_of(v: &Val) -> &'static str {
62    match v {
63        Val::Num(_) => "number",
64        Val::Flo(_) => "float",
65        Val::Sym(_) => "symbol",
66        Val::Atom(_) => "atom",
67        Val::Nil => "nil",
68        Val::Pair(_, _) => "pair",
69        Val::Clo(_, _) => "closure",
70        Val::Code(_) => "code",
71        Val::Cell(_) => "cell",
72        Val::Raise(_) => "raise",
73    }
74}
75
76/// A closure made inside a staged region captures [`Val::Code`] parameters;
77/// persisting it would freeze those holes into the residual, so the walk goes
78/// everywhere one can hide, closure environments included. The memo is not
79/// optional - the environment chain makes a closure a DAG whose sharing is
80/// worth about four orders of magnitude.
81fn has_holes<M: Mail>(v: &Val, h: &Store<M>) -> bool {
82    let mut vals: HashSet<*const Val> = HashSet::new();
83    let mut envs: HashSet<*const EnvChunk> = HashSet::new();
84    let mut work = vec![Val::clone(v)];
85    while let Some(x) = work.pop() {
86        match x {
87            Val::Code(_) => return true,
88            Val::Pair(a, b) => {
89                for c in [a, b] {
90                    if vals.insert(&*c as *const Val) {
91                        work.push(Val::clone(&c));
92                    }
93                }
94            }
95            Val::Raise(a) => {
96                if vals.insert(&*a as *const Val) {
97                    work.push(Val::clone(&a));
98                }
99            }
100            // What a cell holds while staging is not what it will hold, but
101            // this is the only moment there is to check.
102            Val::Cell(i) => work.push(h.cells[i].clone()),
103            Val::Clo(env, _) => {
104                let mut cur = Some(env);
105                while let Some(e) = cur {
106                    if !envs.insert(Shared::as_ptr(&e.0)) {
107                        break;
108                    }
109                    work.extend(e.0.vals.iter().cloned());
110                    cur = e.0.parent.clone();
111                }
112            }
113            _ => {}
114        }
115    }
116    false
117}
118
119/// One `cons` statement per node, post-order, so the residual allocates the
120/// structure rather than carrying it as a constant. `refusal` is the door this
121/// was reached through - the same closure is `unliftable` under `lift` and
122/// `unreturnable` at a region's end.
123fn cons_syntax<M: Mail>(v: &Val, refusal: &'static str, h: &mut Store<M>) -> Result<Exp, Fault> {
124    // `None` is "both components are done, cons them".
125    let mut work: Vec<Option<&Val>> = vec![Some(v)];
126    let mut done: Vec<RcExp> = Vec::new();
127    while let Some(step) = work.pop() {
128        match step {
129            Some(Val::Pair(a, b)) => {
130                work.push(None);
131                work.push(Some(b));
132                work.push(Some(a));
133            }
134            Some(leaf) => done.push(
135                as_syntax(leaf).ok_or_else(|| throw(refusal, Val::Sym(kind_of(leaf).into())))?,
136            ),
137            None => {
138                let cdr = done.pop().expect("cdr");
139                let car = done.pop().expect("car");
140                let named = h.reflect(Exp::Prim2(Prim2::Cons, car, cdr));
141                done.push(rc_exp(named));
142            }
143        }
144    }
145    Ok(RcExp::unwrap_or_clone(done.pop().expect("one result")))
146}
147
148/// `None` when nothing is code, so the caller computes instead; an error when
149/// something is code and something else cannot join it.
150fn staged_operands(vs: &[Val], op: &str) -> Option<Result<Vec<RcExp>, Fault>> {
151    if !vs.iter().any(|v| matches!(v, Val::Code(_))) {
152        return None;
153    }
154    Some(
155        vs.iter()
156            .map(|v| as_syntax(v).ok_or_else(|| wrong_type(op)))
157            .collect(),
158    )
159}
160
161/// The floor's only route to the world. It knows nothing about tasks beyond an
162/// address being an s-expression; the scheduler supplies this.
163pub trait Mail: Send {
164    fn spawn(&mut self, thunk: Val) -> Result<Val, Val>;
165    fn send(&mut self, addr: &Val, msg: Val) -> Result<Val, Val>;
166    /// `None` means the mailbox is empty, which is the only reason
167    /// anything in this machine ever blocks.
168    fn receive(&mut self) -> Option<Val>;
169    fn monitor(&mut self, addr: &Val) -> Result<Val, Val>;
170    fn limit(&mut self, addr: &Val, turns: i64) -> Result<Val, Val>;
171}
172
173/// Every operation throws, so a bare evaluator is usable and a stray `send` is
174/// catchable rather than a panic.
175#[derive(Default)]
176pub struct NoMail;
177
178impl Mail for NoMail {
179    fn spawn(&mut self, _: Val) -> Result<Val, Val> {
180        Err(Val::Sym("no-scheduler".into()))
181    }
182    fn send(&mut self, _: &Val, _: Val) -> Result<Val, Val> {
183        Err(Val::Sym("no-scheduler".into()))
184    }
185    fn receive(&mut self) -> Option<Val> {
186        None
187    }
188    fn monitor(&mut self, _: &Val) -> Result<Val, Val> {
189        Err(Val::Sym("no-scheduler".into()))
190    }
191    fn limit(&mut self, _: &Val, _: i64) -> Result<Val, Val> {
192        Err(Val::Sym("no-scheduler".into()))
193    }
194}
195
196/// The store, in the CESK sense. Cells and the mailbox are effects and survive
197/// a `catch`; `fresh` and `block` are staging state and do not. Tasks may share
198/// effects but never staging state - one interleaved block is two half
199/// residuals.
200pub struct Store<M: Mail = NoMail> {
201    pub fresh: usize,
202    /// Statements of the innermost open reify scope, drained into a `Let`-chain
203    /// when it closes. This is where ANF comes from.
204    pub block: Vec<Exp>,
205    fun: FunTable,
206    /// What [`Prim1::IsCodeFun`] answers about. Restored like `fun`: an
207    /// abandoned region's variable numbers are handed out again.
208    lams: Shared<Vec<usize>>,
209    pub level: usize,
210    /// `Val::Cell(i)` indexes here
211    pub cells: Vec<Val>,
212    pub mail: M,
213}
214
215impl<M: Mail + Default> Default for Store<M> {
216    fn default() -> Self {
217        Store::new(M::default())
218    }
219}
220
221impl<M: Mail> Store<M> {
222    pub fn new(mail: M) -> Store<M> {
223        Store {
224            fresh: 0,
225            block: Vec::new(),
226            fun: Shared::new(Vec::new()),
227            lams: Shared::new(Vec::new()),
228            level: 0,
229            cells: Vec::new(),
230            mail,
231        }
232    }
233
234    pub fn fresh_var(&mut self) -> usize {
235        let v = self.fresh;
236        self.fresh += 1;
237        v
238    }
239
240    fn find_fun(&self, env: &Env, body: &RcExp) -> Option<usize> {
241        // Body first: a pointer hit on the same `Lam` node is the common case,
242        // and the environment comparison only runs after.
243        self.fun.iter().find_map(|(k, level)| {
244            let same_body = RcExp::ptr_eq(&k.body, body) || *k.body == **body;
245            (same_body && env_equal(&k.env, env)).then_some(*level)
246        })
247    }
248
249    fn register_fun(&mut self, level: usize, env: &Env, body: &RcExp) {
250        Shared::make_mut(&mut self.fun).push((
251            FunKey {
252                env: env.clone(),
253                body: RcExp::clone(body),
254            },
255            level,
256        ));
257    }
258
259    /// Called twice for one `lift-fun`, for the name the body knows itself by
260    /// and the name outside - the same number, since the scope restore puts
261    /// `fresh` back before the `Lam` is reflected.
262    fn register_lam(&mut self, level: usize) {
263        Shared::make_mut(&mut self.lams).push(level);
264    }
265
266    fn is_lam(&self, e: &Exp) -> bool {
267        matches!(e, Exp::Var(n) if self.lams.contains(n))
268    }
269
270    /// The paper's `reflect`.
271    pub fn reflect(&mut self, e: Exp) -> Exp {
272        self.block.push(e);
273        Exp::Var(self.fresh_var())
274    }
275
276    pub fn reflectc(&mut self, e: Exp) -> Val {
277        code(self.reflect(e))
278    }
279}
280
281/// Staging state saved across a region that may be abandoned.
282pub struct Scope {
283    fresh: usize,
284    block: Vec<Exp>,
285    fun: FunTable,
286    lams: Shared<Vec<usize>>,
287}
288
289impl Scope {
290    /// Sets the region's statements aside and opens an empty block, so an
291    /// abandoned region cannot leave half a `Let`-chain behind.
292    fn save<M: Mail>(h: &mut Store<M>) -> Scope {
293        Scope {
294            fresh: h.fresh,
295            block: std::mem::take(&mut h.block),
296            // A refcount bump, not a copy; `register_fun` writes through
297            // `make_mut`, so an earlier snapshot stays intact.
298            fun: Shared::clone(&h.fun),
299            lams: Shared::clone(&h.lams),
300        }
301    }
302
303    /// Discards everything the region staged; callers wanting to keep the
304    /// staged form drain the block first.
305    fn restore<M: Mail>(self, h: &mut Store<M>) {
306        h.fresh = self.fresh;
307        h.block = self.block;
308        h.fun = self.fun;
309        h.lams = self.lams;
310    }
311}
312
313/// Where ANF comes from: one `Let` binding per reflected statement, in
314/// emission order, ending in `tail`.
315pub fn drain(stmts: Vec<Exp>, tail: Exp) -> Exp {
316    stmts
317        .into_iter()
318        .rev()
319        .fold(tail, |acc, stmt| Exp::Let(rc_exp(stmt), rc_exp(acc)))
320}
321
322/// How leniently a value is coerced to syntax.
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub enum ForceMode {
325    /// `lift`: scalars fold, a pair is built, a closure η-expands.
326    Lift,
327    /// What a staged site requires of a result. Stops short of
328    /// [`Lift`](ForceMode::Lift) at closures: η-expanding one would compile a
329    /// function nobody asked to compile, freezing its semantics.
330    Scalar,
331    /// `force-code`: code or an error, so a branch that fails to produce code
332    /// is caught rather than silently η-expanded.
333    Strict,
334}
335
336/// What surrounding staged form a completed reify scope slots into.
337pub enum Reify {
338    IfTrue { c: RcExp, env: Env, f: RcExp },
339    IfFalse { c: RcExp, t: RcExp },
340    RunStaged { b: RcExp },
341    RunNowInner { env: Env },
342    LiftRefDone { s1: RcExp },
343    CatchDone,
344    Produce,
345}
346
347/// What to do with the expression a [`Cont::ForceCode`] produces.
348pub enum Then {
349    /// Drain the block into a `Let`-chain, restore the scope, dispatch `then`.
350    Drain {
351        saved: Scope,
352        then: Reify,
353    },
354    WrapAsCode,
355    IsCodeStaged {
356        s1: RcExp,
357    },
358}
359
360pub enum Mode {
361    Eval { env: Env, exp: RcExp },
362    Apply { val: Val },
363}
364
365pub enum Cont {
366    Prim1Finish {
367        op: Prim1,
368    },
369    Prim2Right {
370        env: Env,
371        op: Prim2,
372        e2: RcExp,
373    },
374    Prim2Finish {
375        op: Prim2,
376        v1: Val,
377    },
378    /// The operator goes into `done` alongside the arguments because that is
379    /// the frame the callee is entered under - a closure binds itself first.
380    /// So an empty `done` means the operator is still being evaluated, which is
381    /// how `receives` tells operator position from argument position.
382    AppArgs {
383        env: Env,
384        args: Box<[RcExp]>,
385        done: Vec<Val>,
386    },
387    ApplyArgs {
388        env: Env,
389        args: RcExp,
390    },
391    ApplyFinish {
392        f: Val,
393    },
394    LiftFunClo {
395        env: Env,
396        f: RcExp,
397    },
398    LiftFunGo {
399        arity: u16,
400    },
401    OpArgs {
402        idx: u16,
403        env: Env,
404        args: Vec<RcExp>,
405        next: usize,
406        done: Vec<Val>,
407    },
408    IfDispatch {
409        env: Env,
410        then_: RcExp,
411        else_: RcExp,
412    },
413    LetBody {
414        env: Env,
415        body: RcExp,
416    },
417    /// Carries the staging state to roll back to, so an abandoned region
418    /// leaves no half-built block behind.
419    CatchExit {
420        saved: Scope,
421    },
422    ThrowFinish,
423
424    // ── staging ────────────────────────────────────────────────────────
425    LiftFinish,
426    /// The iterative form of the reference's `force-code`, so a closure
427    /// η-expansion grows the continuation stack rather than Rust's.
428    ForceCode {
429        mode: ForceMode,
430        then: Then,
431    },
432    LiftCloFinish {
433        arity: u16,
434        then: Then,
435    },
436    /// A block that stayed empty means nothing staged, so the value passes
437    /// through unchanged.
438    ReifyVExit {
439        saved: Scope,
440    },
441    RunNowExit {
442        saved_level: usize,
443    },
444    LiftRefDispatch {
445        env: Env,
446        e2: RcExp,
447    },
448    LiftRefPersist,
449    RunDispatch {
450        env: Env,
451        e: RcExp,
452    },
453    IsCodeRight {
454        env: Env,
455        e2: RcExp,
456    },
457    IsCodeFinish {
458        v1: Val,
459    },
460    EvalmsRight {
461        env: Env,
462        e2: RcExp,
463    },
464    EvalmsFinish {
465        env_val: Val,
466    },
467}
468
469pub struct Machine {
470    mode: Mode,
471    kont: Vec<Cont>,
472}
473
474pub enum Step {
475    Running,
476    /// The mailbox was empty. The machine has not consumed the step and
477    /// will retry the same `receive` when stepped again.
478    Blocked,
479    Done(Val),
480}
481
482impl Machine {
483    pub fn new(env: Env, exp: RcExp) -> Machine {
484        Machine {
485            mode: Mode::Eval { env, exp },
486            kont: Vec::new(),
487        }
488    }
489
490    /// A machine poised to apply `f` to `arg`. How a spawned task starts:
491    /// its thunk arrives as a value, never as syntax, so both ride in as
492    /// [`Exp::Proc`], the carrier cross-stage persistence uses.
493    pub fn applying(f: Val, arg: Val) -> Machine {
494        let call = Exp::App(
495            rc_exp(Exp::Proc(rc_val(f))),
496            Box::new([rc_exp(Exp::Proc(rc_val(arg)))]),
497        );
498        Machine::new(Env::default(), rc_exp(call))
499    }
500
501    pub fn step<M: Mail>(&mut self, h: &mut Store<M>) -> Result<Step, Fault> {
502        // A blocking receive must not consume its continuation, so it is
503        // detected before anything is popped. A *staged* receive is an
504        // ordinary primitive and takes the normal path.
505        if let (Mode::Apply { val }, Some(Cont::Prim1Finish { op: Prim1::Receive })) =
506            (&self.mode, self.kont.last())
507        {
508            if !matches!(val, Val::Code(_)) {
509                match h.mail.receive() {
510                    Some(msg) => {
511                        self.kont.pop();
512                        self.mode = Mode::Apply { val: msg };
513                        return Ok(Step::Running);
514                    }
515                    None => return Ok(Step::Blocked),
516                }
517            }
518        }
519
520        if let Mode::Apply { val } = &self.mode {
521            if self.kont.is_empty() {
522                return Ok(Step::Done(val.clone()));
523            }
524            let top = self.kont.last().expect("checked non-empty");
525            if matches!(val, Val::Raise(_)) && !receives(top) {
526                self.propagate(h);
527                return Ok(Step::Running);
528            }
529        }
530
531        let mode = std::mem::replace(&mut self.mode, Mode::Apply { val: Val::Nil });
532        let next = match mode {
533            Mode::Eval { env, exp } => self.eval(env, exp, h),
534            Mode::Apply { val } => {
535                let k = self.kont.pop().expect("checked non-empty");
536                self.resume(k, val, h)
537            }
538        };
539
540        match next {
541            Ok(mode) => {
542                self.mode = mode;
543                Ok(Step::Running)
544            }
545            Err(Fault::Throw(v)) => {
546                self.mode = Mode::Apply {
547                    val: super::raise(v),
548                };
549                Ok(Step::Running)
550            }
551            Err(bug) => Err(bug),
552        }
553    }
554
555    /// Carry a raise past one continuation (see [`receives`]). A frame that
556    /// opened a staging scope restores it, leaving no half-built block behind.
557    fn propagate<M: Mail>(&mut self, h: &mut Store<M>) {
558        match self.kont.pop().expect("checked non-empty") {
559            // The one frame that stops a raise.
560            Cont::CatchExit { saved } => {
561                saved.restore(h);
562                if let Mode::Apply { val: Val::Raise(v) } = &self.mode {
563                    self.mode = Mode::Apply { val: Val::clone(v) };
564                }
565            }
566            Cont::ReifyVExit { saved }
567            | Cont::ForceCode {
568                then: Then::Drain { saved, .. },
569                ..
570            } => saved.restore(h),
571            Cont::RunNowExit { saved_level } => h.level = saved_level,
572            _ => {}
573        }
574    }
575
576    pub fn run<M: Mail>(mut self, h: &mut Store<M>) -> Result<Val, Fault> {
577        loop {
578            match self.step(h)? {
579                Step::Running => {}
580                Step::Done(v) => return Ok(v),
581                Step::Blocked => return Err(throw("blocked", Val::Sym("no-scheduler".into()))),
582            }
583        }
584    }
585
586    /// One turn: step until the machine blocks, finishes, or spends the budget.
587    /// `Blocked` is returned once, on the edge, so the scheduler never polls.
588    pub fn turn<M: Mail>(
589        &mut self,
590        h: &mut Store<M>,
591        budget: usize,
592    ) -> Result<(Step, usize), Fault> {
593        for spent in 0..budget {
594            match self.step(h)? {
595                Step::Running => {}
596                other => return Ok((other, spent + 1)),
597            }
598        }
599        Ok((Step::Running, budget))
600    }
601
602    /// A subexpression needing no frame: nothing to push, no step spent.
603    /// Declines raises - a frame not pushed cannot be asked what it [`receives`].
604    fn direct(exp: &RcExp, env: &Env) -> Option<Val> {
605        let v = match &**exp {
606            Exp::Lit(n) => Val::Num(*n),
607            Exp::Flo(x) => Val::Flo(*x),
608            Exp::Sym(s) => Val::Sym(s.clone()),
609            Exp::Atom(a) => Val::Atom(a.clone()),
610            Exp::Nil => Val::Nil,
611            Exp::Proc(v) => Val::clone(v),
612            // An unbound index declines, so the ordinary path reports it.
613            Exp::Var(i) => env.get(*i)?.clone(),
614            Exp::Lam(_, _) => Val::Clo(env.clone(), RcExp::clone(exp)),
615            _ => return None,
616        };
617        (!matches!(v, Val::Raise(_))).then_some(v)
618    }
619
620    /// An all-direct primitive application, valid only where the frame
621    /// [`receives`]: running it may already have emitted, so it cannot decline.
622    fn direct_prim<M: Mail>(
623        exp: &RcExp,
624        env: &Env,
625        h: &mut Store<M>,
626    ) -> Option<Result<Val, Fault>> {
627        Some(match &**exp {
628            Exp::Prim1(op, e) if !matches!(op, Prim1::Receive) => {
629                prim1(*op, Machine::direct(e, env)?, h)
630            }
631            Exp::Prim2(op, a, b) => {
632                let x = Machine::direct(a, env)?;
633                let y = Machine::direct(b, env)?;
634                prim2(*op, x, y, h)
635            }
636            _ => return None,
637        })
638    }
639
640    fn eval<M: Mail>(&mut self, env: Env, exp: RcExp, h: &mut Store<M>) -> Result<Mode, Fault> {
641        let val = match &*exp {
642            Exp::Lit(n) => Val::Num(*n),
643            Exp::Flo(x) => Val::Flo(*x),
644            Exp::Sym(s) => Val::Sym(s.clone()),
645            Exp::Atom(a) => Val::Atom(a.clone()),
646            Exp::Nil => Val::Nil,
647            Exp::Proc(v) => Val::clone(v),
648
649            Exp::Var(i) => match env.get(*i) {
650                Some(v) => v.clone(),
651                None => return Err(throw("unbound", Val::Num(*i as i64))),
652            },
653
654            // Closures from one node share the `Rc`, which is how repeated
655            // lifts of the same closure recognise each other.
656            Exp::Lam(_, _) => Val::Clo(env, RcExp::clone(&exp)),
657
658            // `receive` is excluded: blocking is recognised by the frame this
659            // fast path would not push (see the head of `step`).
660            Exp::Prim1(op, e) => {
661                if !matches!(op, Prim1::Receive) {
662                    if let Some(v) = Machine::direct(e, &env) {
663                        return Ok(Mode::Apply {
664                            val: prim1(*op, v, h)?,
665                        });
666                    }
667                }
668                self.kont.push(Cont::Prim1Finish { op: *op });
669                return Ok(Mode::Eval {
670                    env,
671                    exp: RcExp::clone(e),
672                });
673            }
674
675            Exp::Prim2(op, a, b) => {
676                if let Some(x) = Machine::direct(a, &env) {
677                    return Ok(match Machine::direct(b, &env) {
678                        Some(y) => Mode::Apply {
679                            val: prim2(*op, x, y, h)?,
680                        },
681                        None => {
682                            self.kont.push(Cont::Prim2Finish { op: *op, v1: x });
683                            Mode::Eval {
684                                env,
685                                exp: RcExp::clone(b),
686                            }
687                        }
688                    });
689                }
690                self.kont.push(Cont::Prim2Right {
691                    env: env.clone(),
692                    op: *op,
693                    e2: RcExp::clone(b),
694                });
695                return Ok(Mode::Eval {
696                    env,
697                    exp: RcExp::clone(a),
698                });
699            }
700
701            // `done` is built to be the callee's frame: the operator first,
702            // because a closure binds itself, then the arguments.
703            Exp::App(f, args) => {
704                let mut done = Vec::with_capacity(args.len() + 1);
705                match Machine::direct(f, &env) {
706                    Some(fv) => done.push(fv),
707                    None => {
708                        self.kont.push(Cont::AppArgs {
709                            env: env.clone(),
710                            args: args.clone(),
711                            done,
712                        });
713                        return Ok(Mode::Eval {
714                            env,
715                            exp: RcExp::clone(f),
716                        });
717                    }
718                }
719                // An argument's frame [`receives`], so a primitive one needs no
720                // frame either; the operator is a value and cannot have emitted.
721                while let Some(a) = args.get(done.len() - 1) {
722                    match Machine::direct(a, &env) {
723                        Some(v) => done.push(v),
724                        None => match Machine::direct_prim(a, &env, h) {
725                            Some(Ok(v)) => done.push(v),
726                            Some(Err(fault)) => {
727                                self.kont.push(Cont::AppArgs {
728                                    env,
729                                    args: args.clone(),
730                                    done,
731                                });
732                                return Err(fault);
733                            }
734                            None => break,
735                        },
736                    }
737                }
738                if done.len() == args.len() + 1 {
739                    return self.enter(done, h);
740                }
741                let exp = RcExp::clone(&args[done.len() - 1]);
742                self.kont.push(Cont::AppArgs {
743                    env: env.clone(),
744                    args: args.clone(),
745                    done,
746                });
747                return Ok(Mode::Eval { env, exp });
748            }
749
750            // Both operands are inspected rather than bound, so neither
751            // position [`receives`] and neither may take a fast path.
752            Exp::Apply(f, args) => {
753                self.kont.push(Cont::ApplyArgs {
754                    env: env.clone(),
755                    args: RcExp::clone(args),
756                });
757                return Ok(Mode::Eval {
758                    env,
759                    exp: RcExp::clone(f),
760                });
761            }
762
763            Exp::Op(idx, args) => {
764                let mut done = Vec::with_capacity(args.len());
765                while let Some(v) = args.get(done.len()).and_then(|e| Machine::direct(e, &env)) {
766                    done.push(v);
767                }
768                if done.len() == args.len() {
769                    return self.run_op(*idx, done, h);
770                }
771                let exp = RcExp::clone(&args[done.len()]);
772                self.kont.push(Cont::OpArgs {
773                    idx: *idx,
774                    env: env.clone(),
775                    args: args.clone(),
776                    next: done.len() + 1,
777                    done,
778                });
779                return Ok(Mode::Eval { env, exp });
780            }
781
782            // A staged condition still needs its frame: both arms are then
783            // compiled, each under its own block.
784            Exp::If(c, t, e) => {
785                if let Some(v) = Machine::direct(c, &env) {
786                    if !matches!(v, Val::Code(_)) {
787                        return Ok(Mode::Eval {
788                            env,
789                            exp: match v {
790                                Val::Num(0) | Val::Nil => RcExp::clone(e),
791                                _ => RcExp::clone(t),
792                            },
793                        });
794                    }
795                    self.kont.push(Cont::IfDispatch {
796                        env: env.clone(),
797                        then_: RcExp::clone(t),
798                        else_: RcExp::clone(e),
799                    });
800                    return Ok(Mode::Apply { val: v });
801                }
802                self.kont.push(Cont::IfDispatch {
803                    env: env.clone(),
804                    then_: RcExp::clone(t),
805                    else_: RcExp::clone(e),
806                });
807                return Ok(Mode::Eval {
808                    env,
809                    exp: RcExp::clone(c),
810                });
811            }
812
813            // `LetBody` receives a raise, so a primitive init needs no frame.
814            Exp::Let(init, body) => {
815                let bound = match Machine::direct(init, &env) {
816                    Some(v) => Some(v),
817                    None => match Machine::direct_prim(init, &env, h) {
818                        Some(Ok(v)) => Some(v),
819                        // The throw has to land where it would have, so the
820                        // frame goes on before the fault is let out.
821                        Some(Err(f)) => {
822                            self.kont.push(Cont::LetBody {
823                                env,
824                                body: RcExp::clone(body),
825                            });
826                            return Err(f);
827                        }
828                        None => None,
829                    },
830                };
831                if let Some(v) = bound {
832                    return Ok(Mode::Eval {
833                        env: env.push_owned(v),
834                        exp: RcExp::clone(body),
835                    });
836                }
837                self.kont.push(Cont::LetBody {
838                    env: env.clone(),
839                    body: RcExp::clone(body),
840                });
841                return Ok(Mode::Eval {
842                    env,
843                    exp: RcExp::clone(init),
844                });
845            }
846
847            Exp::Catch(body) => {
848                let saved = Scope::save(h);
849                self.kont.push(Cont::CatchExit { saved });
850                return Ok(Mode::Eval {
851                    env,
852                    exp: RcExp::clone(body),
853                });
854            }
855
856            Exp::Throw(e) => {
857                self.kont.push(Cont::ThrowFinish);
858                return Ok(Mode::Eval {
859                    env,
860                    exp: RcExp::clone(e),
861                });
862            }
863
864            Exp::Lift(e) => {
865                self.kont.push(Cont::LiftFinish);
866                return Ok(Mode::Eval {
867                    env,
868                    exp: RcExp::clone(e),
869                });
870            }
871
872            Exp::LiftFun(n, f) => {
873                self.kont.push(Cont::LiftFunClo {
874                    env: env.clone(),
875                    f: RcExp::clone(f),
876                });
877                return Ok(Mode::Eval {
878                    env,
879                    exp: RcExp::clone(n),
880                });
881            }
882
883            // Not a `Prim2`: the dispatch on the first operand happens before
884            // the second is evaluated, since a staged first operand means the
885            // second must be evaluated inside a reify scope.
886            Exp::LiftRef(a, b) => {
887                self.kont.push(Cont::LiftRefDispatch {
888                    env: env.clone(),
889                    e2: RcExp::clone(b),
890                });
891                return Ok(Mode::Eval {
892                    env,
893                    exp: RcExp::clone(a),
894                });
895            }
896
897            Exp::Run(b, e) => {
898                self.kont.push(Cont::RunDispatch {
899                    env: env.clone(),
900                    e: RcExp::clone(e),
901                });
902                return Ok(Mode::Eval {
903                    env,
904                    exp: RcExp::clone(b),
905                });
906            }
907
908            Exp::IsCode(a, b) => {
909                self.kont.push(Cont::IsCodeRight {
910                    env: env.clone(),
911                    e2: RcExp::clone(b),
912                });
913                return Ok(Mode::Eval {
914                    env,
915                    exp: RcExp::clone(a),
916                });
917            }
918
919            Exp::Evalms(a, b) => {
920                self.kont.push(Cont::EvalmsRight {
921                    env: env.clone(),
922                    e2: RcExp::clone(b),
923                });
924                return Ok(Mode::Eval {
925                    env,
926                    exp: RcExp::clone(a),
927                });
928            }
929        };
930        Ok(Mode::Apply { val })
931    }
932
933    fn resume<M: Mail>(&mut self, k: Cont, val: Val, h: &mut Store<M>) -> Result<Mode, Fault> {
934        match k {
935            Cont::Prim1Finish { op } => Ok(Mode::Apply {
936                val: prim1(op, val, h)?,
937            }),
938
939            Cont::Prim2Right { env, op, e2 } => {
940                self.kont.push(Cont::Prim2Finish { op, v1: val });
941                Ok(Mode::Eval { env, exp: e2 })
942            }
943
944            Cont::Prim2Finish { op, v1 } => Ok(Mode::Apply {
945                val: prim2(op, v1, val, h)?,
946            }),
947
948            Cont::AppArgs {
949                env,
950                args,
951                mut done,
952            } => {
953                done.push(val);
954                if done.len() == args.len() + 1 {
955                    return self.enter(done, h);
956                }
957                let exp = RcExp::clone(&args[done.len() - 1]);
958                self.kont.push(Cont::AppArgs {
959                    env: env.clone(),
960                    args,
961                    done,
962                });
963                Ok(Mode::Eval { env, exp })
964            }
965
966            Cont::ApplyArgs { env, args } => {
967                self.kont.push(Cont::ApplyFinish { f: val });
968                Ok(Mode::Eval { env, exp: args })
969            }
970
971            Cont::ApplyFinish { f } => {
972                let mut done = vec![f];
973                let mut rest = val;
974                loop {
975                    match rest {
976                        Val::Nil => break,
977                        Val::Pair(a, d) => {
978                            done.push(RcVal::unwrap_or_clone(a));
979                            rest = RcVal::unwrap_or_clone(d);
980                        }
981                        _ => return Err(wrong_type("apply")),
982                    }
983                }
984                self.enter(done, h)
985            }
986
987            Cont::OpArgs {
988                idx,
989                env,
990                args,
991                next,
992                mut done,
993            } => {
994                done.push(val);
995                if next == args.len() {
996                    return self.run_op(idx, done, h);
997                }
998                let exp = RcExp::clone(&args[next]);
999                self.kont.push(Cont::OpArgs {
1000                    idx,
1001                    env: env.clone(),
1002                    args,
1003                    next: next + 1,
1004                    done,
1005                });
1006                Ok(Mode::Eval { env, exp })
1007            }
1008
1009            // Each arm gets its own block: a statement emitted inside one arm
1010            // may not escape into the surrounding sequence.
1011            Cont::IfDispatch { env, then_, else_ } => match val {
1012                Val::Code(c) => {
1013                    let saved = Scope::save(h);
1014                    self.kont.push(Cont::ForceCode {
1015                        mode: ForceMode::Scalar,
1016                        then: Then::Drain {
1017                            saved,
1018                            then: Reify::IfTrue {
1019                                c,
1020                                env: env.clone(),
1021                                f: else_,
1022                            },
1023                        },
1024                    });
1025                    Ok(Mode::Eval { env, exp: then_ })
1026                }
1027                Val::Num(0) | Val::Nil => Ok(Mode::Eval { env, exp: else_ }),
1028                _ => Ok(Mode::Eval { env, exp: then_ }),
1029            },
1030
1031            Cont::LetBody { env, body } => Ok(Mode::Eval {
1032                env: env.push_owned(val),
1033                exp: body,
1034            }),
1035
1036            // Optimistically a reify scope: a body that staged nothing produces
1037            // its value directly, one that staged anything residualizes the
1038            // `catch`, so error handling survives compilation.
1039            Cont::CatchExit { saved } => {
1040                if h.block.is_empty() && !matches!(val, Val::Code(_)) {
1041                    saved.restore(h);
1042                    return Ok(Mode::Apply { val });
1043                }
1044                // A body that staged an effect can still end in a plain value,
1045                // so the tail is lifted rather than required to be code.
1046                self.kont.push(Cont::ForceCode {
1047                    mode: ForceMode::Lift,
1048                    then: Then::Drain {
1049                        saved,
1050                        then: Reify::CatchDone,
1051                    },
1052                });
1053                Ok(Mode::Apply { val })
1054            }
1055
1056            // Throwing a code value residualizes the throw rather than
1057            // performing one at staging time.
1058            Cont::ThrowFinish => match val {
1059                Val::Code(e) => Ok(Mode::Apply {
1060                    val: h.reflectc(Exp::Throw(e)),
1061                }),
1062                v => Ok(Mode::Apply {
1063                    val: super::raise(v),
1064                }),
1065            },
1066
1067            // ── staging ────────────────────────────────────────────────
1068
1069            // Only a closure needs the continuation stack; the rest force
1070            // synchronously.
1071            Cont::LiftFunClo { env, f } => {
1072                let arity = match val {
1073                    Val::Num(n) => u16::try_from(n).map_err(|_| wrong_type("lift-fun"))?,
1074                    _ => return Err(wrong_type("lift-fun")),
1075                };
1076                self.kont.push(Cont::LiftFunGo { arity });
1077                Ok(Mode::Eval { env, exp: f })
1078            }
1079
1080            // The same eta-expansion a closure lift does, but with the frame
1081            // handed over as a list: the expansion is a floor closure of one
1082            // argument whatever arity it is writing.
1083            Cont::LiftFunGo { arity } => {
1084                let saved = Scope::save(h);
1085                // Allocated in the order the body indexes them, the closure's
1086                // own name first - which is the one thing the body could not
1087                // otherwise find out about a residual variable.
1088                h.register_lam(h.fresh);
1089                let vars: Vec<Val> = (0..=arity).map(|_| code(Exp::Var(h.fresh_var()))).collect();
1090                let frame = vars
1091                    .into_iter()
1092                    .rev()
1093                    .fold(Val::Nil, |t, v| Val::Pair(rc_val(v), rc_val(t)));
1094                self.kont.push(Cont::LiftCloFinish {
1095                    arity,
1096                    then: Then::WrapAsCode,
1097                });
1098                // Scalar, unlike the `lift` of a floor closure below: an object
1099                // function that answers with a constant is ordinary.
1100                self.kont.push(Cont::ForceCode {
1101                    mode: ForceMode::Scalar,
1102                    then: Then::Drain {
1103                        saved,
1104                        then: Reify::Produce,
1105                    },
1106                });
1107                self.enter(vec![val, frame], h)
1108            }
1109
1110            Cont::LiftFinish => {
1111                self.kont.push(Cont::ForceCode {
1112                    mode: ForceMode::Lift,
1113                    then: Then::WrapAsCode,
1114                });
1115                Ok(Mode::Apply { val })
1116            }
1117
1118            Cont::ForceCode { mode, then } => self.force(mode, then, val, h),
1119
1120            Cont::LiftCloFinish { arity, then } => {
1121                let body = match val {
1122                    Val::Code(e) => RcExp::unwrap_or_clone(e),
1123                    _ => return Err(wrong_type("lift")),
1124                };
1125                // The scope restore put `fresh` back to the level this closure
1126                // was registered at, so this reflect allocates that level.
1127                let produced = h.reflect(Exp::Lam(arity, rc_exp(body)));
1128                // Again, for the scope outside: the registration made before the
1129                // body was staged went out with the restore that closed it.
1130                if let Exp::Var(n) = produced {
1131                    h.register_lam(n);
1132                }
1133                self.dispatch_then(then, produced, h)
1134            }
1135
1136            Cont::ReifyVExit { saved } => {
1137                let stmts = std::mem::take(&mut h.block);
1138                let out = if stmts.is_empty() {
1139                    val
1140                } else {
1141                    match val {
1142                        Val::Code(e) => code(drain(stmts, RcExp::unwrap_or_clone(e))),
1143                        _ => {
1144                            return Err(Fault::Bug(
1145                                "reify: non-empty block with a non-code result".into(),
1146                            ))
1147                        }
1148                    }
1149                };
1150                saved.restore(h);
1151                Ok(Mode::Apply { val: out })
1152            }
1153
1154            Cont::RunNowExit { saved_level } => {
1155                h.level = saved_level;
1156                Ok(Mode::Apply { val })
1157            }
1158
1159            Cont::LiftRefDispatch { env, e2 } => match val {
1160                Val::Code(s1) => {
1161                    let saved = Scope::save(h);
1162                    self.kont.push(Cont::ForceCode {
1163                        mode: ForceMode::Lift,
1164                        then: Then::Drain {
1165                            saved,
1166                            then: Reify::LiftRefDone { s1 },
1167                        },
1168                    });
1169                    Ok(Mode::Eval { env, exp: e2 })
1170                }
1171                _ => {
1172                    self.kont.push(Cont::LiftRefPersist);
1173                    Ok(Mode::Eval { env, exp: e2 })
1174                }
1175            },
1176
1177            // Cross-stage persistence is by reference, never structural, so a
1178            // free closure keeps its identity instead of being η-recompiled -
1179            // which is why it must be a whole value, refused here where the
1180            // program still says which operand it was.
1181            Cont::LiftRefPersist => {
1182                // The tag, not the value: what fails here is a graph with enough
1183                // sharing to have no printed form, so carrying it out would
1184                // trade a diagnosis for a hang.
1185                if has_holes(&val, h) {
1186                    return Err(throw("unpersistable", Val::Sym(kind_of(&val).into())));
1187                }
1188                Ok(Mode::Apply {
1189                    val: Val::Code(rc_exp(Exp::Proc(rc_val(val)))),
1190                })
1191            }
1192
1193            Cont::RunDispatch { env, e } => match val {
1194                Val::Code(b) => {
1195                    let saved = Scope::save(h);
1196                    self.kont.push(Cont::ForceCode {
1197                        mode: ForceMode::Strict,
1198                        then: Then::Drain {
1199                            saved,
1200                            then: Reify::RunStaged { b },
1201                        },
1202                    });
1203                    Ok(Mode::Eval { env, exp: e })
1204                }
1205                // Compile under an inner scope, then execute what it produced
1206                // under an outer one. Three continuations, firing inside out.
1207                _ => {
1208                    let env_len = env.len();
1209                    self.kont.push(Cont::RunNowExit {
1210                        saved_level: h.level,
1211                    });
1212                    h.level += 1;
1213
1214                    let saved_outer = Scope::save(h);
1215                    self.kont.push(Cont::ReifyVExit { saved: saved_outer });
1216
1217                    let saved_inner = Scope::save(h);
1218                    self.kont.push(Cont::ForceCode {
1219                        mode: ForceMode::Strict,
1220                        then: Then::Drain {
1221                            saved: saved_inner,
1222                            then: Reify::RunNowInner { env: env.clone() },
1223                        },
1224                    });
1225
1226                    // The compiled program is closed over this environment, so
1227                    // its residual variables start above the bindings in scope.
1228                    h.fresh = env_len;
1229                    Ok(Mode::Eval { env, exp: e })
1230                }
1231            },
1232
1233            Cont::IsCodeRight { env, e2 } => {
1234                self.kont.push(Cont::IsCodeFinish { v1: val });
1235                Ok(Mode::Eval { env, exp: e2 })
1236            }
1237
1238            Cont::IsCodeFinish { v1 } => match v1 {
1239                Val::Code(s1) => {
1240                    self.kont.push(Cont::ForceCode {
1241                        mode: ForceMode::Strict,
1242                        then: Then::IsCodeStaged { s1 },
1243                    });
1244                    Ok(Mode::Apply { val })
1245                }
1246                _ => Ok(Mode::Apply {
1247                    val: bool_val(matches!(val, Val::Code(_))),
1248                }),
1249            },
1250
1251            Cont::EvalmsRight { env, e2 } => {
1252                self.kont.push(Cont::EvalmsFinish { env_val: val });
1253                Ok(Mode::Eval { env, exp: e2 })
1254            }
1255
1256            Cont::EvalmsFinish { env_val } => {
1257                let env = env_from_list(&env_val)?;
1258                let exp = match val {
1259                    Val::Code(e) => e,
1260                    _ => return Err(wrong_type("evalms")),
1261                };
1262                let saved = Scope::save(h);
1263                self.kont.push(Cont::ReifyVExit { saved });
1264                Ok(Mode::Eval { env, exp })
1265            }
1266        }
1267    }
1268
1269    /// Coerce a value to syntax. Every case but a closure is immediate; a
1270    /// closure η-expands through the continuation stack, so lifting a deeply
1271    /// recursive function cannot overflow.
1272    fn force<M: Mail>(
1273        &mut self,
1274        mode: ForceMode,
1275        then: Then,
1276        val: Val,
1277        h: &mut Store<M>,
1278    ) -> Result<Mode, Fault> {
1279        if let ForceMode::Strict | ForceMode::Scalar = mode {
1280            let immediate = match (mode, &val) {
1281                // The one structure this mode builds: a region answering with a
1282                // pair is answering with a result, not leaking an artefact.
1283                (ForceMode::Scalar, Val::Pair(_, _)) => {
1284                    Some(rc_exp(cons_syntax(&val, "unreturnable", h)?))
1285                }
1286                (ForceMode::Scalar, v) => as_syntax(v),
1287                (_, Val::Code(e)) => Some(RcExp::clone(e)),
1288                _ => None,
1289            };
1290            return match immediate {
1291                Some(e) => {
1292                    let produced = RcExp::unwrap_or_clone(e);
1293                    self.dispatch_then(then, produced, h)
1294                }
1295                // Nothing is named at a scalar boundary - the region merely
1296                // ended - so the refusal is named for the boundary instead.
1297                None if mode == ForceMode::Scalar => {
1298                    Err(throw("unreturnable", Val::Sym(kind_of(&val).into())))
1299                }
1300                None => Err(wrong_type("force-code")),
1301            };
1302        }
1303        match val {
1304            Val::Code(e) => {
1305                let produced = RcExp::unwrap_or_clone(e);
1306                self.dispatch_then(then, produced, h)
1307            }
1308            Val::Num(n) => self.dispatch_then(then, Exp::Lit(n), h),
1309            Val::Flo(x) => self.dispatch_then(then, Exp::Flo(x), h),
1310            Val::Sym(s) => self.dispatch_then(then, Exp::Sym(s), h),
1311            Val::Atom(a) => self.dispatch_then(then, Exp::Atom(a), h),
1312            Val::Nil => self.dispatch_then(then, Exp::Nil, h),
1313
1314            // Folding a cell would freeze its contents at staging time. Cell
1315            // *operations* residualize; the cell itself is not syntax.
1316            Val::Cell(_) => Err(throw("unliftable", Val::Sym("cell".into()))),
1317
1318            // Unreachable in practice - `step` propagates past a frame that
1319            // inspects - but coercing a raise to syntax would lose it.
1320            v @ Val::Raise(_) => Ok(Mode::Apply { val: v }),
1321
1322            Val::Pair(a, b) => {
1323                let produced = cons_syntax(&Val::Pair(a, b), "unliftable", h)?;
1324                self.dispatch_then(then, produced, h)
1325            }
1326
1327            Val::Clo(cenv, lam) => {
1328                let Exp::Lam(arity, body) = &*lam else {
1329                    return Err(Fault::Bug(format!("closure over {lam}")));
1330                };
1331                let (arity, body) = (*arity, RcExp::clone(body));
1332                // Already expanded in this scope: resolving to the variable
1333                // naming it is what stops a recursive lift.
1334                if let Some(n) = h.find_fun(&cenv, &lam) {
1335                    return self.dispatch_then(then, Exp::Var(n), h);
1336                }
1337                let level = h.fresh;
1338                h.register_fun(level, &cenv, &lam);
1339
1340                let saved = Scope::save(h);
1341                // One variable for the closure's own name, then one per
1342                // parameter, in the order the body indexes them.
1343                let mut frame = Vec::with_capacity(usize::from(arity) + 1);
1344                for _ in 0..=arity {
1345                    frame.push(code(Exp::Var(h.fresh_var())));
1346                }
1347                let env = cenv.frame(frame);
1348
1349                self.kont.push(Cont::LiftCloFinish { arity, then });
1350                // Strict: a lifted closure's body must itself produce code.
1351                // `(lift (lambda 5))` is an error - lift the 5.
1352                self.kont.push(Cont::ForceCode {
1353                    mode: ForceMode::Strict,
1354                    then: Then::Drain {
1355                        saved,
1356                        then: Reify::Produce,
1357                    },
1358                });
1359                Ok(Mode::Eval { env, exp: body })
1360            }
1361        }
1362    }
1363
1364    fn dispatch_then<M: Mail>(
1365        &mut self,
1366        then: Then,
1367        produced: Exp,
1368        h: &mut Store<M>,
1369    ) -> Result<Mode, Fault> {
1370        match then {
1371            Then::WrapAsCode => Ok(Mode::Apply {
1372                val: code(produced),
1373            }),
1374            Then::IsCodeStaged { s1 } => Ok(Mode::Apply {
1375                val: h.reflectc(Exp::IsCode(s1, rc_exp(produced))),
1376            }),
1377            Then::Drain { saved, then } => {
1378                let stmts = std::mem::take(&mut h.block);
1379                let built = drain(stmts, produced);
1380                saved.restore(h);
1381                self.dispatch_reify(then, built, h)
1382            }
1383        }
1384    }
1385
1386    fn dispatch_reify<M: Mail>(
1387        &mut self,
1388        then: Reify,
1389        built: Exp,
1390        h: &mut Store<M>,
1391    ) -> Result<Mode, Fault> {
1392        match then {
1393            Reify::IfTrue { c, env, f } => {
1394                let saved = Scope::save(h);
1395                self.kont.push(Cont::ForceCode {
1396                    mode: ForceMode::Scalar,
1397                    then: Then::Drain {
1398                        saved,
1399                        then: Reify::IfFalse {
1400                            c,
1401                            t: rc_exp(built),
1402                        },
1403                    },
1404                });
1405                Ok(Mode::Eval { env, exp: f })
1406            }
1407            Reify::IfFalse { c, t } => Ok(Mode::Apply {
1408                val: h.reflectc(Exp::If(c, t, rc_exp(built))),
1409            }),
1410            Reify::RunStaged { b } => Ok(Mode::Apply {
1411                val: h.reflectc(Exp::Run(b, rc_exp(built))),
1412            }),
1413            Reify::LiftRefDone { s1 } => Ok(Mode::Apply {
1414                val: h.reflectc(Exp::LiftRef(s1, rc_exp(built))),
1415            }),
1416            // The inner scope's code is executed here; its value flows to the
1417            // outer scope waiting below on the stack.
1418            Reify::RunNowInner { env } => Ok(Mode::Eval {
1419                env,
1420                exp: rc_exp(built),
1421            }),
1422            Reify::CatchDone => Ok(Mode::Apply {
1423                val: h.reflectc(Exp::Catch(rc_exp(built))),
1424            }),
1425            Reify::Produce => Ok(Mode::Apply { val: code(built) }),
1426        }
1427    }
1428
1429    /// Apply the operator at the head of `done` to the arguments behind it.
1430    /// `done` is already the frame the callee runs in - a closure binds itself
1431    /// then its parameters - so it becomes the environment chunk whole.
1432    fn enter<M: Mail>(&mut self, done: Vec<Val>, h: &mut Store<M>) -> Result<Mode, Fault> {
1433        let arity = done.len() - 1;
1434        match &done[0] {
1435            Val::Clo(cenv, lam) => {
1436                let Exp::Lam(n, body) = &**lam else {
1437                    return Err(Fault::Bug(format!("closure over {lam}")));
1438                };
1439                if usize::from(*n) != arity {
1440                    return Err(throw("arity", Val::Num(arity as i64)));
1441                }
1442                let (cenv, body) = (cenv.clone(), RcExp::clone(body));
1443                Ok(Mode::Eval {
1444                    env: cenv.frame(done),
1445                    exp: body,
1446                })
1447            }
1448
1449            // A code operator residualizes the whole application, so the
1450            // arguments join it as constants; anything with no syntactic form
1451            // is the type error. Same rule as the primitives and host ops.
1452            Val::Code(_) => {
1453                let mut parts = staged_operands(&done, "app").expect("operator is code")?;
1454                let f = parts.remove(0);
1455                Ok(Mode::Apply {
1456                    val: h.reflectc(Exp::App(f, parts.into())),
1457                })
1458            }
1459
1460            _ => Err(wrong_type("app")),
1461        }
1462    }
1463
1464    fn run_op<M: Mail>(
1465        &mut self,
1466        idx: u16,
1467        args: Vec<Val>,
1468        h: &mut Store<M>,
1469    ) -> Result<Mode, Fault> {
1470        let def = host::registry().get(idx);
1471        if args.len() != def.arity {
1472            return Err(throw("arity", Val::Sym(def.name.into())));
1473        }
1474        // The staging rule for the whole table, the same one the core
1475        // primitives follow: one staged operand stages the call, and the rest
1476        // join it as constants of the residual.
1477        if let Some(parts) = staged_operands(&args, def.name) {
1478            return Ok(Mode::Apply {
1479                val: h.reflectc(Exp::Op(idx, parts?)),
1480            });
1481        }
1482        match (def.run)(&args) {
1483            Ok(v) => Ok(Mode::Apply { val: v }),
1484            Err(v) => Err(Fault::Throw(v)),
1485        }
1486    }
1487}
1488
1489fn bool_val(b: bool) -> Val {
1490    Val::Num(if b { 1 } else { 0 })
1491}
1492
1493/// `evalms` takes its environment as an ordinary list, so a program can build
1494/// one. De Bruijn indices count from the front, the order the list has.
1495fn env_from_list(v: &Val) -> Result<Env, Fault> {
1496    let mut vals = Vec::new();
1497    let mut cur = v;
1498    loop {
1499        match cur {
1500            Val::Nil => return Ok(Env::new(vals)),
1501            Val::Pair(a, b) => {
1502                vals.push(Val::clone(a));
1503                cur = b;
1504            }
1505            _ => return Err(wrong_type("evalms")),
1506        }
1507    }
1508}
1509
1510/// Whether a frame takes delivery of a raise rather than passing it on. Two
1511/// ways to be on this list: a frame that only *binds* the value it resumes
1512/// with (so object code can be handed a raise and decide for itself), or one
1513/// that asks the question (`raise?`, `raise-value`). Everything else - operator
1514/// position, arithmetic operand, condition, lift - propagates.
1515fn receives(k: &Cont) -> bool {
1516    match k {
1517        // An argument is only bound, so a raise passes into the callee as an
1518        // ordinary value. An operator is inspected, so it does not.
1519        Cont::AppArgs { done, .. } => !done.is_empty(),
1520        Cont::LetBody { .. }
1521        | Cont::Prim1Finish {
1522            op: Prim1::IsRaise | Prim1::RaiseValue,
1523        } => true,
1524        _ => false,
1525    }
1526}
1527
1528fn prim1<M: Mail>(op: Prim1, v: Val, h: &mut Store<M>) -> Result<Val, Fault> {
1529    // Every predicate answers `false` for another type but residualizes for
1530    // code, since at staging time the type is unknown.
1531    macro_rules! pred {
1532        ($ctor:expr, $pat:pat) => {
1533            match v {
1534                Val::Code(e) => Ok(h.reflectc($ctor(e))),
1535                ref x => Ok(bool_val(matches!(x, $pat))),
1536            }
1537        };
1538    }
1539    match op {
1540        Prim1::IsNum => pred!(|e| Exp::Prim1(Prim1::IsNum, e), Val::Num(_) | Val::Flo(_)),
1541        Prim1::IsSym => pred!(|e| Exp::Prim1(Prim1::IsSym, e), Val::Sym(_)),
1542        Prim1::IsNil => pred!(|e| Exp::Prim1(Prim1::IsNil, e), Val::Nil),
1543        Prim1::IsPair => pred!(|e| Exp::Prim1(Prim1::IsPair, e), Val::Pair(_, _)),
1544
1545        Prim1::Car => match v {
1546            Val::Pair(a, _) => Ok(Val::clone(&a)),
1547            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::Car, e))),
1548            _ => Err(wrong_type("car")),
1549        },
1550        Prim1::Cdr => match v {
1551            Val::Pair(_, b) => Ok(Val::clone(&b)),
1552            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::Cdr, e))),
1553            _ => Err(wrong_type("cdr")),
1554        },
1555
1556        Prim1::CellNew => match v {
1557            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::CellNew, e))),
1558            v => {
1559                h.cells.push(v);
1560                Ok(Val::Cell(h.cells.len() - 1))
1561            }
1562        },
1563        Prim1::CellRead => match v {
1564            Val::Cell(i) => Ok(h.cells[i].clone()),
1565            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::CellRead, e))),
1566            _ => Err(wrong_type("cell-read")),
1567        },
1568
1569        // Deliberately exempt from the rule `send` obeys: a thunk stays a live
1570        // closure, so spawning is heap-local by construction - the child shares
1571        // the captured cells. Starting a task elsewhere is a message, not this.
1572        Prim1::Spawn => match v {
1573            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::Spawn, e))),
1574            v => h.mail.spawn(v).map_err(Fault::Throw),
1575        },
1576        Prim1::Monitor => match v {
1577            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::Monitor, e))),
1578            v => h.mail.monitor(&v).map_err(Fault::Throw),
1579        },
1580        // The watch cannot fail where the spawn succeeded: a child is heap-local
1581        // by construction, so its address routes locally.
1582        Prim1::SpawnMonitor => match v {
1583            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::SpawnMonitor, e))),
1584            v => {
1585                let child = h.mail.spawn(v).map_err(Fault::Throw)?;
1586                h.mail.monitor(&child).map_err(Fault::Throw)?;
1587                Ok(child)
1588            }
1589        },
1590        // Handled before the continuation is popped: an empty mailbox must
1591        // leave the machine resumable.
1592        Prim1::Receive => match v {
1593            Val::Code(e) => Ok(h.reflectc(Exp::Prim1(Prim1::Receive, e))),
1594            _ => Err(Fault::Bug("receive reached prim1".into())),
1595        },
1596
1597        // No code arm, deliberately: a residual is not a raise but an
1598        // expression that may produce one later, and saying so is what lets an
1599        // interpreter's raise test vanish during compilation.
1600        Prim1::IsRaise => Ok(bool_val(matches!(v, Val::Raise(_)))),
1601        Prim1::IsCodeFun => Ok(bool_val(matches!(&v, Val::Code(e) if h.is_lam(e)))),
1602        Prim1::RaiseValue => match v {
1603            Val::Raise(e) => Ok(Val::clone(&e)),
1604            _ => Err(wrong_type("raise-value")),
1605        },
1606    }
1607}
1608
1609fn prim2<M: Mail>(op: Prim2, v1: Val, v2: Val, h: &mut Store<M>) -> Result<Val, Fault> {
1610    // The one member of this enum with no code arm: a pair is run-time data, so
1611    // `cons` of two code values is a pair of them. Staging a pair is `lift`.
1612    if let Prim2::Cons = op {
1613        return Ok(Val::Pair(rc_val(v1), rc_val(v2)));
1614    }
1615
1616    if matches!((&v1, &v2), (Val::Code(_), _) | (_, Val::Code(_))) {
1617        let (Some(a), Some(b)) = (as_syntax(&v1), as_syntax(&v2)) else {
1618            return Err(wrong_type(op.name()));
1619        };
1620        return Ok(h.reflectc(Exp::Prim2(op, a, b)));
1621    }
1622
1623    match op {
1624        Prim2::Cons => unreachable!("handled above"),
1625
1626        Prim2::Eq => Ok(bool_val(v1 == v2)),
1627
1628        Prim2::Plus | Prim2::Minus | Prim2::Times | Prim2::Div | Prim2::Mod | Prim2::Lt => {
1629            arith(op, v1, v2)
1630        }
1631
1632        Prim2::CellSet => match v1 {
1633            Val::Cell(i) => {
1634                h.cells[i] = v2.clone();
1635                Ok(v2)
1636            }
1637            _ => Err(wrong_type("cell-set!")),
1638        },
1639
1640        Prim2::Send => match is_data(&v2) {
1641            true => h.mail.send(&v1, v2).map_err(Fault::Throw),
1642            false => Err(throw("not-data", v2)),
1643        },
1644
1645        Prim2::LimitTurns => match v2 {
1646            Val::Num(n) if n > 0 => h.mail.limit(&v1, n).map_err(Fault::Throw),
1647            _ => Err(wrong_type("limit-turns")),
1648        },
1649    }
1650}
1651
1652/// Whether a value is an s-expression, which is all a channel carries. A
1653/// closure ships the semantics it was made under and a cell index means nothing
1654/// outside its store, so restricting the payload here rather than in a
1655/// scheduler is what makes a local send observationally identical to a remote
1656/// one. Iterative, and terminating because a cycle needs a cell to tie it.
1657fn is_data(v: &Val) -> bool {
1658    let mut work = vec![v];
1659    while let Some(v) = work.pop() {
1660        match v {
1661            Val::Num(_) | Val::Flo(_) | Val::Sym(_) | Val::Atom(_) | Val::Nil => {}
1662            Val::Pair(a, b) => {
1663                work.push(a);
1664                work.push(b);
1665            }
1666            // A raise is in flight, not at rest: what crosses a channel is the
1667            // value it carries, unwrapped by whoever decided to report it.
1668            Val::Clo(_, _) | Val::Code(_) | Val::Cell(_) | Val::Raise(_) => return false,
1669        }
1670    }
1671    true
1672}
1673
1674fn arith(op: Prim2, v1: Val, v2: Val) -> Result<Val, Fault> {
1675    let name = match op {
1676        Prim2::Plus => "+",
1677        Prim2::Minus => "-",
1678        Prim2::Times => "*",
1679        Prim2::Div => "/",
1680        Prim2::Mod => "%",
1681        Prim2::Lt => "<",
1682        _ => unreachable!("not arithmetic"),
1683    };
1684    match (v1, v2) {
1685        (Val::Num(a), Val::Num(b)) => match op {
1686            Prim2::Plus => Ok(Val::Num(a.wrapping_add(b))),
1687            Prim2::Minus => Ok(Val::Num(a.wrapping_sub(b))),
1688            Prim2::Times => Ok(Val::Num(a.wrapping_mul(b))),
1689            Prim2::Lt => Ok(bool_val(a < b)),
1690            Prim2::Div | Prim2::Mod if b == 0 => {
1691                Err(throw("divide-by-zero", Val::Sym(name.into())))
1692            }
1693            Prim2::Div => Ok(Val::Num(a.wrapping_div(b))),
1694            Prim2::Mod => Ok(Val::Num(a.wrapping_rem(b))),
1695            _ => unreachable!("not arithmetic"),
1696        },
1697        // Mixed integer and float widens, the only place the core's second
1698        // numeric type costs anything.
1699        (a, b) if is_num(&a) && is_num(&b) => {
1700            let (x, y) = (as_f64(&a), as_f64(&b));
1701            match op {
1702                Prim2::Plus => Ok(Val::Flo(x + y)),
1703                Prim2::Minus => Ok(Val::Flo(x - y)),
1704                Prim2::Times => Ok(Val::Flo(x * y)),
1705                Prim2::Div => Ok(Val::Flo(x / y)),
1706                Prim2::Mod => Ok(Val::Flo(x % y)),
1707                Prim2::Lt => Ok(bool_val(x < y)),
1708                _ => unreachable!("not arithmetic"),
1709            }
1710        }
1711        _ => Err(wrong_type(name)),
1712    }
1713}
1714
1715fn is_num(v: &Val) -> bool {
1716    matches!(v, Val::Num(_) | Val::Flo(_))
1717}
1718
1719fn as_f64(v: &Val) -> f64 {
1720    match v {
1721        Val::Num(n) => *n as f64,
1722        Val::Flo(x) => *x,
1723        _ => unreachable!("checked by is_num"),
1724    }
1725}
1726
1727#[cfg(test)]
1728mod tests {
1729    use super::*;
1730    use crate::floor::host::Str;
1731    use crate::floor::{rc_exp, Env};
1732
1733    fn eval(e: Exp) -> Result<Val, Fault> {
1734        Machine::new(Env::default(), rc_exp(e)).run(&mut Store::<NoMail>::default())
1735    }
1736
1737    fn lam(body: Exp) -> Exp {
1738        Exp::Lam(1, rc_exp(body))
1739    }
1740    fn app(f: Exp, a: Exp) -> Exp {
1741        Exp::App(rc_exp(f), Box::new([rc_exp(a)]))
1742    }
1743    fn p1(op: Prim1, a: Exp) -> Exp {
1744        Exp::Prim1(op, rc_exp(a))
1745    }
1746    fn p2(op: Prim2, a: Exp, b: Exp) -> Exp {
1747        Exp::Prim2(op, rc_exp(a), rc_exp(b))
1748    }
1749    fn if_(c: Exp, t: Exp, f: Exp) -> Exp {
1750        Exp::If(rc_exp(c), rc_exp(t), rc_exp(f))
1751    }
1752
1753    #[test]
1754    fn arithmetic_widens_only_when_an_operand_is_float() {
1755        assert_eq!(
1756            eval(p2(Prim2::Plus, Exp::Lit(2), Exp::Lit(3))),
1757            Ok(Val::Num(5))
1758        );
1759        assert_eq!(
1760            eval(p2(Prim2::Plus, Exp::Lit(2), Exp::Flo(0.5))),
1761            Ok(Val::Flo(2.5))
1762        );
1763        assert_eq!(
1764            eval(p2(Prim2::Div, Exp::Lit(7), Exp::Lit(2))),
1765            Ok(Val::Num(3))
1766        );
1767    }
1768
1769    #[test]
1770    fn a_closure_receives_itself_so_recursion_needs_no_fixed_point() {
1771        // (lambda self n. if n < 1 then 1 else n * (self (n - 1))) 5
1772        let n = Exp::Var(1);
1773        let me = Exp::Var(0);
1774        let fact = lam(if_(
1775            p2(Prim2::Lt, n.clone(), Exp::Lit(1)),
1776            Exp::Lit(1),
1777            p2(
1778                Prim2::Times,
1779                n.clone(),
1780                app(me, p2(Prim2::Minus, n, Exp::Lit(1))),
1781            ),
1782        ));
1783        assert_eq!(eval(app(fact, Exp::Lit(5))), Ok(Val::Num(120)));
1784    }
1785
1786    #[test]
1787    fn a_cell_written_inside_an_abandoned_catch_keeps_its_write() {
1788        // (let c (cell-new 1)
1789        //   (let _ (catch (let _ (cell-set! c 2) (throw 'boom)))
1790        //     (cell-read c)))
1791        //
1792        // Unwinding is not a transaction: staging state rolls back,
1793        // effects do not.
1794        let body = Exp::Let(
1795            rc_exp(Exp::Catch(rc_exp(Exp::Let(
1796                rc_exp(p2(Prim2::CellSet, Exp::Var(0), Exp::Lit(2))),
1797                rc_exp(Exp::Throw(rc_exp(Exp::Sym("boom".into())))),
1798            )))),
1799            rc_exp(p1(Prim1::CellRead, Exp::Var(0))),
1800        );
1801        let prog = Exp::Let(rc_exp(p1(Prim1::CellNew, Exp::Lit(1))), rc_exp(body));
1802
1803        let mut h = Store::<NoMail>::default();
1804        let got = Machine::new(Env::default(), rc_exp(prog)).run(&mut h);
1805        assert_eq!(got, Ok(Val::Num(2)));
1806        assert!(h.block.is_empty());
1807        assert_eq!(h.fresh, 0);
1808    }
1809
1810    #[test]
1811    fn catch_yields_the_thrown_value() {
1812        assert_eq!(
1813            eval(Exp::Catch(rc_exp(Exp::Throw(rc_exp(Exp::Sym(
1814                "boom".into()
1815            )))))),
1816            Ok(Val::Sym("boom".into()))
1817        );
1818    }
1819
1820    #[test]
1821    fn an_uncaught_throw_is_the_answer_rather_than_a_failure() {
1822        assert_eq!(
1823            eval(Exp::Throw(rc_exp(Exp::Sym("boom".into())))),
1824            Ok(super::super::raise(Val::Sym("boom".into())))
1825        );
1826    }
1827
1828    #[test]
1829    fn a_raise_passed_as_an_argument_is_an_ordinary_value() {
1830        assert_eq!(
1831            eval(Exp::App(
1832                rc_exp(Exp::Lam(1, rc_exp(Exp::Lit(7)))),
1833                Box::new([rc_exp(Exp::Throw(rc_exp(Exp::Sym("boom".into()))))])
1834            )),
1835            Ok(Val::Num(7))
1836        );
1837    }
1838
1839    #[test]
1840    fn a_raise_propagates_through_a_primitive() {
1841        assert_eq!(
1842            eval(p2(
1843                Prim2::Plus,
1844                Exp::Lit(1),
1845                Exp::Throw(rc_exp(Exp::Sym("boom".into())))
1846            )),
1847            Ok(super::super::raise(Val::Sym("boom".into())))
1848        );
1849    }
1850
1851    #[test]
1852    fn a_raise_is_a_datum_in_exactly_the_positions_that_bind_one() {
1853        let let_ = |init: Exp, body: Exp| Exp::Let(rc_exp(init), rc_exp(body));
1854        // A partial primitive over direct operands - the case the fast
1855        // path runs itself instead of pushing a frame for - and an
1856        // explicit throw, which no fast path is eligible for. Both must
1857        // land in the same place or the fast path is the difference.
1858        let boom = || p2(Prim2::Div, Exp::Lit(1), Exp::Lit(0));
1859        let thrown = || Exp::Throw(rc_exp(Exp::Sym("boom".into())));
1860        let div0 = || {
1861            Ok(super::super::raise(Val::Pair(
1862                rc_val(Val::Sym("divide-by-zero".into())),
1863                rc_val(Val::Sym("/".into())),
1864            )))
1865        };
1866
1867        // Bound: `LetBody` and `AppFinish` receive, so the body runs and
1868        // the raise is an ordinary value it may ignore.
1869        assert_eq!(eval(let_(boom(), Exp::Lit(7))), Ok(Val::Num(7)));
1870        assert_eq!(eval(let_(thrown(), Exp::Lit(7))), Ok(Val::Num(7)));
1871        assert_eq!(eval(app(lam(Exp::Lit(7)), boom())), Ok(Val::Num(7)));
1872        assert_eq!(eval(app(lam(Exp::Lit(7)), thrown())), Ok(Val::Num(7)));
1873        // Or use, in which case it leaves as the value it always was.
1874        assert_eq!(eval(let_(boom(), Exp::Var(0))), div0());
1875        assert_eq!(eval(app(lam(Exp::Var(1)), boom())), div0());
1876
1877        // Propagated: nothing here receives, so the surrounding
1878        // computation does not happen.
1879        assert_eq!(eval(p2(Prim2::Plus, boom(), Exp::Lit(1))), div0());
1880        assert_eq!(eval(p2(Prim2::Plus, Exp::Lit(1), boom())), div0());
1881        assert_eq!(eval(p1(Prim1::Car, boom())), div0());
1882        assert_eq!(eval(if_(boom(), Exp::Lit(1), Exp::Lit(2))), div0());
1883        assert_eq!(eval(app(boom(), Exp::Lit(1))), div0());
1884
1885        // Inspected: the two predicates receive, which is how object code
1886        // asks the question at all.
1887        assert_eq!(eval(p1(Prim1::IsRaise, boom())), Ok(Val::Num(1)));
1888        assert_eq!(
1889            eval(p1(Prim1::RaiseValue, boom())),
1890            Ok(Val::Pair(
1891                rc_val(Val::Sym("divide-by-zero".into())),
1892                rc_val(Val::Sym("/".into()))
1893            ))
1894        );
1895
1896        // Through a `catch`, which is how it reaches object code. The
1897        // third is the one that matters: a raise that *propagated* is
1898        // caught the same as one that was bound and returned, so `catch`
1899        // does not inherit the distinction.
1900        let caught = || {
1901            Ok(Val::Pair(
1902                rc_val(Val::Sym("divide-by-zero".into())),
1903                rc_val(Val::Sym("/".into())),
1904            ))
1905        };
1906        assert_eq!(
1907            eval(Exp::Catch(rc_exp(let_(boom(), Exp::Var(0))))),
1908            caught()
1909        );
1910        assert_eq!(
1911            eval(Exp::Catch(rc_exp(app(lam(Exp::Var(1)), boom())))),
1912            caught()
1913        );
1914        assert_eq!(
1915            eval(Exp::Catch(rc_exp(p2(Prim2::Plus, Exp::Lit(1), boom())))),
1916            caught()
1917        );
1918
1919        // Nested, where an inner fast path must not consume the outer
1920        // position: an ordinary `let` over a folded primitive still binds
1921        // its result, and a raise one argument deep still propagates from
1922        // the position it was raised in.
1923        assert_eq!(
1924            eval(let_(p2(Prim2::Plus, Exp::Lit(1), Exp::Lit(2)), Exp::Var(0))),
1925            Ok(Val::Num(3))
1926        );
1927        assert_eq!(
1928            eval(app(lam(Exp::Var(1)), p1(Prim1::Car, Exp::Nil))),
1929            Ok(super::super::raise(Val::Pair(
1930                rc_val(Val::Sym("wrong-type".into())),
1931                rc_val(Val::Sym("car".into()))
1932            )))
1933        );
1934    }
1935
1936    #[test]
1937    fn a_partial_primitive_throws_a_catchable_value() {
1938        let bad = p2(Prim2::Div, Exp::Lit(1), Exp::Lit(0));
1939        assert_eq!(
1940            eval(Exp::Catch(rc_exp(bad))),
1941            Ok(Val::Pair(
1942                rc_val(Val::Sym("divide-by-zero".into())),
1943                rc_val(Val::Sym("/".into()))
1944            ))
1945        );
1946    }
1947
1948    #[test]
1949    fn host_ops_run_through_the_table() {
1950        let idx = host::registry().lookup("str-append").expect("op present");
1951        let prog = Exp::Op(
1952            idx,
1953            vec![
1954                rc_exp(Exp::Atom(match Str::val("na") {
1955                    Val::Atom(a) => a,
1956                    _ => unreachable!(),
1957                })),
1958                rc_exp(Exp::Atom(match Str::val("rju") {
1959                    Val::Atom(a) => a,
1960                    _ => unreachable!(),
1961                })),
1962            ],
1963        );
1964        assert_eq!(eval(prog), Ok(Str::val("narju")));
1965    }
1966
1967    #[test]
1968    fn receive_blocks_without_consuming_its_continuation() {
1969        let mut h = Store::<NoMail>::default();
1970        let mut m = Machine::new(Env::default(), rc_exp(p1(Prim1::Receive, Exp::Nil)));
1971        // Drive to the block and confirm it stays blocked rather than
1972        // falling through with a value.
1973        for _ in 0..8 {
1974            match m.step(&mut h).expect("no fault") {
1975                Step::Blocked => return,
1976                Step::Done(v) => panic!("completed with {v}"),
1977                Step::Running => {}
1978            }
1979        }
1980        panic!("never reached the block");
1981    }
1982
1983    // ── staging ────────────────────────────────────────────────────────
1984
1985    fn lift(e: Exp) -> Exp {
1986        Exp::Lift(rc_exp(e))
1987    }
1988    fn stage(e: Exp) -> (Val, Vec<Exp>) {
1989        let mut h = Store::<NoMail>::default();
1990        let v = Machine::new(Env::default(), rc_exp(e))
1991            .run(&mut h)
1992            .expect("no fault");
1993        (v, h.block)
1994    }
1995    fn coded(v: &Val) -> &Exp {
1996        match v {
1997            Val::Code(e) => e,
1998            other => panic!("expected code, got {other}"),
1999        }
2000    }
2001
2002    #[test]
2003    fn lifting_a_closure_eta_expands_it_into_one_binding() {
2004        // (lift (lambda self x. (+ x (lift 1))))
2005        //
2006        // The body's `+` reflects, so the lambda's residual carries the
2007        // block the body left - and the outer scope sees only the `Lam`.
2008        let (v, block) = stage(lift(lam(p2(Prim2::Plus, Exp::Var(1), lift(Exp::Lit(1))))));
2009        assert_eq!(coded(&v), &Exp::Var(0));
2010        assert_eq!(
2011            block,
2012            vec![Exp::Lam(
2013                1,
2014                rc_exp(Exp::Let(
2015                    rc_exp(p2(Prim2::Plus, Exp::Var(1), Exp::Lit(1))),
2016                    rc_exp(Exp::Var(2)),
2017                ))
2018            )]
2019        );
2020    }
2021
2022    #[test]
2023    fn lifting_a_pair_builds_it_a_cons_at_a_time() {
2024        // (lift (cons 1 (cons (lift 2) nil)))
2025        let (v, block) = stage(lift(p2(
2026            Prim2::Cons,
2027            Exp::Lit(1),
2028            p2(Prim2::Cons, lift(Exp::Lit(2)), Exp::Nil),
2029        )));
2030        assert_eq!(coded(&v), &Exp::Var(1));
2031        assert_eq!(
2032            block,
2033            vec![
2034                p2(Prim2::Cons, Exp::Lit(2), Exp::Nil),
2035                p2(Prim2::Cons, Exp::Lit(1), Exp::Var(0)),
2036            ]
2037        );
2038    }
2039
2040    #[test]
2041    fn a_variable_the_floor_emitted_a_lambda_for_says_so() {
2042        let is_fun = |e: Exp| eval(Exp::Prim1(Prim1::IsCodeFun, rc_exp(e)));
2043        assert_eq!(is_fun(lift(lam(Exp::Var(1)))), Ok(Val::Num(1)));
2044        // A constant is code without naming anything, and a residualized
2045        // primitive names a value of a kind the floor did not choose.
2046        assert_eq!(is_fun(lift(Exp::Lit(1))), Ok(Val::Num(0)));
2047        assert_eq!(
2048            is_fun(Exp::Prim1(
2049                Prim1::Car,
2050                rc_exp(p2(Prim2::Cons, lift(Exp::Lit(1)), lift(Exp::Lit(2))))
2051            )),
2052            Ok(Val::Num(0))
2053        );
2054    }
2055
2056    #[test]
2057    fn building_a_deep_structure_costs_no_stack() {
2058        let mut h = Store::<NoMail>::default();
2059        let long = (0..10_000).fold(Val::Nil, |t, i| Val::Pair(rc_val(Val::Num(i)), rc_val(t)));
2060        cons_syntax(&long, "unliftable", &mut h).expect("built");
2061        let deep = (0..10_000).fold(Val::Nil, |t, _| Val::Pair(rc_val(t), rc_val(Val::Nil)));
2062        cons_syntax(&deep, "unliftable", &mut h).expect("built");
2063        assert_eq!(h.block.len(), 20_000);
2064    }
2065
2066    #[test]
2067    fn a_closure_lifted_twice_expands_once() {
2068        // (let g (lambda self x. x) (cons (lift g) (lift g)))
2069        //
2070        // The second lift resolves through the memo to the variable the
2071        // first one bound. Without it a recursive lift would not
2072        // terminate.
2073        let (v, block) = stage(Exp::Let(
2074            rc_exp(lam(Exp::Var(1))),
2075            rc_exp(p2(Prim2::Cons, lift(Exp::Var(0)), lift(Exp::Var(0)))),
2076        ));
2077        match v {
2078            Val::Pair(a, b) => {
2079                assert_eq!(coded(&a), &Exp::Var(0));
2080                assert_eq!(coded(&b), &Exp::Var(0));
2081            }
2082            other => panic!("expected a pair, got {other}"),
2083        }
2084        assert_eq!(block, vec![Exp::Lam(1, rc_exp(Exp::Var(1)))]);
2085    }
2086
2087    #[test]
2088    fn a_staged_if_reifies_each_branch_into_its_own_block() {
2089        // (if (lift 1) (+ (lift 2) (lift 3)) (lift 4))
2090        let (v, block) = stage(if_(
2091            lift(Exp::Lit(1)),
2092            p2(Prim2::Plus, lift(Exp::Lit(2)), lift(Exp::Lit(3))),
2093            lift(Exp::Lit(4)),
2094        ));
2095        assert_eq!(coded(&v), &Exp::Var(0));
2096        assert_eq!(
2097            block,
2098            vec![if_(
2099                Exp::Lit(1),
2100                // The branch's own reflect stayed inside the branch.
2101                Exp::Let(
2102                    rc_exp(p2(Prim2::Plus, Exp::Lit(2), Exp::Lit(3))),
2103                    rc_exp(Exp::Var(0)),
2104                ),
2105                Exp::Lit(4),
2106            )]
2107        );
2108    }
2109
2110    #[test]
2111    fn cross_stage_persistence_carries_a_value_by_reference() {
2112        // (let g (lambda self x. x) (lift-ref 1 g))
2113        //
2114        // A closure crossing a stage boundary this way keeps its identity
2115        // rather than being η-expanded into syntax.
2116        let (v, block) = stage(Exp::Let(
2117            rc_exp(lam(Exp::Var(1))),
2118            rc_exp(Exp::LiftRef(rc_exp(Exp::Lit(1)), rc_exp(Exp::Var(0)))),
2119        ));
2120        assert!(block.is_empty());
2121        assert!(matches!(coded(&v), Exp::Proc(p) if matches!(&**p, Val::Clo(_, _))));
2122    }
2123
2124    #[test]
2125    fn a_catch_whose_body_stages_residualizes() {
2126        // (catch (+ (lift 1) (lift 2)))
2127        let (v, block) = stage(Exp::Catch(rc_exp(p2(
2128            Prim2::Plus,
2129            lift(Exp::Lit(1)),
2130            lift(Exp::Lit(2)),
2131        ))));
2132        assert_eq!(coded(&v), &Exp::Var(0));
2133        assert_eq!(
2134            block,
2135            vec![Exp::Catch(rc_exp(Exp::Let(
2136                rc_exp(p2(Prim2::Plus, Exp::Lit(1), Exp::Lit(2))),
2137                rc_exp(Exp::Var(0)),
2138            )))]
2139        );
2140    }
2141
2142    #[test]
2143    fn a_staged_catch_ending_in_a_plain_value_lifts_its_tail() {
2144        // (catch (let _ (+ (lift 1) (lift 2)) 7))
2145        //
2146        // Staging an effect does not oblige the body to end in code.
2147        let (v, block) = stage(Exp::Catch(rc_exp(Exp::Let(
2148            rc_exp(p2(Prim2::Plus, lift(Exp::Lit(1)), lift(Exp::Lit(2)))),
2149            rc_exp(Exp::Lit(7)),
2150        ))));
2151        assert_eq!(coded(&v), &Exp::Var(0));
2152        assert_eq!(
2153            block,
2154            vec![Exp::Catch(rc_exp(Exp::Let(
2155                rc_exp(p2(Prim2::Plus, Exp::Lit(1), Exp::Lit(2))),
2156                rc_exp(Exp::Lit(7)),
2157            )))]
2158        );
2159    }
2160
2161    #[test]
2162    fn run_now_executes_the_code_its_argument_produced() {
2163        // ((run 0 (lift (lambda self x. (+ x (lift 1))))) 41)
2164        let f = Exp::Run(
2165            rc_exp(Exp::Lit(0)),
2166            rc_exp(lift(lam(p2(Prim2::Plus, Exp::Var(1), lift(Exp::Lit(1)))))),
2167        );
2168        let mut h = Store::<NoMail>::default();
2169        let got = Machine::new(Env::default(), rc_exp(app(f, Exp::Lit(41)))).run(&mut h);
2170        assert_eq!(got, Ok(Val::Num(42)));
2171        // Running restores the level and leaves no residual behind.
2172        assert_eq!(h.level, 0);
2173        assert!(h.block.is_empty());
2174    }
2175
2176    fn run_src(text: &str) -> Result<Val, Fault> {
2177        let forms = crate::floor::read::read(text).expect("reads");
2178        assert_eq!(forms.len(), 1);
2179        let exp = match crate::floor::read::trans(&forms[0], &Val::Nil).expect("lowers") {
2180            Val::Code(e) => e,
2181            other => panic!("expected code, got {other}"),
2182        };
2183        Machine::new(Env::default(), exp).run(&mut Store::<NoMail>::default())
2184    }
2185
2186    #[test]
2187    fn source_text_runs() {
2188        assert_eq!(
2189            run_src(
2190                "(let (fact (lambda (fact n)
2191                              (if (< n 1) 1 (* n (fact (- n 1))))))
2192                   (fact 5))"
2193            ),
2194            Ok(Val::Num(120))
2195        );
2196    }
2197
2198    #[test]
2199    fn source_text_stages_and_runs_what_it_staged() {
2200        // The power-function specializer, the paper's example: `n` is
2201        // static, `x` is dynamic, so the residual is a chain of
2202        // multiplications with no loop left in it.
2203        assert_eq!(
2204            run_src(
2205                "((run 0 (let (pow (lambda (pow n)
2206                                     (lambda (f x)
2207                                       (if (< n 1) (lift 1) (* x ((pow (- n 1)) x))))))
2208                           (lift (pow 3))))
2209                  2)"
2210            ),
2211            Ok(Val::Num(8))
2212        );
2213    }
2214
2215    #[test]
2216    fn one_staged_operand_stages_the_call_and_the_rest_join_it() {
2217        let (v, block) = stage(p2(Prim2::Plus, lift(Exp::Lit(1)), Exp::Lit(2)));
2218        assert_eq!(coded(&v), &Exp::Var(0));
2219        assert_eq!(block, vec![p2(Prim2::Plus, Exp::Lit(1), Exp::Lit(2))]);
2220        assert!(matches!(
2221            eval(p2(
2222                Prim2::Plus,
2223                lift(Exp::Lit(1)),
2224                Exp::Prim2(Prim2::Cons, rc_exp(Exp::Lit(2)), rc_exp(Exp::Nil))
2225            )),
2226            Ok(Val::Raise(_))
2227        ));
2228    }
2229}