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(¶ms, 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(¶ms, 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}