beck_core/
liveness.rs

1//! Which read of a local is its **last** one, so a backend may move the value instead of copying it.
2//!
3//! # Why this exists
4//!
5//! Beck has no mutable sequence, so every loop that builds one is a tail-recursive accumulator:
6//!
7//! ```text
8//! def add_from(a, b, i, carry, done):
9//!     …
10//!     return add_from(a, b, i + 1, total / base(), list_append(done, total % base()))
11//! ```
12//!
13//! `list_append` cannot push into `done` because the caller's frame still binds it, so it copies —
14//! and the idiom is therefore quadratic in time. [`69`](../../../../../docs/69-standard-library-imports-report.md)
15//! §69.7 is the measurement, and the fix is knowing that this read of `done` is its last: the frame
16//! can hand the value over rather than lend it, and the append can push into a list nobody else
17//! holds.
18//!
19//! It is the same idea as Koka's Perceus and Roc's opportunistic mutation, and it is *why* a
20//! language can be pure and still write in place. It is computed here rather than in a backend
21//! because it is a fact about the program: [`19`](../../../../../docs/19-phase-1-report.md) §19.4's
22//! rule that a copied accumulator is "a semantic defect, not a backend one" cuts both ways.
23//!
24//! # What the flag promises
25//!
26//! `last_use` on a [`CoreKind::Var`] means: **on every path that evaluates this node, no later
27//! evaluation in this function body reads that binding.** It says nothing about other frames, other
28//! calls or the heap — a value may still be shared, and a backend must check that separately.
29//!
30//! `false` is always safe, and everything not understood here is left `false`.
31//!
32//! # The three rules that make it sound
33//!
34//! 1. **Branches are alternatives.** A read in the `then` arm is a last use if the variable is not
35//!    read after the whole `if`, whatever `alt` does, because only one arm runs.
36//! 2. **A `lam` body is not analysed against the enclosing frame.** A closure captures its
37//!    environment and may be called any number of times later, so every variable free in it stays
38//!    live, and nothing inside it is marked.
39//! 3. **Evaluation order is left to right**, which is the order the evaluator uses for arguments,
40//!    fields and list elements. Walking backwards over that order is what makes "later" mean
41//!    anything.
42
43use std::collections::BTreeSet;
44use std::sync::Arc;
45
46use crate::core::{Core, CoreKind, VarId};
47
48/// Mark every definition and test in a checked program.
49///
50/// Run once, where the program is finished and before any backend sees it, so that "which read is
51/// the last" is a property of the compiled program rather than something one backend worked out
52/// for itself.
53pub fn mark_program(program: &mut crate::check::Program) {
54    for def in program.defs.values_mut() {
55        mark(&mut def.body);
56    }
57    for signal in program.signals.iter_mut() {
58        mark(&mut signal.expr);
59    }
60    // A test's expressions are code, and the runner wraps each one in a lambda whose frame is
61    // built and dropped by a single call — so a last read inside one is exactly as safe to move as
62    // a last read inside a definition. `docs/79`.
63    for test in program.tests.iter_mut() {
64        for c in test.cores_mut() {
65            mark(c);
66        }
67    }
68}
69
70/// Mark every last read in `body`, given the parameters bound around it.
71///
72/// Idempotent, and safe to run on a body that has been marked already: the flag is recomputed from
73/// scratch rather than accumulated.
74pub fn mark(body: &mut Core) {
75    // A definition *is* a lambda — [`crate::check::Def::body`] is "the whole definition as a
76    // lambda, so evaluating the name yields a callable value" — and that outermost one is not a
77    // closure: its frame is built fresh by each call and dies with it, so its parameters are
78    // exactly the bindings worth moving. Looking through it is the difference between this pass
79    // marking every function and marking nothing at all.
80    if let CoreKind::Lam {
81        params,
82        body: inner,
83    } = &mut body.kind
84    {
85        let params: Vec<VarId> = params.to_vec();
86        // `make_mut` rather than a clone: this runs once, on a program nothing else holds yet, so
87        // the copy-on-write never copies. The `Arc` is there for the *evaluator*, which shares one
88        // body across every closure built from it (`docs/73` §73.1).
89        mark_frame(&params, Arc::make_mut(inner));
90    } else {
91        // An expression that is not a definition: a signal, or a clause of a `test` block, which
92        // the runner wraps in a lambda of its own when it evaluates it. Every variable it reads is
93        // bound by that frame — its own bindings, or the wrapper's parameters — so all of them are
94        // this frame's to hand over.
95        let mut own = BTreeSet::new();
96        reads(body, &mut own);
97        mark_scope(body, own);
98    }
99}
100
101/// Mark a frame's body, given the parameters its call binds.
102fn mark_frame(params: &[VarId], body: &mut Core) {
103    // What this frame owns: its parameters, and everything bound *directly* in the body. A binding
104    // made inside a nested lambda belongs to that lambda's own frame and is marked when this walk
105    // reaches it.
106    let mut own: BTreeSet<VarId> = params.iter().copied().collect();
107    collect_own_binders(body, &mut own);
108    mark_scope(body, own);
109}
110
111fn mark_scope(body: &mut Core, own: BTreeSet<VarId>) {
112    // Rule 2, and it has to be a pre-pass rather than something the backward walk discovers. A
113    // closure is created *before* the reads that follow it in the body, so walking backwards meets
114    // those reads first and would call one of them the last — while the closure it already captured
115    // is still holding the binding, to read whenever it is called. Every variable any lambda
116    // touches is therefore excluded outright.
117    let mut captured: BTreeSet<VarId> = BTreeSet::new();
118    collect_captures(body, &mut captured, false);
119    let mut live: BTreeSet<VarId> = BTreeSet::new();
120    walk(body, &mut live, &captured, &own);
121}
122
123/// Every variable bound by *this* frame: a `let`, a match arm's binders, and a lambda's parameters
124/// only when that lambda is this node. A nested lambda's bindings live in the frame its own call
125/// makes, so the walk stops there.
126fn collect_own_binders(c: &Core, out: &mut BTreeSet<VarId>) {
127    match &c.kind {
128        CoreKind::Lam { .. } => return,
129        CoreKind::Let { var, .. } => {
130            out.insert(*var);
131        }
132        CoreKind::Match { arms, .. } => {
133            for arm in arms {
134                out.extend(arm.pattern.binders());
135            }
136        }
137        _ => {}
138    }
139    for child in children(c) {
140        collect_own_binders(child, out);
141    }
142}
143
144/// Every variable read anywhere inside, including inside nested lambdas.
145fn reads(c: &Core, out: &mut BTreeSet<VarId>) {
146    if let CoreKind::Var(v) = &c.kind {
147        out.insert(*v);
148    }
149    for child in children(c) {
150        reads(child, out);
151    }
152}
153
154/// Every variable bound anywhere inside, nested lambdas included — so that subtracting it from
155/// [`reads`] leaves exactly the variables a lambda takes from the scope around it.
156fn binds(c: &Core, out: &mut BTreeSet<VarId>) {
157    match &c.kind {
158        CoreKind::Lam { params, .. } => out.extend(params.iter().copied()),
159        CoreKind::Let { var, .. } => {
160            out.insert(*var);
161        }
162        CoreKind::Match { arms, .. } => {
163            for arm in arms {
164                out.extend(arm.pattern.binders());
165            }
166        }
167        _ => {}
168    }
169    for child in children(c) {
170        binds(child, out);
171    }
172}
173
174/// Every variable read anywhere inside a `lam`, over-approximated: a lambda's own parameters are
175/// counted too, which costs a missed move and never an unsound one.
176fn collect_captures(c: &Core, out: &mut BTreeSet<VarId>, inside: bool) {
177    if let CoreKind::Var(v) = &c.kind {
178        if inside {
179            out.insert(*v);
180        }
181    }
182    let inside = inside || matches!(c.kind, CoreKind::Lam { .. });
183    for child in children(c) {
184        collect_captures(child, out, inside);
185    }
186}
187
188/// Every subexpression, in no particular order — the traversal `collect_captures` needs and the
189/// only place in this module that does not care about evaluation order.
190fn children(c: &Core) -> Vec<&Core> {
191    match &c.kind {
192        CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => Vec::new(),
193        CoreKind::Lam { body, .. } => vec![&**body],
194        CoreKind::App { func, args } => std::iter::once(&**func).chain(args.iter()).collect(),
195        CoreKind::Prim { args, .. } => args.iter().collect(),
196        CoreKind::Let { value, body, .. } => vec![&**value, &**body],
197        CoreKind::If { cond, then, alt } => vec![&**cond, &**then, &**alt],
198        CoreKind::Match { scrutinee, arms } => std::iter::once(&**scrutinee)
199            .chain(arms.iter().flat_map(|a| a.exprs()))
200            .collect(),
201        CoreKind::Make { fields, .. } => fields.iter().map(|(_, f)| f).collect(),
202        CoreKind::Field { base, .. } => vec![&**base],
203        CoreKind::With { base, fields } => std::iter::once(&**base)
204            .chain(fields.iter().map(|(_, f)| f))
205            .collect(),
206        CoreKind::ListLit(xs) => xs.iter().collect(),
207        CoreKind::MapLit(kvs) => kvs.iter().flat_map(|(k, v)| [k, v]).collect(),
208    }
209}
210
211/// Backwards over evaluation order. `live` is the set of variables read *after* this node, and
212/// `captured` is rule 2's exclusion list.
213fn walk(
214    c: &mut Core,
215    live: &mut BTreeSet<VarId>,
216    captured: &BTreeSet<VarId>,
217    own: &BTreeSet<VarId>,
218) {
219    match &mut c.kind {
220        CoreKind::Var(v) => {
221            let v = *v;
222            // `own` is the third condition and the newest: a read may only be handed over by the
223            // frame that binds it. Without it a lambda would mark a read of a variable it took
224            // from the scope around it, and that binding outlives the call (`docs/79` §79.3).
225            c.last_use = own.contains(&v) && !captured.contains(&v) && !live.contains(&v);
226            live.insert(v);
227        }
228        CoreKind::Const(_) | CoreKind::Global(_) => {}
229
230        CoreKind::Lam { params, body } => {
231            // Two separate jobs, and conflating them is what made this pass mark nothing inside a
232            // lambda for three reports.
233            //
234            // Outwards: every variable the lambda takes from the scope around it becomes live
235            // here, because a closure that reads `xs` is a reader of `xs` for as long as it
236            // exists — and it may be called any number of times, at any point later.
237            //
238            // Inwards: the body is a frame of its own, built by each call to the closure and
239            // dropped with it, exactly as a definition's is. So it gets its own analysis, in which
240            // the lambda's parameters and bindings are the ones worth handing over. `list_fold`'s
241            // accumulator is a lambda's parameter and nothing else, which is why the fold form of
242            // the accumulator idiom stayed quadratic after `docs/70` made the recursive form
243            // linear.
244            let params: Vec<VarId> = params.to_vec();
245            let body = Arc::make_mut(body);
246            let (mut read, mut bound) = (BTreeSet::new(), BTreeSet::new());
247            reads(body, &mut read);
248            bound.extend(params.iter().copied());
249            binds(body, &mut bound);
250            live.extend(read.difference(&bound).copied());
251            mark_frame(&params, body);
252        }
253
254        CoreKind::App { func, args } => {
255            // **The callee is evaluated after its arguments**, not before: `Interp::step`'s `App`
256            // arm evaluates every operand and *then* the function, so that a stub can answer "with
257            // what?" (§21.3 rule 4). Walking backwards therefore means the callee first. Getting
258            // this the intuitive way round says the last read of `f` in `f(x, g(f))` is the inner
259            // one, moves it there, and leaves the call itself with nothing to call — which is what
260            // `sicp/ch1.beck`'s exercise 1.32 does, and what caught it.
261            walk(func, live, captured, own);
262            for a in args.iter_mut().rev() {
263                walk(a, live, captured, own);
264            }
265        }
266        CoreKind::Prim { op: _, args } => {
267            for a in args.iter_mut().rev() {
268                walk(a, live, captured, own);
269            }
270        }
271        CoreKind::ListLit(items) => {
272            for i in items.iter_mut().rev() {
273                walk(i, live, captured, own);
274            }
275        }
276        CoreKind::MapLit(kvs) => {
277            for (k, v) in kvs.iter_mut().rev() {
278                walk(v, live, captured, own);
279                walk(k, live, captured, own);
280            }
281        }
282        CoreKind::Make { fields, .. } => {
283            for (_, f) in fields.iter_mut().rev() {
284                walk(f, live, captured, own);
285            }
286        }
287        CoreKind::Field { base, .. } => walk(base, live, captured, own),
288        CoreKind::With { base, fields } => {
289            for (_, f) in fields.iter_mut().rev() {
290                walk(f, live, captured, own);
291            }
292            walk(base, live, captured, own);
293        }
294
295        CoreKind::Let { var, value, body } => {
296            walk(body, live, captured, own);
297            // The binding dies with its `let`: a read of `var` after this node cannot be this
298            // `var`, because the name is out of scope there.
299            let var = *var;
300            live.remove(&var);
301            walk(value, live, captured, own);
302        }
303
304        CoreKind::If { cond, then, alt } => {
305            // Rule 1: each arm sees what is live after the whole `if`, not what the other arm reads.
306            let mut then_live = live.clone();
307            walk(then, &mut then_live, captured, own);
308            let mut alt_live = std::mem::take(live);
309            walk(alt, &mut alt_live, captured, own);
310            *live = then_live;
311            live.extend(alt_live);
312            walk(cond, live, captured, own);
313        }
314
315        CoreKind::Match { scrutinee, arms } => {
316            let after = std::mem::take(live);
317            let mut union = BTreeSet::new();
318            for arm in arms.iter_mut() {
319                let mut arm_live = after.clone();
320                // Backwards through the arm: the body runs after the guard, so it is walked
321                // first — a read in the guard is *earlier*, and a last use is the latest read.
322                walk(&mut arm.body, &mut arm_live, captured, own);
323                if let Some(guard) = &mut arm.guard {
324                    walk(guard, &mut arm_live, captured, own);
325                }
326                // The arm's own binders die with the arm, the way a `let`'s does.
327                for b in arm.pattern.binders() {
328                    arm_live.remove(&b);
329                }
330                union.extend(arm_live);
331            }
332            *live = union;
333            walk(scrutinee, live, captured, own);
334        }
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341    use crate::ty::Ty;
342    use beck_diag::Span;
343
344    fn var(v: VarId) -> Core {
345        Core::new(CoreKind::Var(v), Ty::int(), Span::NONE)
346    }
347
348    fn prim(op: crate::core::Prim, args: Vec<Core>) -> Core {
349        Core::new(CoreKind::Prim { op, args }, Ty::int(), Span::NONE)
350    }
351
352    /// Every `Var` node in the tree, in a stable order, with its flag.
353    fn marks(c: &Core) -> Vec<(VarId, bool)> {
354        let mut out = Vec::new();
355        fn go(c: &Core, out: &mut Vec<(VarId, bool)>) {
356            if let CoreKind::Var(v) = &c.kind {
357                out.push((*v, c.last_use));
358            }
359            for child in children(c) {
360                go(child, out);
361            }
362        }
363        fn children(c: &Core) -> Vec<&Core> {
364            match &c.kind {
365                CoreKind::Prim { args, .. } => args.iter().collect(),
366                CoreKind::App { func, args } => {
367                    let mut v: Vec<&Core> = vec![func];
368                    v.extend(args.iter());
369                    v
370                }
371                CoreKind::If { cond, then, alt } => vec![cond, then, alt],
372                CoreKind::Let { value, body, .. } => vec![value, body],
373                CoreKind::Lam { body, .. } => vec![body],
374                CoreKind::ListLit(xs) => xs.iter().collect(),
375                CoreKind::Field { base, .. } => vec![base],
376                _ => Vec::new(),
377            }
378        }
379        go(c, &mut out);
380        out
381    }
382
383    #[test]
384    fn the_only_read_of_a_variable_is_its_last() {
385        let mut c = prim(crate::core::Prim::ListAppend, vec![var(1), var(2)]);
386        mark(&mut c);
387        assert_eq!(marks(&c), vec![(1, true), (2, true)]);
388    }
389
390    #[test]
391    fn an_earlier_read_of_the_same_variable_is_not() {
392        // `add(x, x)` — arguments run left to right, so the *second* one is the last read.
393        let mut c = prim(crate::core::Prim::Add, vec![var(1), var(1)]);
394        mark(&mut c);
395        assert_eq!(marks(&c), vec![(1, false), (1, true)]);
396    }
397
398    /// Rule 1. Only one arm runs, so a read in each arm is that path's last read.
399    #[test]
400    fn a_read_in_each_branch_is_a_last_read_in_both() {
401        let mut c = Core::new(
402            CoreKind::If {
403                cond: Box::new(var(9)),
404                then: Box::new(var(1)),
405                alt: Box::new(var(1)),
406            },
407            Ty::int(),
408            Span::NONE,
409        );
410        mark(&mut c);
411        assert_eq!(marks(&c), vec![(9, true), (1, true), (1, true)]);
412    }
413
414    /// …but not when the variable is read again after the branch.
415    #[test]
416    fn a_read_in_a_branch_is_not_a_last_read_when_the_value_outlives_the_branch() {
417        let inner = Core::new(
418            CoreKind::If {
419                cond: Box::new(var(9)),
420                then: Box::new(var(1)),
421                alt: Box::new(var(2)),
422            },
423            Ty::int(),
424            Span::NONE,
425        );
426        let mut c = prim(crate::core::Prim::Add, vec![inner, var(1)]);
427        mark(&mut c);
428        assert_eq!(
429            marks(&c),
430            vec![(9, true), (1, false), (2, true), (1, true)],
431            "the `then` arm's read of 1 is followed by another read"
432        );
433    }
434
435    /// Rule 2. A closure may be called twice, so it never gets to move what it **captured**.
436    #[test]
437    fn a_lambda_never_moves_what_it_took_from_the_scope_around_it() {
438        let lam = Core::new(
439            CoreKind::Lam {
440                params: vec![7].into(),
441                body: Arc::new(var(1)),
442            },
443            Ty::int(),
444            Span::NONE,
445        );
446        // The captured read comes *first* in evaluation order, and the later direct read of the
447        // same variable must not be treated as the last one either — the closure outlives it.
448        let mut c = prim(crate::core::Prim::Add, vec![lam, var(1)]);
449        mark(&mut c);
450        assert_eq!(marks(&c), vec![(1, false), (1, false)]);
451    }
452
453    /// …but a variable the lambda **binds itself** is another matter: its frame is built by the
454    /// call and dropped with it, exactly as a definition's is.
455    ///
456    /// This is `docs/79`, and `list_fold`'s accumulator is the case that matters — `acc` is a
457    /// lambda's parameter and nothing else, so before this the fold form of the accumulator idiom
458    /// copied where the recursive form moved.
459    #[test]
460    fn a_lambdas_own_parameter_is_moved_on_its_last_read() {
461        let mut c = Core::new(
462            CoreKind::Lam {
463                params: vec![7, 8].into(),
464                body: Arc::new(prim(crate::core::Prim::ListAppend, vec![var(7), var(8)])),
465            },
466            Ty::int(),
467            Span::NONE,
468        );
469        mark(&mut c);
470        assert_eq!(marks(&c), vec![(7, true), (8, true)]);
471    }
472
473    /// The line between the two, in one lambda: its own parameter is handed over and the variable
474    /// it took from outside is not.
475    ///
476    /// The evaluator would refuse the second one anyway — a captured environment is shared, so
477    /// `Env::read` cannot empty it — which is why this is asserted here, on the flag, rather than
478    /// by a program that could not tell the difference (`docs/79` §79.6).
479    #[test]
480    fn a_lambda_moves_its_parameter_and_lends_its_capture() {
481        let mut c = Core::new(
482            CoreKind::Lam {
483                params: vec![7].into(),
484                body: Arc::new(prim(crate::core::Prim::ListAppend, vec![var(1), var(7)])),
485            },
486            Ty::int(),
487            Span::NONE,
488        );
489        mark(&mut c);
490        assert_eq!(
491            marks(&c),
492            vec![(1, false), (7, true)],
493            "1 is free in the lambda and 7 is its parameter"
494        );
495    }
496
497    /// And a parameter a *deeper* lambda reads goes back to being lent, by the same rule one level
498    /// down.
499    #[test]
500    fn a_lambda_parameter_a_deeper_lambda_reads_is_not_moved() {
501        let deeper = Core::new(
502            CoreKind::Lam {
503                params: vec![9].into(),
504                body: Arc::new(var(7)),
505            },
506            Ty::int(),
507            Span::NONE,
508        );
509        let mut c = Core::new(
510            CoreKind::Lam {
511                params: vec![7].into(),
512                body: Arc::new(prim(crate::core::Prim::ListAppend, vec![var(7), deeper])),
513            },
514            Ty::int(),
515            Span::NONE,
516        );
517        mark(&mut c);
518        assert_eq!(marks(&c), vec![(7, false), (7, false)]);
519    }
520
521    #[test]
522    fn a_let_bound_variable_dies_with_its_body() {
523        // let x = y in x  — both reads are last reads.
524        let mut c = Core::new(
525            CoreKind::Let {
526                var: 1,
527                value: Box::new(var(2)),
528                body: Box::new(var(1)),
529            },
530            Ty::int(),
531            Span::NONE,
532        );
533        mark(&mut c);
534        assert_eq!(marks(&c), vec![(2, true), (1, true)]);
535    }
536
537    /// `accumulate`'s shape: the combiner is the callee *and* an argument of the nested call.
538    ///
539    /// The evaluator runs the arguments first, so the callee position is the last read and the
540    /// nested argument is not. Marking it the other way round is a program that loses its own
541    /// function — `sicp/ch1.beck` exercise 1.32, which is where this came from.
542    #[test]
543    fn a_callee_is_read_after_its_arguments() {
544        let inner = Core::new(
545            CoreKind::App {
546                func: Box::new(var(1)),
547                args: vec![var(2)],
548            },
549            Ty::int(),
550            Span::NONE,
551        );
552        let mut c = Core::new(
553            CoreKind::App {
554                func: Box::new(var(1)),
555                args: vec![inner],
556            },
557            Ty::int(),
558            Span::NONE,
559        );
560        mark(&mut c);
561        // Outer callee (evaluated last) is the last read; the inner callee and argument are not.
562        assert_eq!(marks(&c), vec![(1, true), (1, false), (2, true)]);
563    }
564
565    #[test]
566    fn marking_twice_is_marking_once() {
567        let mut a = prim(crate::core::Prim::Add, vec![var(1), var(1)]);
568        mark(&mut a);
569        let once = marks(&a);
570        mark(&mut a);
571        assert_eq!(marks(&a), once);
572    }
573}