plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


58b6f33 — Peter John 2026-11-03T20:51:15+05:30
docs(plans): supercompiler: uniqueness inference stage to test whether own is needed
docs/plans/2026-11-03-supercompiler.review.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Supercompiler: fusing List chains and view-into-draw: review plan
2
2
 
3
- **Builds on:** branch `pure-scene-values` at 651acd8 (on top of master 59d923d): closure specialisation, tail calls and TRMC, unboxed variants, std cons `List`, recursive `Picture` and the state/view Orb Run.
3
+ **Builds on:** master after `2026-11-03-pure-scene-enums` (pure scene on `Node` + `Kind`, `own` kept): closure specialisation, tail calls and TRMC, unboxed variants, std cons `List`, recursive `Picture` and the state/view Orb Run.
4
+
5
+ **Added 2026-11-03 (user):** a uniqueness stage. `own` stays for now. If the compiler can prove for itself that nobody else still holds a value, `own` becomes redundant and can be removed in a later plan. See "Uniqueness without `own`" below.
4
6
 
5
7
  ## Summary
6
8
  Two kinds of code still build data only to take it apart straight away:
@@ -16,6 +18,16 @@ A small **supercompiler** removes both. It is a compiler pass that, when one fun
16
18
  - **Pure code only.** The pass fuses only when every function and closure involved is pure (no `extern` calls, no field or variable writes outside its own locals, no `print`). Fusing changes the order in which steps run (`f`, `g` and `h` interleave per element instead of running stage by stage), and pure code can't tell the difference. Anything else is left exactly as it is today.
17
19
  - **Visible results.** A `plum build --explain-fusion` flag (or an env var, whichever fits the CLI better) lists each fused site and each place where the pass gave up, and why.
18
20
 
21
+ ## Uniqueness without `own`
22
+ Today `own` is how a function such as `update(own n) -> Node` changes `n` in place and hands it back: the checker proves no one else still holds `n`. Koka and Lean get the same in-place updates with no keyword, by checking at runtime that a value has one reference. Plum has no reference counts (wasm-gc), so the proof has to be static. Whole-program compilation makes it tractable.
23
+
24
+ - **Inference.** For every spread copy (`Node(..n, x: 1.0)`) and every rebuilt constructor, the pass checks whether the source value is dead afterwards on every path and is not reachable from anything still live. Live things include another binding, a field of a live value, an element of a live collection, a closure capture or a global. If so, the copy becomes an in-place write, as `own` does today.
25
+ - **Reuse.** A constructor that takes a matched value apart and builds one of the same shape (e.g. `Cons(h, t) => Cons(f(h), go(t))`) reuses the old cell's memory, Koka's "reuse" idea.
26
+ - **Measured against `own`.** The pass runs on the code with every `own` erased, and the plan reports how many of today's `own` in-place sites it proves on its own across std, `game_pure_scene` and its fixture.
27
+ - If it proves all of them, a follow-up plan removes the `own` keyword and the ownership checker.
28
+ - If not, the report lists the sites it can't prove and why, and `own` stays.
29
+ - **Never unsound.** Anything unclear stays a copy. A copy is always correct, only slower.
30
+
19
31
  ## What will be built
20
32
  | Part | What it does | Size |
21
33
  |---|---|---|
@@ -25,6 +37,7 @@ A small **supercompiler** removes both. It is a compiler pass that, when one fun
25
37
  | Stop rule | A growth check (homeomorphic embedding plus a size budget per site) stops driving that would never end or would blow up code size; the site is left unfused. No generalisation in this round | M |
26
38
  | Std reshaping | `List.filter` written in direct recursive style (TRMC still makes it constant-stack) so it fuses; `Picture`'s draw, bounds and hit walks checked to be pure; `toNodes`-style helpers in the example kept fusable | S |
27
39
  | Pipeline placement | Runs after closure specialisation (so closures are already inlined) and before TRMC and tail-call marking (so the fused functions still get loops and `return_call`) | S |
40
+ | Uniqueness inference | Dead-after-use analysis turns spread copies and same-shape rebuilds into in-place writes or cell reuse; a report against today's `own` sites with `own` erased | L |
28
41
  | Benchmarks and docs | 1M List pipeline table, state/view vs mutable vs pure scene table, README "how fusion works" section | S |
29
42
 
30
43
  ## How it fits together