@@ -106,24 +106,52 @@ First result on the http hello server: call-arg retains dominate (72 of the 97 m
106106retains) and 55 are movable, so the calling-convention retain (the caller retains, the callee drops)
107107is the large lever and most arguments are moves.
108108
109- ### Follow-ups (non-trivial, revisit before building the elision)
110-
111- - ** The liveness is a first cut.** The loop rule is sound but coarse. A full version wants proper
112- dataflow liveness: nested loops, ` break ` /` continue ` , conditionals and merge points, multiple
113- reaching definitions, and cross-block moves. The current rule is intentionally conservative, so it
114- under-counts rather than ever over-counting, but the real elision transform needs the stronger
115- analysis to catch the cases it currently rejects.
116- - ** Find more elision categories.** Beyond the three movable categories there are more: a value
117- returned moves ownership out of the function (its drop is elidable), a uniquely-owned orphan
118- temporary's drop can become a direct free rather than a decrement-and-check, a retain and release
119- that cancel within a block regardless of category, and a struct construction that moves its fields
120- in rather than retaining them. Each is its own rule to measure and prove.
121- - ** The counts are static, not runtime-weighted.** They count retain sites in the code, not their
122- execution frequency, so they show the shape of the opportunity rather than the exact runtime total.
123- Weighting by the per-request hot path would bridge to the runtime ` vm-counters ` number.
124- - ** Then build the transform.** The mechanism is already present: the dormant ` moved ` flag, which the
125- drop emitter already respects. The elision sets it for proven moves and skips the paired retain,
126- keyed on the same decision the measurement makes.
109+ ### The elision transform (built, conservative first cut)
110+
111+ ` elidable_moves ` in the lowering decides when a reference-typed variable's last use moves it into a
112+ consumer (a call argument, a spawn argument, or an aliasing copy), and the lowering skips both the
113+ retain at that site and the variable's drop. It is deliberately conservative, so it can only
114+ under-elide, never over-elide: the site must be the last real use (loop-aware), the variable must
115+ appear exactly once at the site, and it must have a single drop the move dominates (approximated by no
116+ control flow between the move and the drop). Measured on the http hello path: incref 102 to 81 and
117+ decref 172 to 151 per request, memory-safe, all suites green.
118+
119+ ### Refinements (there is a lot of headroom)
120+
121+ The static analysis found 80 movable retains but the runtime elided about 21 per request, so the gap
122+ is the conservatism plus what actually runs on the hot path. Each refinement widens what is elided, so
123+ each needs its own review since that is where an unsound over-elision would hide.
124+
125+ - ** Proper dominance instead of "no control flow between".** The current straight-line rule rejects a
126+ move that happens before a branch, or in a ` match ` arm, even when the move still dominates the drop.
127+ A dominance analysis catches those and enables per-path and multi-drop elision.
128+ - ** Full dataflow liveness.** Nested loops, ` break ` /` continue ` , merge points, and multiple reaching
129+ definitions, replacing the loop-and-linear-last-use approximation.
130+ - ** More elision categories.** A value returned moves ownership out of the function, so its drop is
131+ elidable. A uniquely-owned orphan temporary's drop can become a direct free rather than a
132+ decrement-and-check, which attacks the roughly 94 allocations per request that RC-op elision alone
133+ does not touch. A retain and release that cancel within a block regardless of category. A struct or
134+ enum construction that moves its fields in rather than retaining them.
135+ - ** The report counts are static, not runtime-weighted.** Weighting by the per-request hot path would
136+ bridge ` --rc-report ` to the runtime ` vm-counters ` number.
137+
138+ ### Guiding model: Rust's analysis as an oracle, not a gate
139+
140+ The north star is to take Rust's ownership and borrow analysis as an internal optimization oracle
141+ while keeping none of its surface. Where the analysis proves a value's lifetime is clean the reference
142+ counting is dropped, and where it cannot the reference counting stays, which is always correct. So the
143+ programmer never sees a move, a borrow, or an ownership error, and no program is ever rejected. This
144+ is the difference from Rust: Rust rejects what it cannot prove, this keeps the refcount.
145+
146+ Under this model the current move elision is only the caller-side, last-use case. The larger lever is
147+ the borrow, Rust's ` &T ` : a parameter the callee only reads is borrowed, so the caller needs no retain
148+ at all, even when it uses the value again after the call. Realizing it needs a per-function escape
149+ summary (does a parameter get stored into a field, returned, or spawned, or is it only read), which
150+ makes it interprocedural and wants a fixpoint over recursion. It stays fallback-safe: an unknown or
151+ conservatively-escaping callee just keeps the retain, so a wrong summary costs performance, never
152+ correctness. Most parameters are borrows, so this is a much larger category than move elision, and
153+ together they approach a common case with no reference counting, leaving it only for genuine shared
154+ references, escapes into aggregates, and cycles, where the future cycle collector takes over.
127155
128156## Where this sits
129157
0 commit comments