Repository navigation
perf(codegen): propagate Ptr<Shape> facts into guarded argument clones #8774
Description
Summary
Perry's Ptr<Shape> proof stops when a known-shape object is passed as a non-receiver call argument. The callee therefore performs guarded field ICs and repeated GC-header/shape-descriptor reads even when a monomorphic call site proves the argument's class.
Generalize the existing clone-and-route mechanism for proven this receivers to selected object argument positions: a guarded call should route a known-shape argument into a callee clone that can directly access its declared fields while retaining the tagged ABI, shadow rooting, and generic fallback.
Self-contained reproduction
class Entity {
constructor(id) {
this.id = id;
this.components = [];
}
}
class Registry {
add(entity, component) {
entity.components.push(component);
}
hash(entity) {
let value = entity.id;
for (let i = 0; i < entity.components.length; i++) {
value += entity.components[i];
}
return value;
}
clear(entity) {
entity.components.length = 0;
}
}
const registry = new Registry();
let checksum = 0;
const iterations = 200_000;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
const entity = new Entity(i);
registry.add(entity, 1);
registry.add(entity, 2);
checksum += registry.hash(entity);
registry.clear(entity);
}
console.log(JSON.stringify({
elapsedMs: performance.now() - start,
checksum,
}));Expected checksum: 20000500000.
Build with release artifacts and retain --trace llvm --opt-report=json --explain-lowering --no-cache output. The public integration case is ddmills/js-ecs-benchmarks, perform-ecs@0.7.8, suite Destroy.
Current evidence
The fresh lowering report for the real workload on Perry 20a388974d7333ccaec9da9175fbcee54ad49902 diagnoses the exact loss in perform-ecs/src/ECS.ts, closure#7:
local: entity
candidate class: Entity
outcome: denied
rule: rule 2 (containment)
reason: passed as a call argument. There is no mechanism yet by which a
shape fact at a call site becomes a fact about the callee's parameter.
The timed M1 profile collected 701 main-thread samples. shape_descriptor_by_id was the top leaf in 68 samples (9.7%) and try_read_tracked_gc_header in 62 (8.8%). Within ECS.addComponentsToEntity$pshape, field-get IC misses accounted for 47 samples in the create path plus 14 and 22 in the two later add paths (83 inclusive samples, 11.8% of the profile). The full case remains 13.268x slower than Node.
The closed design/scoping issue #7034 explicitly identified argument-position clone-and-route as the next Ptr<Shape> phase; it did not implement it. #8693/#8737 applies the same general idea to the receiver (this) position only.
Proposed direction
- For a direct/guarded call whose argument has a proven class/shape, build or select a callee clone annotated with a
Ptr<Shape>fact for that parameter. - Start with non-exported or producer-known callees and monomorphic argument positions; require all routed call sites to agree, or guard each call site and retain the boxed generic entry.
- Keep the public machine ABI tagged. On clone entry, shadow-bind the tagged parameter slot exactly as proven-
thisclones do, and re-derive the raw pointer from the rewritten slot after every safepoint. - Consume the parameter proof in direct declared-field loads/stores, then allow downstream inlining and scalar replacement to use the same fact.
- Record argument index, producer provenance, selected shape/class, guard, clone, and fallback in
--explain-lowering.
Semantic and GC constraints
- A runtime TypeScript annotation is not proof; wrong-class, subclass, proxy, cross-realm, or mutated-shape values must take the generic fallback.
- Preserve aliasing, identity, accessors, private brands, prototype mutation, exceptions, and method replacement semantics.
- A shadow root must provide liveness, relocation, and the value actually observed after GC. Do not copy the non-movable typed-array-pointer shortcut: ordinary objects move.
- Do not keep raw object pointers at rest or across calls/safepoints; write barriers remain required for pointer stores.
Acceptance criteria
- Register the reproduction as semantic/compiler-output coverage; Node and Perry print checksum
20000500000, normally and under forced-moving GC. - The stable
Registry.add/hash/clearclones carry a provenEntityargument and accessid/componentsdirectly without field-get IC diamonds orshape_descriptor_by_idon the fast arm. - The tagged generic entry and explicit guard-failure fallback remain present and are exercised.
- Negative tests cover multiple caller shapes, subclassing, shape mutation, accessors, proxies, alias/reassignment, exceptions, imports/re-exports, and forced-moving GC.
--explain-loweringno longer reports call-argument containment as the denial for the valid fixture and names every selected argument clone.- Re-run
perform-ecs/destroywith exact component-ID/view-count parity. Require at least a 10% median Perry improvement and 9/11 wins on the quiet M1 protocol; report shape/header/IC profile samples, RSS, and executable size.
Related work
- repsel row 1: scoping Ptr<Shape> beyond locals — measured, promotion is 0 on the motivating workload; build --opt-report (#6952) first #7034 is the completed measurement/design issue that specified argument clone-and-route as phase P3.
- perf(codegen): specialize imported class methods that capture this #8693/fix(runtime): fence the dense subclass seqlock; specialize imported this-methods and proven Symbols #8737 implemented proven-
thisrouting for imported class methods; this ticket generalizes it to non-receiver object arguments. - codegen: type-directed specialization — emit native ops where the type is statically known (#5334 Tier 3 / Lever E) #5497 is broader type-directed specialization.
Activity
fix(codegen): keep the argument-shape route guard when the barrier ki…
land #8833: ECS specializations, with the argument-route guard restor…