Use fuses to optimize instanceof more
Categories
(Core :: JavaScript Engine, task, P3)
Tracking
()
People
(Reporter: jandem, Unassigned)
References
(Blocks 3 open bugs)
Details
Attachments
(4 files)
On earley-boyer we spend a fair amount of time doing instanceof operations. For x instanceof Foo in the test case below we currently emit the following instructions:
(1) MNurseryObject + (2) MGuardShape for Foo
# for @@hasInstance
(3) MConstant + (4) MGuardShape for Function.prototype
(5) MSlots + (6) MLoadDynamicSlot to load Foo.prototype
(7) MInstanceOf(x, Foo.prototype)
The lowest hanging fruit here is the shape guard on Function.prototype for @@hasInstance shadowing and proto mutations - we should be able to eliminate (3) and (4) with object fuses.
It would be neat if we could also use object fuses for certain constructor functions (Foo here). This would let us bake in Foo.prototype as a constant and eliminate all of the remaining guard instructions. We could also use this to optimize CallIRGenerator::emitCallScriptedGuards where we guard on newTarget.prototype for constructor calls.
function Foo() {}
function test(x) {
return x instanceof Foo;
}
function run() {
with (this) {} // No Ion.
var f = new Foo();
var t = Date.now();
for (var i = 0; i < 100000; i++) {
test(f);
}
print(Date.now() - t);
}
run();
| Reporter | ||
Comment 1•4 months ago
|
||
Function.prototype[Symbol.hasInstance] is non-writable and non-configurable, and shape implies proto, so I think (3) and (4) might be redundant anyway in this case where Foo.__proto__ === Function.prototype.
Maybe here we could add a fast path for fun->proto == hasInstanceHolder because the shape guard on "fun" ensures @@hasInstance can't be shadowed.
Updated•4 months ago
|
Comment 2•3 months ago
|
||
I was going to leave a comment pointing out that a fuse for Foo.prototype would also help with constructors, before I noticed that it was already mentioned:
We could also use this to optimize CallIRGenerator::emitCallScriptedGuards where we guard on newTarget.prototype for constructor calls.
For function make(n) { return new Foo(n); }, we generate the following guards:
(1) MConstant + (2) MGuardShape for Foo
(3) Slots + (4) LoadDynamicSlotAndUnbox to load Foo.prototype
(5) MConstant to load expected Foo.prototype
(6) GuardObjectIdentity to compare loaded with expected
If we could use a fuse + constant load to load Foo.prototype instead, then I believe we could optimize away steps (3) through (6) entirely.
Comment 3•11 days ago
|
||
x instanceof Foo emits a shape guard on Foo plus an MConstant and shape
guard for Function.prototype (the @@hasInstance holder). When
Foo.[[Prototype]] is Function.prototype itself, the Foo shape guard
already implies its own properties and [[Prototype]], so the lookup of
@@hasInstance provably terminates at the immutable
Function.prototype[@@hasInstance] and the holder guards are redundant.
Skip them in that case, which is by far the most common one.
This removes two instructions (and their Warp transpilation) from every
instanceof stub for ordinary functions.
Comment 4•11 days ago
|
||
x instanceof Foo loads Foo.prototype from a slot on every call. With a
per-function ObjectFuse marking .prototype constant, Warp turns the load
into a compile-time constant (the fuse guard becomes an invalidation
dependency) and Baseline checks two words instead of loading the slot.
The HasObjectFuse flag is set lazily on the function when the IC first
attaches, so functions that are never optimized pay nothing. Setting the
flag reshapes the object, so shape and slot captures happen after
canOptimizeConstantFunctionPrototype returns.
Guard correctness:
- value overwrite: Watchtower::watchPropertyValueChange marks the property
NotConstant and invalidates dependent Ion scripts - delete/redefine: .prototype is non-configurable on functions
- [[Prototype]] change: still caught by the retained shape guard, which
matters because the fuse does not observe proto mutations of objects
that were never used as prototypes - different function with same shape: caught by the new specific-object
guard required because the fuse belongs to one object
Gated behind javascript.options.objectfuse_for_function_protos.
Comment 5•11 days ago
|
||
For 'new Foo()' the scripted-call IC guards newTarget's .prototype with a
shape guard, a slot load and an identity compare against the baked
prototype object. With canOptimizeConstantFunctionPrototype (previous
commit) all three collapse to guardSpecificObject plus
guardObjectFuseProperty: Warp drops them entirely in favour of the fuse
dependency, and metaCreateThis keeps its baked thisShape.
The specific-object guard is required even for bound callees because
emitCalleeGuard may pin BaseScript rather than identity, and lambda
clones share a script but not .prototype.
newTarget is rooted now because the helper can GC. Derived class
constructors are untouched (thisShape is null there). Depends on the
objectfuse_for_function_protos pref from the previous commit.
Comment 6•11 days ago
|
||
Extend the function-proto fuse to ordinary property loads: reading
Foo.prototype now marks the function's .prototype constant instead of
loading it from a slot, so Warp folds it into a compile-time constant.
This covers inheritance shims, Foo.prototype.x setup loops and
Foo.prototype.method dispatch outside instanceof and constructor calls,
and lets Foo.prototype.method fold to a single constant load.
The Specialized-mode check moves into
canOptimizeConstantFunctionPrototype so megamorphic attaches don't set
the HasObjectFuse flag pointlessly (also benefits the instanceof and
call sites).
The helper can GC (setting the flag reshapes the object), so the fused
path runs on rooted locals; PropertyInfo travels by value.
Comment 7•9 days ago
|
||
Comment 8•9 days ago
|
||
Do these patches affect the microbenchmark Jan posted? earley-boyer is a subtest of a subtest in JS3 right now, so it's not going to move the needle much, but this still seems potentially worth doing on principle.
Comment 9•8 days ago
|
||
Scratch that, earley-boyer is still an independent subtest, and it does seem to improve on every platform.
Comment 10•8 days ago
|
||
Use these patches as a "WIP starting point""
Description
•