Commit 0065945
* fix(codegen): scalar replacement ignored own-method writes and prototype mutation (#5872)
#5466 taught codegen's escape check to keep a `new C()` receiver
scalar-replaced across a method call when `simple_scalar_method_summary`
proves the method body is a numeric read of `this.<field>`. That summary
proves the method *body* is safe to inline; it does not prove that
`obj.method` still RESOLVES to that class method at the call site.
JS lets user code break the lookup after construction, and both shapes
reached the fast path:
* own-property shadow — `(obj as any).getValue = () => 99` writes an own
property that wins over the prototype method. The escape check treats a
literal-key property write on a candidate as a plain field store, so the
receiver stayed scalar-replaced and `obj.getValue()` folded to the field
value: 14 instead of 99.
* prototype mutation — `C.prototype.getValue = fn`,
`Object.defineProperty(C.prototype, …)`, `C.prototype[key] = fn`, or a
helper/constructor/field-initializer that does any of those. None of it
touches the receiver local, so a per-function walk cannot see it.
Before #5466 every method call escaped its receiver, so the only layer that
folded `obj.m()` into a field read was perry-transform's exact-receiver
inliner — which invalidates its facts on exactly these mutations.
Restores the missing half with two narrow fact sets:
* `ModuleDispatchFacts` (module-scoped): every class whose prototype is
*named* anywhere in the module — top-level init, any function body, any
constructor / field initializer / method / accessor / computed member.
Naming is enough, because a named prototype can be aliased and written
through later. A prototype named through an expression that can't be
pinned to a declared class marks the whole module opaque. Function-classic
prototypes (`function F(){}; F.prototype.m = …`) are keyed by a synthetic
class id and cannot rewrite a declared class's table, so they don't count.
* per-candidate own-property writes (function-scoped): the literal property
names written directly on each scalar-replacement candidate.
A summarized method call keeps its receiver off the heap only when the
receiver's class chain has a stable prototype and no own write shadows the
called method; otherwise the receiver escapes and takes the ordinary
heap-allocate + dispatch path, which observes the shadow exactly like Node.
Only receivers of summarized method calls are affected — plain field scalar
replacement is untouched.
Also repairs the IR guard itself, which has been failing on main since #5294
independently of this bug: it asserted the unsafe receivers allocate by
grepping for `js_inline_arena_state`, but #5294 outlined the per-new-site
inline bump allocator, so class instances now allocate via
`js_object_alloc_class_inline_keys`. The guard therefore reported "scalarized
instead of allocated" even for correctly heap-allocated receivers. It now
accepts the whole allocation-helper family — and still fails on unfixed main,
where `ownMethodWrite()` genuinely emits no allocation at all.
test-files/test_issue_945_scalar_method_guards.ts is byte-identical to
`node --experimental-strip-types` again (own method write 99, computed method
write 101, loop mutation receiver 132).
* test(codegen): unit-cover the #5872 scalar-method dispatch guard
compile-smoke (the IR guard) and parity only run on tag pushes / labeled PRs,
which is why #5466 could land this regression green. These run in cargo-test,
the PR gate:
* an own-property write shadowing the called method escapes the receiver
(the #5872 repro), including when the write is nested in a loop;
* a write to a different property name does not;
* a prototype mutation anywhere in the module — including from an unrelated
function — escapes the receiver, while an unrelated class's mutation does
not;
* merely naming a class prototype marks it unstable, an unattributable
prototype access marks the whole module opaque, and a default fact set is
conservative.
Also fixes the two existing hir_facts tests for the new fact-graph arity.
---------
Co-authored-by: Ralph Küpper <ralph@skelpo.com>
1 parent 7a3b48a commit 0065945
11 files changed
Lines changed: 741 additions & 4 deletions
File tree
- crates/perry-codegen/src
- codegen
- collectors
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
738 | 738 | | |
739 | 739 | | |
740 | 740 | | |
| 741 | + | |
741 | 742 | | |
742 | 743 | | |
743 | 744 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
552 | 552 | | |
553 | 553 | | |
554 | 554 | | |
| 555 | + | |
555 | 556 | | |
556 | 557 | | |
557 | 558 | | |
| |||
1112 | 1113 | | |
1113 | 1114 | | |
1114 | 1115 | | |
| 1116 | + | |
1115 | 1117 | | |
1116 | 1118 | | |
1117 | 1119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
456 | 456 | | |
457 | 457 | | |
458 | 458 | | |
| 459 | + | |
459 | 460 | | |
460 | 461 | | |
461 | 462 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
371 | 371 | | |
372 | 372 | | |
373 | 373 | | |
| 374 | + | |
374 | 375 | | |
375 | 376 | | |
376 | 377 | | |
| |||
1295 | 1296 | | |
1296 | 1297 | | |
1297 | 1298 | | |
| 1299 | + | |
1298 | 1300 | | |
1299 | 1301 | | |
1300 | 1302 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1535 | 1535 | | |
1536 | 1536 | | |
1537 | 1537 | | |
| 1538 | + | |
1538 | 1539 | | |
1539 | 1540 | | |
1540 | 1541 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
728 | 728 | | |
729 | 729 | | |
730 | 730 | | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
731 | 737 | | |
732 | 738 | | |
733 | 739 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
54 | 73 | | |
55 | 74 | | |
56 | 75 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
306 | 306 | | |
307 | 307 | | |
308 | 308 | | |
| 309 | + | |
309 | 310 | | |
310 | 311 | | |
311 | 312 | | |
| |||
323 | 324 | | |
324 | 325 | | |
325 | 326 | | |
326 | | - | |
327 | | - | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
328 | 334 | | |
329 | 335 | | |
330 | 336 | | |
| |||
415 | 421 | | |
416 | 422 | | |
417 | 423 | | |
| 424 | + | |
418 | 425 | | |
419 | 426 | | |
420 | 427 | | |
| |||
426 | 433 | | |
427 | 434 | | |
428 | 435 | | |
| 436 | + | |
429 | 437 | | |
430 | 438 | | |
431 | 439 | | |
| |||
447 | 455 | | |
448 | 456 | | |
449 | 457 | | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
450 | 461 | | |
451 | 462 | | |
452 | 463 | | |
| |||
1653 | 1664 | | |
1654 | 1665 | | |
1655 | 1666 | | |
| 1667 | + | |
1656 | 1668 | | |
1657 | 1669 | | |
1658 | 1670 | | |
| |||
1742 | 1754 | | |
1743 | 1755 | | |
1744 | 1756 | | |
| 1757 | + | |
1745 | 1758 | | |
1746 | 1759 | | |
1747 | 1760 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
75 | 76 | | |
76 | 77 | | |
77 | 78 | | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
78 | 82 | | |
79 | 83 | | |
80 | 84 | | |
| |||
0 commit comments