Commit Graph
2 Commits
Author SHA1 Message Date
Malte Ubl 363a9360fd Keep copied nullable field reads and union binding copies in stack storage
A whole-value read of a nullable-pointer field allocated a heap box per
read, so list walks (`p = p.next`) regressed: 0.07 s -> 0.35 s on a 100k
list walked 200 times. Such reads now retain the instance pointer into
the current frame and wrap it in a private stack box with that
independent owner:

- local union storage accepts nullable field reads and copies of another
  binding of the same union as sources (the payload is retained; scalar
  and unit arms own nothing; retains branch on the tag because not every
  retain entry point is NULL-tolerant);
- arguments to projection-only parameters of calls and constructors.

The list walk now takes 0.05 s (baseline 0.07 s). Corpus 4061 adds a copy
that outlives a store to the field and copies across string, number,
-0 and undefined arms.
2026-10-08 15:34:37 -05:00
Malte Ubl bdd58b4705 Store nullable class fields as instance pointers and borrow constructor bodies
A class field typed `C | null` or `C | undefined` (one unit arm plus one
emitted class arm) now holds the instance pointer itself: NULL is the unit
arm. The struct slot stays `ptr`, so layouts are unchanged.

- Teardown, trace and gcFree use the slot's storage type (the class arm);
  every class retain/release/visit entry point is NULL-tolerant.
- Projections of a field read (tag tests, narrowing, truthiness, unionDisc,
  nullish, borrowed equality operands) use a private stack box over the
  borrowed pointer; whole-value reads build an owned heap box (or the
  immortal unit instance); generic borrowed receivers get that owned box.
- Stores take the payload of a wrap directly, or the retained payload of
  a borrowed union; a store of a union binding is a projection use, so
  constructor parameters stored this way are projection-only.
- `new` calls the borrowed constructor body when the lifetime analysis
  allows it, so projection-only arguments become private stack boxes.
- The dyn bridges (materialize, commit, symbol fields) convert between
  the pointer and a temporary union.

alloc-trees no longer allocates a union box per tree edge. Corpus 4061
pins tests, narrowing, nullish, optional chaining, equality with
mutation, escapes, reassignment, subclass instances, unionDisc, `any`
reads/writes, JSON, console output, cycles, and a 300k chain teardown.

macOS arm64 (H23 runner, --layouts=4 --runs=16, vs perf/harness):
alloc-trees -45.5%, records-sort -72.8%, numeric-kernels -29.4%,
ast-interp -21.3%, word-graph -12.4%; template-render +1.9% and
functional-pipeline +0.8% (both within 2%); geomean -17.6%.
2026-10-08 15:34:37 -05:00