Problem
A let-bound record, array, constructor, or record update that is only ever read by field, never used as a whole value, still allocates its table. pslua unpacks constructors through a let (propagateKnownCtorThroughLet) and folds an in-place object projection (reduceObjectProp), but has no general rule for records, arrays, or updates. On the Lua target, where a table allocation costs more than in a JS engine with escape analysis, an aggregate that exists only to be projected is pure waste.
The unpacking rewrite explodes such a binding into a per-field let so the aggregate is never built, gated on an exact use equation: total == access + case for a constructor, == access + update for a record, == access for an array. A single whole-value use vetoes the rewrite, preserving sharing and identity.
Approach
Add an unpacking rewrite for let x = <aggregate> in body where <aggregate> is a record literal, array literal, constructor application, or record update and every occurrence of x in body is a field/element projection (or, for a constructor, a case reading its fields). Bind each read field to its own let and drop the aggregate. The exact-use equation is the gate; any use of x as a whole value (returned, passed, compared) turns it off.
Prerequisites / Relations
Blocked by #231 (the Complexity/Capture analysis lattice): "read only field-wise, never as a whole" is the same exact-use accounting the lattice makes precise. The result-side dual is #206 (CPR: return components rather than a product); together they avoid materializing a box on either side of a call. Relates to #208 (SpecConstr), which removes the boxed accumulator this rule would otherwise unpack every iteration.
Verification / Measurement
A let-bound record read at two fields compiles with no table allocation and two direct bindings in golden.lua; a record also used as a whole value keeps its allocation, the soundness guard, tested directly. Allocation-counter drop on a record-heavy benchmark through #172; eval goldens unchanged.
Problem
A
let-bound record, array, constructor, or record update that is only ever read by field, never used as a whole value, still allocates its table. pslua unpacks constructors through a let (propagateKnownCtorThroughLet) and folds an in-place object projection (reduceObjectProp), but has no general rule for records, arrays, or updates. On the Lua target, where a table allocation costs more than in a JS engine with escape analysis, an aggregate that exists only to be projected is pure waste.The unpacking rewrite explodes such a binding into a per-field let so the aggregate is never built, gated on an exact use equation:
total == access + casefor a constructor,== access + updatefor a record,== accessfor an array. A single whole-value use vetoes the rewrite, preserving sharing and identity.Approach
Add an unpacking rewrite for
let x = <aggregate> in bodywhere<aggregate>is a record literal, array literal, constructor application, or record update and every occurrence ofxinbodyis a field/element projection (or, for a constructor, a case reading its fields). Bind each read field to its own let and drop the aggregate. The exact-use equation is the gate; any use ofxas a whole value (returned, passed, compared) turns it off.Prerequisites / Relations
Blocked by #231 (the Complexity/Capture analysis lattice): "read only field-wise, never as a whole" is the same exact-use accounting the lattice makes precise. The result-side dual is #206 (CPR: return components rather than a product); together they avoid materializing a box on either side of a call. Relates to #208 (SpecConstr), which removes the boxed accumulator this rule would otherwise unpack every iteration.
Verification / Measurement
A let-bound record read at two fields compiles with no table allocation and two direct bindings in
golden.lua; a record also used as a whole value keeps its allocation, the soundness guard, tested directly. Allocation-counter drop on a record-heavy benchmark through #172; eval goldens unchanged.