Skip to content

Drop guards on private constructor stores and borrow construction-only fields - #838

Open
cramforce wants to merge 1 commit into
perf/trace-cycle-candidatesfrom
perf/static-immutability
Open

cramforce wants to merge 1 commit into
perf/trace-cycle-candidatesfrom
perf/static-immutability

Conversation

@cramforce

Copy link
Copy Markdown
Contributor

Two uses of the construction-only facts outside the collector, in backend/immutability.ts:

  • Publication guards. A store into the constructor's own this that is not yet observable cannot target a published object. The conditions: the store comes before the constructor's escape point, no ancestor constructor escapes, and the constructor is never invoked directly on an existing object. Such a store loses its rc == SIZE_MAX guard. It also stops seeding may-throw, so constructors that could only throw through guards lose their pending checks at every new.
  • Borrowed reads. x.f (an object, record, array or string) is borrowed as a call argument, receiver or stable alias when f is construction-only and x is itself borrowable. Nothing can replace and release it while x lives. This does not apply inside constructors. Module constants (immutable, not TDZ-checked, assigned at most once) are borrowable too.

Nothing is frozen or marked at run time. Object.isFrozen, publication errors and store order are unchanged.

Measured on tsc-ts, both PRs against #830, Linux sandbox, interleaved medians:

single-threaded (6 runs)   base     new     change
Excalidraw                 8.62 s   8.34 s  -3.2%
Playwright                 7.83 s   7.71 s  -1.5%
tsc-ts self-check          4.36 s   4.22 s  -3.0%
tRPC                       1.63 s   1.58 s  -3.1%
TypeORM                    6.68 s   6.50 s  -2.5%
--checkers 8 (10 runs): -2.3% .. +0.7% (neutral)

Static counts: published-field guards 6,528 → 4,462, sc_release calls −11%, sc_retain calls −16%. Binary 23.68 → 22.96 MB.

Executed on the self-check: guards 96.1M → 60.8M, retains −9.6%, releases −7.7%.

Removing every publication guard (unsound, measurement only) stays within noise, so the time comes from borrowing and tracing, not from the guards.

Runtime suite (--layouts=4 --runs=16): geomean −1.9%. ast-interp −4.4%, validate-errors −3.8% and regex-logs −1.7% are faster. template-render is +1.4% slower. The rest are neutral, and sizes are unchanged.

Tests: new immutability.test.ts unit tests, and an adjusted string-lifetimes test (its record field was never written, so the borrow is sound; the test now adds a writer to keep covering the snapshot path). New corpus programs:

  • 4598-publish-constructor-stores: escaping constructors must still throw Node's frozen-object errors.
  • 4599-construction-only-field-borrows: callees drop every other reference before using a borrowed field; a field written by a method; a constructor reading its own field while a later operand replaces it.
  • 4600-construction-only-fields-dynamic-writes: writes through any and structural casts while the field is being read.

All pass the plain and sanitized lanes. Preflight/order baselines are recorded for the five new fixtures.

Known risk: borrowed field reads rely on every field write being visible in the IR or in the untyped-store audit; a runtime helper writing class fields directly would become a use-after-free. Stacked on the cycle-tracing PR.

…y fields

Two uses of the construction-only facts outside the collector
(backend/immutability.ts):

- Publication guards: a store into the constructor's own `this` that lies
  before the constructor's escape point, in a constructor no ancestor of
  which lets `this` escape and that is never invoked directly on an
  existing object, cannot target a published object. It loses its
  rc == SIZE_MAX guard and no longer seeds may-throw, so constructors that
  could only throw through guards lose their pending checks at every `new`.
  Storing `this` into one of its own fields does not expose it.
- Borrowed reads: `x.f` holding an object, record, array or string is
  borrowed as a call argument, receiver or stable alias when `f` is
  construction-only and `x` is itself borrowable (unchanged local,
  borrowed parameter, or such a projection): nothing can replace and
  release it while `x` lives. Not inside constructors, which may still
  write their own fields. Module constants (immutable, not TDZ-checked,
  assigned at most once) are borrowable too.

No object is frozen or marked at run time; publication errors, store order
and reflection are unchanged.

tsc-ts (e469e33), static: published-field guards 6,528 -> 4,462,
sc_release calls 146,056 -> 129,756, sc_retain calls 26,215 -> 22,123,
scr_arr_release 29,865 -> 25,617, binary 23.68 -> 22.96 MB (both commits).
Self-check single-threaded, executed: guards 96.1M -> 60.8M, retains
273.5M -> 247.2M, releases 340.3M -> 314.0M.

Linux sandbox, interleaved medians, both commits against
perf/alloc-hugepages:

  single-threaded (6 runs)   base     new     change
  Excalidraw                 8.62 s   8.34 s  -3.2%
  Playwright                 7.83 s   7.71 s  -1.5%
  tsc-ts self-check          4.36 s   4.22 s  -3.0%
  tRPC                       1.63 s   1.58 s  -3.1%
  TypeORM                    6.68 s   6.50 s  -2.5%

  --checkers 8 (10 runs): -2.3% .. +0.7% (neutral)

Runtime suite (--layouts=4 --runs=16): geomean -1.9%; ast-interp -4.4%,
validate-errors -3.8%, regex-logs -1.7% faster, template-render +1.4%
slower, the rest neutral; sizes unchanged.
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Oct 11, 2026 3:41am UTC

@vercel-security-reviewer

Copy link
Copy Markdown

Security review details

This branch was successfully deployed

1 active (outdated) deployment
Preview — 2ae8459a Deployed Oct 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant