Bug 1054906 - Implement ES6 Symbol.hasInstance 2/2; r=jandem
authorMorgan Phillips <winter2718@gmail.com>
Thu, 02 Jun 2016 14:30:35 -0700
changeset 376151 6394d8078dfe6a02451a5cc4fbf4ec7666ce5357
parent 376150 35cc4a2451cc1aa71253c29c702db30e74c5f8ff
child 376152 24cb98ca61eae5629a28333cbd6e4fa22fc34a4f
push id20510
push usercholler@mozilla.com
push dateTue, 07 Jun 2016 13:42:30 +0000
reviewersjandem
bugs1054906
milestone49.0a1
Bug 1054906 - Implement ES6 Symbol.hasInstance 2/2; r=jandem
js/src/jit/BaselineIC.cpp
js/src/jit/IonBuilder.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/tests/ecma_6/Function/has-instance-jitted.js
js/src/vm/Runtime.h
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -7758,16 +7758,30 @@ ICIteratorClose_Fallback::Compiler::gene
 static bool
 TryAttachInstanceOfStub(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub,
                         HandleFunction fun, bool* attached)
 {
     MOZ_ASSERT(!*attached);
     if (fun->isBoundFunction())
         return true;
 
+    // If the user has supplied their own @@hasInstance method we shouldn't
+    // clobber it.
+    if (!js::FunctionHasDefaultHasInstance(fun, cx->wellKnownSymbols()))
+        return true;
+
+    // Refuse to optimize any function whose [[Prototype]] isn't
+    // Function.prototype.
+    if (!fun->hasStaticPrototype() || fun->hasUncacheableProto())
+        return true;
+
+    Value funProto = cx->global()->getPrototype(JSProto_Function);
+    if (funProto.isObject() && fun->staticPrototype() != &funProto.toObject())
+        return true;
+
     Shape* shape = fun->lookupPure(cx->names().prototype);
     if (!shape || !shape->hasSlot() || !shape->hasDefaultGetter())
         return true;
 
     uint32_t slot = shape->slot();
     MOZ_ASSERT(fun->numFixedSlots() == 0, "Stub code relies on this");
 
     if (!fun->getSlot(slot).isObject())
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -13882,20 +13882,46 @@ IonBuilder::jsop_instanceof()
     // If this is an 'x instanceof function' operation and we can determine the
     // exact function and prototype object being tested for, use a typed path.
     do {
         TemporaryTypeSet* rhsTypes = rhs->resultTypeSet();
         JSObject* rhsObject = rhsTypes ? rhsTypes->maybeSingleton() : nullptr;
         if (!rhsObject || !rhsObject->is<JSFunction>() || rhsObject->isBoundFunction())
             break;
 
+        // Refuse to optimize anything whose [[Prototype]] isn't Function.prototype
+        // since we can't guarantee that it uses the default @@hasInstance method.
+        if (rhsObject->hasUncacheableProto() || !rhsObject->hasStaticPrototype())
+            break;
+
+        Value funProto = script()->global().getPrototype(JSProto_Function);
+        if (!funProto.isObject() || rhsObject->staticPrototype() != &funProto.toObject())
+            break;
+
+        // If the user has supplied their own @@hasInstance method we shouldn't
+        // clobber it.
+        JSFunction* fun = &rhsObject->as<JSFunction>();
+        const WellKnownSymbols* symbols = &compartment->runtime()->wellKnownSymbols();
+        if (!js::FunctionHasDefaultHasInstance(fun, *symbols))
+            break;
+
+        // Ensure that we will bail if the @@hasInstance property or [[Prototype]]
+        // change.
         TypeSet::ObjectKey* rhsKey = TypeSet::ObjectKey::get(rhsObject);
+        if (!rhsKey->hasStableClassAndProto(constraints()))
+            break;
+
         if (rhsKey->unknownProperties())
             break;
 
+        HeapTypeSetKey hasInstanceObject =
+            rhsKey->property(SYMBOL_TO_JSID(symbols->hasInstance));
+        if (hasInstanceObject.isOwnProperty(constraints()))
+            break;
+
         HeapTypeSetKey protoProperty =
             rhsKey->property(NameToId(names().prototype));
         JSObject* protoObject = protoProperty.singleton(constraints());
         if (!protoObject)
             break;
 
         rhs->setImplicitlyUsedUnchecked();
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1152,16 +1152,30 @@ fun_toStringHelper(JSContext* cx, Handle
         return nullptr;
     }
 
     RootedFunction fun(cx, &obj->as<JSFunction>());
     return FunctionToString(cx, fun, indent != JS_DONT_PRETTY_PRINT);
 }
 
 bool
+js::FunctionHasDefaultHasInstance(JSFunction* fun, const WellKnownSymbols& symbols)
+{
+    jsid id = SYMBOL_TO_JSID(symbols.hasInstance);
+    Shape* shape = fun->lookupPure(id);
+    if (shape) {
+        if (!shape->hasSlot() || !shape->hasDefaultGetter())
+            return false;
+        const Value hasInstance = fun->as<NativeObject>().getSlot(shape->slot());
+        return IsNativeFunction(hasInstance, js::fun_symbolHasInstance);
+    }
+    return true;
+}
+
+bool
 js::fun_toString(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(IsFunctionObject(args.calleev()));
 
     uint32_t indent = 0;
 
     if (args.length() != 0 && !ToUint32(cx, args[0], &indent))
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -672,16 +672,21 @@ DefineFunction(JSContext* cx, HandleObje
                gc::AllocKind allocKind = gc::AllocKind::FUNCTION);
 
 bool
 FunctionHasResolveHook(const JSAtomState& atomState, jsid id);
 
 extern bool
 fun_toString(JSContext* cx, unsigned argc, Value* vp);
 
+struct WellKnownSymbols;
+
+extern bool
+FunctionHasDefaultHasInstance(JSFunction* fun, const WellKnownSymbols& symbols);
+
 extern bool
 fun_symbolHasInstance(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 OrdinaryHasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v, bool* bp);
 
 /*
  * Function extended with reserved slots for use by various kinds of functions.
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/has-instance-jitted.js
@@ -0,0 +1,96 @@
+const OriginalHasInstance = Function.prototype[Symbol.hasInstance];
+
+// Ensure that folding doesn't impact user defined @@hasInstance methods.
+{
+    function Test() {
+        this.x = 1;
+    }
+
+    Object.defineProperty(Test, Symbol.hasInstance,
+                          {writable: true, value: () => false});
+
+    function x(t) {
+        return t instanceof Test;
+    }
+
+    function y() {
+        let t = new Test;
+        let b = true;
+        for (let i = 0; i < 10; i++) {
+            b = b && x(t);
+        }
+        return b;
+    }
+
+
+    function z() {
+        let f = 0;
+        let t = 0;
+        for (let i = 0; i < 100; i++)
+            assertEq(y(), false);
+    }
+
+    z();
+}
+
+// Ensure that the jitting does not clobber user defined @@hasInstance methods.
+{
+    function a() {
+        function b() {};
+        b.__proto__ = a.prototype;
+        return b;
+    };
+    let c = new a();
+
+    let t = 0;
+    let f = 0;
+    let e = 0;
+    for (let i = 0; i < 40000; i++) {
+        if (i == 20000)
+            Object.defineProperty(a.prototype, Symbol.hasInstance,
+                                  {writable: true, value: () => true});
+        if (i == 30000)
+            Object.setPrototypeOf(c, Function.prototype);
+
+        if (1 instanceof c) {
+            t++;
+        } else {
+            f++;
+        }
+    }
+
+    assertEq(t, 10000);
+    assertEq(f, 30000);
+}
+
+{
+    function a() {};
+    function b() {};
+    Object.defineProperty(a, Symbol.hasInstance, {writable: true, value: () => true});
+    assertEq(b instanceof a, true);
+    for (let _ of Array(10000))
+        assertEq(b instanceof a, true);
+}
+
+{
+    function a(){};
+    function b(){};
+    function c(){};
+    function d(){};
+    function e(){};
+    Object.defineProperty(a, Symbol.hasInstance, {value: () => true });
+    Object.defineProperty(b, Symbol.hasInstance, {value: () => true });
+    Object.defineProperty(c, Symbol.hasInstance, {value: () => true });
+    Object.defineProperty(d, Symbol.hasInstance, {value: () => true });
+    let funcs = [a, b, c, d];
+    for (let f of funcs)
+        assertEq(e instanceof f, true);
+
+    for (let _ of Array(10001)) {
+        for (let f of funcs)
+            assertEq(e instanceof f, true);
+    }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -470,16 +470,20 @@ struct WellKnownSymbols
         MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
         const ImmutableSymbolPtr* symbols = reinterpret_cast<const ImmutableSymbolPtr*>(this);
         return symbols[u];
     }
 
     const ImmutableSymbolPtr& get(JS::SymbolCode code) const {
         return get(size_t(code));
     }
+
+    WellKnownSymbols() {}
+    WellKnownSymbols(const WellKnownSymbols&) = delete;
+    WellKnownSymbols& operator=(const WellKnownSymbols&) = delete;
 };
 
 #define NAME_OFFSET(name)       offsetof(JSAtomState, name)
 
 inline HandlePropertyName
 AtomStateOffsetToName(const JSAtomState& atomState, size_t offset)
 {
     return *reinterpret_cast<js::ImmutablePropertyNamePtr*>((char*)&atomState + offset);