You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
repsel: dependency JS is walled by rule 1 (unbound allocations), not containment — 506 of 746 candidates #7152
#7139 recovered rule-5 eligibility for 84 of 85 CommonJS dependency modules and
recorded that of the ~103 Ptr<Shape> candidates it freed, 100 were
immediately re-denied by rule 2 — reading as "containment is the wall in
dependency JS". Track D (#7034 §3, PR #7149) was scheduled on that basis.
Measured, it is not containment. Two compiler arms — #7139 alone, and #7139 + #7149 — over 180 real __esModule CJS modules from real-apps/scriptc/node_modules (perry compile m.js --opt-report --no-link,
180/180 compiled in both, every count identical between arms):
bucket
candidates
rule 1 — allocated in expression position, never bound to a local
506
rule 5 — module barrier still armed
99
rule 2 — bare reference
130
rule 2 — call argument
5
rule 2 — array element
1
rule 2 — closure capture
1
rule 2 — undeclared property
1
total candidates
746
selected / consumed
3 / 0
Two things fall out.
1. Rule 1 is the wall, and its shape is known
The 506 unbound allocations break down by position:
position
count
constructor argument
182
statement
162
call argument
84
return
64
array element
8
initializer
6
new X(…) written directly into an argument, a statement, or a return, never
bound to a let/const. Rule 1 needs a local to anchor the proof to, and
transpiled/minified code does not create one. This is the .map(x => ({…})) / return {…} idiom the report already names — at 68% of all candidates it is
larger than every containment position combined by a factor of ~3.5.
2. The rule-2 "bare reference" denials are Perry's own scaffolding
All 130 are the local __cjs_module. That is the CommonJS wrapper's own
binding, not user code — the same family #7139 was narrowing. Whether it is
worth exempting depends on whether __cjs_module's object is ever a useful Ptr<Shape> candidate at all; if it is not, it should not be a candidate,
and the 130 denials are noise in every report.
Caveat on the corpus
This is not #7139's exact 85-module set (that selection was
"self-contained __esModule leaves"; this one is every third __esModule file
by sorted path, requires allowed). A tighter 82-module self-contained set gave
the same shape: 185 candidates, 100 rule 1, 82 rule 2 — all 82 __cjs_module
bare references, 0 element. The conclusion does not depend on the sample.
What this changes
Nothing about #7149 — it pays on app-shaped TypeScript, where it removes every
guard diamond from a build-then-consume loop. But call argument (#7034 §1)
should not be scheduled on the assumption that it unlocks dependency JS
either: it is 5 of 746. The dependency-JS lever is rule 1, and its fix is a
different mechanism — anchoring the proof to an allocation site rather than to
a binding.
PR #7171 handles §2 (the __cjs_module bare references) and, in doing so,
changes the arithmetic under §1. Two things worth recording here before anyone
schedules work off the 506.
1. The bare references are not exemptable, only un-candidate-able — and that
is now done. The reference is var module = __cjs_module;, the statement cjs_wrap's own preamble emits immediately after the record. module is a
reassignable var that the same preamble stores into require.main, and that
CJS bodies write module.exports = X through; real packages also read module.id / .filename / .loaded. The record genuinely escapes for its
whole life, so no narrowing of rule 2 could promote it — and promotion would
buy nothing anyway (a one-field record, three reads at module init, loop depth
0). So #7171 drops it at the seed rather than exempting it at the rule, which
is what this issue proposed. The recogniser requires the aliasing statement to
be present, which is what makes the drop provably fact-neutral: that statement is the denial.
2. The rule-1 "constructor argument" bucket is the same scaffolding, and the 506 needs re-deriving rather than adjusting.const __cjs_module = { exports: {} } contains an inner {} allocated in constructor-argument
position — one per CommonJS module, exactly the shape rule 1 counts as an
unbound allocation. On a 195-module sample (every third __esModule file by
sorted path; not this issue's 180-module set — that tree has drifted):
188 of the 194 object literal { ... } constructor-argument rows were that
inner {}. 136 of the 140 bare references and 55 of the rule-5 denials were __cjs_module. 379 rows, ~2 per module, 31 % of every Ptr<Shape> candidate
in the corpus, all of it Perry's own template.
A caveat that matters for anyone re-measuring: the report dedups
allocation-site rows per function on (module, function, name, position, rule)
and every object literal renders as object literal { ... }, so each wrapped
module contributes exactly one alloc-site row. Which context that row reports
is whichever preamble literal the walk reaches first. Two consequences:
What did not change: nothing was promoted. Selected/consumed are 3/11 in
both arms on every module in the corpus, by construction — every suppressed
value was already denied. This is a report-accuracy fix, not a performance one.
The dependency-JS lever is still rule 1, and still needs the different
mechanism this issue describes (anchoring the proof to an allocation site
rather than to a binding); it is just smaller than 506 by an amount that has to
be measured, not subtracted.
Both halves are resolved on main @ 514ff7c9a — recommend close, with a fresh census
§2 — the 130 __cjs_module bare references: fixed and landed
4cede62f6 (#7171) is on main. Re-measured below, rule-2 containment denials
are 23 of 838 ptr-shape denials (2.7%), down from this issue's 130 of
746 (17.4%). The bucket this section was about is gone.
§1 — "rule 1 is the wall": replicates, and the number is now computed by the compiler
ed5f1c749 (#7176, R0) is on main, so summary.by_rule and summary.by_alloc_context come out of --opt-report=json directly — the
re-derivation this issue's numbers needed is now a file read, not a jq line
in a comment. c9cd73ba5 (#7233, R1) is on main too.
Caveat first: real-apps/scriptc/node_modules no longer exists in the tree,
so this issue's exact 180-module slice cannot be reproduced. I used a different
sample of the same kind of code — every third __esModule.js under the
repo's own node_modules (376 matched, 126 selected, each copied to its own
directory), perry compile m.js --no-link --opt-report=json, --profile perry-dev. 124 produced a report, 2 did not. Treat this as a replication of
the shape, not a strict before/after.
Note the return split: 70 served / 44 unserved. Pre-R1 that was 4
served of 1842 (0.2%). #7233 is visibly working.
Recommendation
Close. §2 is fixed on main; §1's finding is confirmed but its number now
lives in the instrument (summary.by_rule) and its reading was corrected by #7170 §5, whose R0/R1 have both landed. The live tracker for the rule-1 wall is #7170, and everything actionable that remains is ranked there.
Unmeasured: the original scriptc corpus (deleted); x86-64; anything about
speed — this is a static census only.
The live consequences were tracked on #7170, which is also being closed with its
own re-census; the two remaining tiers live on #6759 (heap-position provenance)
and #7142 (the runtime fallback tier).
Activity
proggeramlug commentedon Aug 1, 2026
PR #7171 handles §2 (the
__cjs_modulebare references) and, in doing so,changes the arithmetic under §1. Two things worth recording here before anyone
schedules work off the 506.
1. The bare references are not exemptable, only un-candidate-able — and that
is now done. The reference is
var module = __cjs_module;, the statementcjs_wrap's own preamble emits immediately after the record.moduleis areassignable
varthat the same preamble stores intorequire.main, and thatCJS bodies write
module.exports = Xthrough; real packages also readmodule.id/.filename/.loaded. The record genuinely escapes for itswhole life, so no narrowing of rule 2 could promote it — and promotion would
buy nothing anyway (a one-field record, three reads at module init, loop depth
0). So #7171 drops it at the seed rather than exempting it at the rule, which
is what this issue proposed. The recogniser requires the aliasing statement to
be present, which is what makes the drop provably fact-neutral: that statement
is the denial.
2. The rule-1 "constructor argument" bucket is the same scaffolding, and the
506needs re-deriving rather than adjusting.const __cjs_module = { exports: {} }contains an inner{}allocated in constructor-argumentposition — one per CommonJS module, exactly the shape rule 1 counts as an
unbound allocation. On a 195-module sample (every third
__esModulefile bysorted path; not this issue's 180-module set — that tree has drifted):
188 of the 194
object literal { ... }constructor-argument rows were thatinner
{}. 136 of the 140 bare references and 55 of the rule-5 denials were__cjs_module. 379 rows, ~2 per module, 31 % of everyPtr<Shape>candidatein the corpus, all of it Perry's own template.
A caveat that matters for anyone re-measuring: the report dedups
allocation-site rows per function on
(module, function, name, position, rule)and every object literal renders as
object literal { ... }, so each wrappedmodule contributes exactly one alloc-site row. Which context that row reports
is whichever preamble literal the walk reaches first. Two consequences:
constructor argumenttostatementand changed the total by zero — which is why perf(repsel): stop charging Perry's own cjs_wrap preamble to the Ptr<Shape> report (#7152) #7171also stops walking the four allocating preamble statements behind it.
masked by them (
statement273 → 320,call argument175 → 187). So theresidual rule-1 population is not
506 − 180. Re-derive it against thepost-perf(repsel): stop charging Perry's own cjs_wrap preamble to the Ptr<Shape> report (#7152) #7171 compiler.
What did not change: nothing was promoted. Selected/consumed are 3/11 in
both arms on every module in the corpus, by construction — every suppressed
value was already denied. This is a report-accuracy fix, not a performance one.
The dependency-JS lever is still rule 1, and still needs the different
mechanism this issue describes (anchoring the proof to an allocation site
rather than to a binding); it is just smaller than 506 by an amount that has to
be measured, not subtracted.
perf(repsel): stop charging Perry's own cjs_wrap preamble to the Ptr<…
perf(repsel): make the Ptr<Shape> alloc-site buckets mean what schedu…
perf(repsel): make the Ptr<Shape> alloc-site buckets mean what schedu…
proggeramlug commentedon Aug 14, 2026
Both halves are resolved on
main@514ff7c9a— recommend close, with a fresh census§2 — the 130
__cjs_modulebare references: fixed and landed4cede62f6(#7171) is onmain. Re-measured below, rule-2 containment denialsare 23 of 838 ptr-shape denials (2.7%), down from this issue's 130 of
746 (17.4%). The bucket this section was about is gone.
§1 — "rule 1 is the wall": replicates, and the number is now computed by the compiler
ed5f1c749(#7176, R0) is onmain, sosummary.by_ruleandsummary.by_alloc_contextcome out of--opt-report=jsondirectly — there-derivation this issue's numbers needed is now a file read, not a
jqlinein a comment.
c9cd73ba5(#7233, R1) is onmaintoo.Caveat first:
real-apps/scriptc/node_modulesno longer exists in the tree,so this issue's exact 180-module slice cannot be reproduced. I used a different
sample of the same kind of code — every third
__esModule.jsunder therepo's own
node_modules(376 matched, 126 selected, each copied to its owndirectory),
perry compile m.js --no-link --opt-report=json,--profile perry-dev. 124 produced a report, 2 did not. Treat this as a replication ofthe shape, not a strict before/after.
ptr-shape, 124 modules: 10 selected / 838 denied / 36 consumed.Rule 1 = 796 of 838 = 95.0%. This issue's central claim holds on an
independent corpus with a compiler three instrument-fixes newer.
By allocation position (784 alloc-site rows):
Note the
returnsplit: 70 served / 44 unserved. Pre-R1 that was 4served of 1842 (0.2%). #7233 is visibly working.
Recommendation
Close. §2 is fixed on
main; §1's finding is confirmed but its number nowlives in the instrument (
summary.by_rule) and its reading was corrected by#7170 §5, whose R0/R1 have both landed. The live tracker for the rule-1 wall is
#7170, and everything actionable that remains is ranked there.
Unmeasured: the original scriptc corpus (deleted); x86-64; anything about
speed — this is a static census only.
proggeramlug commentedon Aug 14, 2026
Closing — both halves are resolved on
main, per the census above.__cjs_modulebare references): fixed by4cede62f6(perf(repsel): stop charging Perry's own cjs_wrap preamble to the Ptr<Shape> report (#7152) #7171).Re-measured, rule-2 containment denials are 23 of 838 ptr-shape denials
(2.7%), down from 130 of 746 (17.4%).
independent 124-module dependency corpus with the post-perf(repsel): make the Ptr<Shape> alloc-site buckets mean what schedulers read them as (#7170 R0) #7176/perf(repsel): make Ptr<Shape> return-shape facts reachable inside the CJS IIFE (#7170 R1) #7233 compiler.
The number is now computed by the instrument itself
(
summary.by_rule/summary.by_alloc_contextin--opt-report=json), whichwas the point of perf(repsel): make the Ptr<Shape> alloc-site buckets mean what schedulers read them as (#7170 R0) #7176, so re-deriving it is a file read rather than a
jqline in an issue comment.
The live consequences were tracked on #7170, which is also being closed with its
own re-census; the two remaining tiers live on #6759 (heap-position provenance)
and #7142 (the runtime fallback tier).
fix(runtime): an Error subclass has a .stack and an [object Error] ta…