Inline caching is wrong for custom accessors and custom values
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 Oct 2019 00:50:46 +0000 (00:50 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 Oct 2019 00:50:46 +0000 (00:50 +0000)
https://bugs.webkit.org/show_bug.cgi?id=201994
<rdar://problem/50850326>

Reviewed by Yusuke Suzuki.

JSTests:

* microbenchmarks/custom-accessor-materialized.js: Added.
(assert):
(test4.get const):
* microbenchmarks/custom-accessor-thin-air.js: Added.
(assert):
(test5.get const):
(test5.get proto):
* microbenchmarks/custom-accessor.js: Added.
(assert):
(test3.get const):
* microbenchmarks/custom-value-2.js: Added.
(assert):
(test1.getMultiline):
(test1):
* microbenchmarks/custom-value.js: Added.
(assert):
(test1.getMultiline):
(test1):
* stress/custom-accessor-delete-1.js: Added.
(assert):
(test3.get const):
* stress/custom-accessor-delete-2.js: Added.
(assert):
(test4.get const):
* stress/custom-accessor-delete-3.js: Added.
(assert):
(test5.get const):
(test5.get proto):
* stress/custom-value-delete-property-1.js: Added.
(assert):
(test1.getMultiline):
(test1):
* stress/custom-value-delete-property-2.js: Added.
(test2.foo):
(test2):
* stress/custom-value-delete-property-3.js: Added.
(test6.foo):
(test6):

Source/JavaScriptCore:

There was an oversight in our inline caching code for custom accessors and
custom values. We used to assume that if an object O had a custom function for
property P, then O will forever respond to the same custom function for
property P.

This assumption was very wrong. These custom accessors/values might be
properties in JS which are configurable, so they can be rewritten to be
other properties. Our inline caching code would be wrong in the scenarios
where these property descriptors got redefined.

This patch makes it so that we now properly watchpoint for custom functions
being changed. If the custom accessor has been materialized, we place an
Equivalence watchpoint on the custom accessor. This patch also teaches
StructureStubInfo how to watchpoint on property value equivalence. Before,
we just watchpointed on structure transitions.

This patch also adds a new property condition kind for when the custom function
exists inside the static property table. This case is really easy to test for
because we just need to see if the structure still has static properties and
the static property table has the entry for a particular property. This
property condition kind just needs to watch for structure transitions because
an entry in the static property table can't be mutated.

This patch is neutral on the microbenchmarks I've added.

* bytecode/AccessCase.cpp:
(JSC::AccessCase::AccessCase):
(JSC::AccessCase::couldStillSucceed const):
(JSC::AccessCase::generateImpl):
* bytecode/AdaptiveInferredPropertyValueWatchpointBase.h:
* bytecode/ObjectPropertyCondition.cpp:
(JSC::ObjectPropertyCondition::structureEnsuresValidityAssumingImpurePropertyWatchpoint const):
* bytecode/ObjectPropertyCondition.h:
(JSC::ObjectPropertyCondition::customFunctionEquivalence):
* bytecode/ObjectPropertyConditionSet.cpp:
(JSC::ObjectPropertyConditionSet::hasOneSlotBaseCondition const):
(JSC::ObjectPropertyConditionSet::slotBaseCondition const):
(JSC::generateConditionsForPrototypePropertyHitCustom):
* bytecode/ObjectPropertyConditionSet.h:
* bytecode/PolyProtoAccessChain.cpp:
(JSC::PolyProtoAccessChain::create):
* bytecode/PolymorphicAccess.cpp:
(JSC::AccessGenerationState::installWatchpoint):
(JSC::PolymorphicAccess::commit):
(JSC::AccessGenerationState::addWatchpoint): Deleted.
* bytecode/PolymorphicAccess.h:
* bytecode/PropertyCondition.cpp:
(JSC::PropertyCondition::dumpInContext const):
(JSC::PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint const):
(JSC::PropertyCondition::validityRequiresImpurePropertyWatchpoint const):
(JSC::PropertyCondition::isStillValid const):
(JSC::PropertyCondition::isWatchableWhenValid const):
(WTF::printInternal):
* bytecode/PropertyCondition.h:
(JSC::PropertyCondition::customFunctionEquivalence):
(JSC::PropertyCondition::hash const):
(JSC::PropertyCondition::operator== const):
* bytecode/StructureStubClearingWatchpoint.cpp:
(JSC::StructureTransitionStructureStubClearingWatchpoint::fireInternal):
(JSC::WatchpointsOnStructureStubInfo::addWatchpoint):
(JSC::WatchpointsOnStructureStubInfo::ensureReferenceAndInstallWatchpoint):
(JSC::WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint):
(JSC::AdaptiveValueStructureStubClearingWatchpoint::handleFire):
(JSC::StructureStubClearingWatchpoint::fireInternal): Deleted.
* bytecode/StructureStubClearingWatchpoint.h:
* bytecode/Watchpoint.h:
* jit/Repatch.cpp:
(JSC::tryCacheGetByID):
(JSC::tryCachePutByID):
* runtime/ClassInfo.h:
* runtime/JSObject.cpp:
(JSC::JSObject::findPropertyHashEntry const):
* runtime/JSObject.h:
* runtime/ObjectPropertyChangeAdaptiveWatchpoint.h:
* runtime/Structure.cpp:
(JSC::Structure::findPropertyHashEntry const):
* runtime/Structure.h:
* tools/JSDollarVM.cpp:
(JSC::testStaticAccessorGetter):
(JSC::testStaticAccessorPutter):
(JSC::StaticCustomAccessor::StaticCustomAccessor):
(JSC::StaticCustomAccessor::createStructure):
(JSC::StaticCustomAccessor::create):
(JSC::StaticCustomAccessor::getOwnPropertySlot):
(JSC::functionCreateStaticCustomAccessor):
(JSC::JSDollarVM::finishCreation):

LayoutTests:

* js/dom/custom-accessor-redefine-expected.txt: Added.
* js/dom/custom-accessor-redefine.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@250540 268f45cc-cd09-0410-ab3c-d52691b4dbfc

38 files changed:
JSTests/ChangeLog
JSTests/microbenchmarks/custom-accessor-materialized.js [new file with mode: 0644]
JSTests/microbenchmarks/custom-accessor-thin-air.js [new file with mode: 0644]
JSTests/microbenchmarks/custom-accessor.js [new file with mode: 0644]
JSTests/microbenchmarks/custom-value-2.js [new file with mode: 0644]
JSTests/microbenchmarks/custom-value.js [new file with mode: 0644]
JSTests/stress/custom-accessor-delete-1.js [new file with mode: 0644]
JSTests/stress/custom-accessor-delete-2.js [new file with mode: 0644]
JSTests/stress/custom-accessor-delete-3.js [new file with mode: 0644]
JSTests/stress/custom-value-delete-property-1.js [new file with mode: 0644]
JSTests/stress/custom-value-delete-property-2.js [new file with mode: 0644]
JSTests/stress/custom-value-delete-property-3.js [new file with mode: 0644]
LayoutTests/ChangeLog
LayoutTests/js/dom/custom-accessor-redefine-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/custom-accessor-redefine.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/AccessCase.cpp
Source/JavaScriptCore/bytecode/AdaptiveInferredPropertyValueWatchpointBase.h
Source/JavaScriptCore/bytecode/ObjectPropertyCondition.cpp
Source/JavaScriptCore/bytecode/ObjectPropertyCondition.h
Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp
Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h
Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp
Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp
Source/JavaScriptCore/bytecode/PolymorphicAccess.h
Source/JavaScriptCore/bytecode/PropertyCondition.cpp
Source/JavaScriptCore/bytecode/PropertyCondition.h
Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.cpp
Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.h
Source/JavaScriptCore/bytecode/Watchpoint.h
Source/JavaScriptCore/jit/Repatch.cpp
Source/JavaScriptCore/runtime/ClassInfo.h
Source/JavaScriptCore/runtime/JSObject.cpp
Source/JavaScriptCore/runtime/JSObject.h
Source/JavaScriptCore/runtime/ObjectPropertyChangeAdaptiveWatchpoint.h
Source/JavaScriptCore/runtime/Structure.cpp
Source/JavaScriptCore/runtime/Structure.h
Source/JavaScriptCore/tools/JSDollarVM.cpp

index c32a22e..db379a4 100644 (file)
@@ -1,3 +1,50 @@
+2019-09-30  Saam Barati  <sbarati@apple.com>
+
+        Inline caching is wrong for custom accessors and custom values
+        https://bugs.webkit.org/show_bug.cgi?id=201994
+        <rdar://problem/50850326>
+
+        Reviewed by Yusuke Suzuki.
+
+        * microbenchmarks/custom-accessor-materialized.js: Added.
+        (assert):
+        (test4.get const):
+        * microbenchmarks/custom-accessor-thin-air.js: Added.
+        (assert):
+        (test5.get const):
+        (test5.get proto):
+        * microbenchmarks/custom-accessor.js: Added.
+        (assert):
+        (test3.get const):
+        * microbenchmarks/custom-value-2.js: Added.
+        (assert):
+        (test1.getMultiline):
+        (test1):
+        * microbenchmarks/custom-value.js: Added.
+        (assert):
+        (test1.getMultiline):
+        (test1):
+        * stress/custom-accessor-delete-1.js: Added.
+        (assert):
+        (test3.get const):
+        * stress/custom-accessor-delete-2.js: Added.
+        (assert):
+        (test4.get const):
+        * stress/custom-accessor-delete-3.js: Added.
+        (assert):
+        (test5.get const):
+        (test5.get proto):
+        * stress/custom-value-delete-property-1.js: Added.
+        (assert):
+        (test1.getMultiline):
+        (test1):
+        * stress/custom-value-delete-property-2.js: Added.
+        (test2.foo):
+        (test2):
+        * stress/custom-value-delete-property-3.js: Added.
+        (test6.foo):
+        (test6):
+
 2019-09-30  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] AI folds CompareEq wrongly when it sees proven Boolean and Number
diff --git a/JSTests/microbenchmarks/custom-accessor-materialized.js b/JSTests/microbenchmarks/custom-accessor-materialized.js
new file mode 100644 (file)
index 0000000..50438a7
--- /dev/null
@@ -0,0 +1,22 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test4() {
+    function get(o) {
+        return o.testStaticAccessor;
+    }
+    noInline(get);
+
+    const proto = $vm.createStaticCustomAccessor();
+    const o = {__proto__: proto};
+    const d = Object.getOwnPropertyDescriptor(proto, "testStaticAccessor");
+    assert(!!d.get);
+    o.testField = 1337;
+
+    for (let i = 0; i < 500000; ++i) {
+        assert(get(o) === 1337);
+    }
+}
+test4();
diff --git a/JSTests/microbenchmarks/custom-accessor-thin-air.js b/JSTests/microbenchmarks/custom-accessor-thin-air.js
new file mode 100644 (file)
index 0000000..7984c4b
--- /dev/null
@@ -0,0 +1,24 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test5() {
+    function get(o) {
+        return o.thinAirCustomGetter;
+    }
+    noInline(get);
+
+    const proto = $vm.createStaticCustomAccessor();
+    const o = {__proto__: proto};
+    o.testField = 1337;
+
+    for (let i = 0; i < 500000; ++i) {
+        assert(get(o) === 1337);
+    }
+    proto.xyz = 42;
+    for (let i = 0; i < 500000; ++i) {
+        assert(get(o) === 1337);
+    }
+}
+test5();
diff --git a/JSTests/microbenchmarks/custom-accessor.js b/JSTests/microbenchmarks/custom-accessor.js
new file mode 100644 (file)
index 0000000..76c34c8
--- /dev/null
@@ -0,0 +1,20 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test3() {
+    function get(o) {
+        return o.testStaticAccessor;
+    }
+    noInline(get);
+
+    const proto = $vm.createStaticCustomAccessor();
+    const o = {__proto__: proto};
+    o.testField = 1337;
+
+    for (let i = 0; i < 500000; ++i) {
+        assert(get(o) === 1337);
+    }
+}
+test3();
diff --git a/JSTests/microbenchmarks/custom-value-2.js b/JSTests/microbenchmarks/custom-value-2.js
new file mode 100644 (file)
index 0000000..6ca2742
--- /dev/null
@@ -0,0 +1,26 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test1() {
+    function getMultiline(o) {
+        return o.multiline;
+    }
+    noInline(getMultiline);
+
+    RegExp.foo = 42;
+
+    const o = {};
+    o.__proto__ = RegExp;
+    RegExp.multiline = false;
+
+    for (let i = 0; i < 5000000; ++i) {
+        assert(getMultiline(o) === false);
+    }
+    delete RegExp.foo;
+    for (let i = 0; i < 5000000; ++i) {
+        assert(getMultiline(o) === false);
+    }
+}
+test1();
diff --git a/JSTests/microbenchmarks/custom-value.js b/JSTests/microbenchmarks/custom-value.js
new file mode 100644 (file)
index 0000000..852244a
--- /dev/null
@@ -0,0 +1,21 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test1() {
+    function getMultiline(o) {
+        return o.multiline;
+    }
+    noInline(getMultiline);
+
+    const o = {};
+    o.__proto__ = RegExp;
+    RegExp.multiline = false;
+
+    for (let i = 0; i < 5000000; ++i) {
+        assert(getMultiline(o) === false);
+    }
+}
+
+test1();
diff --git a/JSTests/stress/custom-accessor-delete-1.js b/JSTests/stress/custom-accessor-delete-1.js
new file mode 100644 (file)
index 0000000..0853914
--- /dev/null
@@ -0,0 +1,25 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test3() {
+    function get(o) {
+        return o.testStaticAccessor;
+    }
+    noInline(get);
+
+    const proto = $vm.createStaticCustomAccessor();
+    const o = {__proto__: proto};
+    o.testField = 1337;
+
+    for (let i = 0; i < 500; ++i) {
+        assert(get(o) === 1337);
+    }
+
+    proto.xyz = 42;
+
+    assert(delete proto.testStaticAccessor);
+    assert(get(o) === undefined);
+}
+test3();
diff --git a/JSTests/stress/custom-accessor-delete-2.js b/JSTests/stress/custom-accessor-delete-2.js
new file mode 100644 (file)
index 0000000..0dc0fbc
--- /dev/null
@@ -0,0 +1,27 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test4() {
+    function get(o) {
+        return o.testStaticAccessor;
+    }
+    noInline(get);
+
+    const proto = $vm.createStaticCustomAccessor();
+    const o = {__proto__: proto};
+    const d = Object.getOwnPropertyDescriptor(proto, "testStaticAccessor");
+    assert(!!d.get);
+    o.testField = 1337;
+
+    for (let i = 0; i < 500; ++i) {
+        assert(get(o) === 1337);
+    }
+
+    proto.xyz = 42;
+
+    assert(delete proto.testStaticAccessor);
+    assert(get(o) === undefined);
+}
+test4();
diff --git a/JSTests/stress/custom-accessor-delete-3.js b/JSTests/stress/custom-accessor-delete-3.js
new file mode 100644 (file)
index 0000000..417ed40
--- /dev/null
@@ -0,0 +1,24 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test5() {
+    function get(o) {
+        return o.thinAirCustomGetter;
+    }
+    noInline(get);
+
+    const proto = $vm.createStaticCustomAccessor();
+    const o = {__proto__: proto};
+    o.testField = 1337;
+
+    for (let i = 0; i < 500; ++i) {
+        assert(get(o) === 1337);
+    }
+    proto.xyz = 42;
+    for (let i = 0; i < 500; ++i) {
+        assert(get(o) === 1337);
+    }
+}
+test5();
diff --git a/JSTests/stress/custom-value-delete-property-1.js b/JSTests/stress/custom-value-delete-property-1.js
new file mode 100644 (file)
index 0000000..0cc804a
--- /dev/null
@@ -0,0 +1,23 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test1() {
+    function getMultiline(o) {
+        return o.multiline;
+    }
+    noInline(getMultiline);
+
+    const o = {};
+    o.__proto__ = RegExp;
+    RegExp.multiline = false;
+
+    for (let i = 0; i < 500; ++i) {
+        assert(getMultiline(o) === false);
+    }
+    delete RegExp.input;
+    delete RegExp.multiline;
+    assert(getMultiline(o) === undefined);
+}
+test1();
diff --git a/JSTests/stress/custom-value-delete-property-2.js b/JSTests/stress/custom-value-delete-property-2.js
new file mode 100644 (file)
index 0000000..0a5f9e2
--- /dev/null
@@ -0,0 +1,14 @@
+function test2() {
+    function foo() {
+        const o = {};
+        for (let i = 0; i < 8; i++) {
+            let x = o.multiline;
+            o.__proto__ = RegExp;
+        }
+        delete RegExp.input;
+    }
+
+    foo();
+    foo();
+}
+test2();
diff --git a/JSTests/stress/custom-value-delete-property-3.js b/JSTests/stress/custom-value-delete-property-3.js
new file mode 100644 (file)
index 0000000..c066a28
--- /dev/null
@@ -0,0 +1,14 @@
+function test6() {
+    function foo() {
+        const o = {};
+        for (let i = 0; i < 8; i++) {
+            o.lastMatch;
+            o.__proto__ = RegExp;
+        }
+        delete RegExp.input;
+    }
+
+    foo();
+    foo();
+}
+test6();
index a069305..8d676a1 100644 (file)
@@ -1,3 +1,14 @@
+2019-09-30  Saam Barati  <sbarati@apple.com>
+
+        Inline caching is wrong for custom accessors and custom values
+        https://bugs.webkit.org/show_bug.cgi?id=201994
+        <rdar://problem/50850326>
+
+        Reviewed by Yusuke Suzuki.
+
+        * js/dom/custom-accessor-redefine-expected.txt: Added.
+        * js/dom/custom-accessor-redefine.html: Added.
+
 2019-09-30  Chris Dumez  <cdumez@apple.com>
 
         IDBTransaction / IDBObjectStore should not prevent a page from entering the back / forward cache
diff --git a/LayoutTests/js/dom/custom-accessor-redefine-expected.txt b/LayoutTests/js/dom/custom-accessor-redefine-expected.txt
new file mode 100644 (file)
index 0000000..3e7a1e7
--- /dev/null
@@ -0,0 +1,10 @@
+Check that custom accessor being redefined invalidates our inline caches.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS setter called as expected
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/custom-accessor-redefine.html b/LayoutTests/js/dom/custom-accessor-redefine.html
new file mode 100644 (file)
index 0000000..73611fb
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description('Check that custom accessor being redefined invalidates our inline caches.');
+
+(function() {
+    "use strict";
+
+    let setterCalled = false;
+
+    function accessProperty() {
+        let oScript = document.createElement("script");
+        oScript.src = Math.random();
+    }
+
+    // Force "code optimization" by calling the function several times
+    for (let i = 0; i < 1000; i++) {
+        accessProperty();
+    }
+
+    // Define a custom setter for HTMLScriptElement#src
+    const descriptor = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, "src");
+    Object.defineProperty(HTMLScriptElement.prototype, "src", {
+        get: descriptor.get,
+        set: function() {
+
+            setterCalled = true;
+
+            descriptor.set.apply(this, arguments);
+        },
+        enumerable: descriptor.enumerable,
+        configurable: descriptor.configurable
+    });
+    accessProperty();
+
+    if (setterCalled)
+        testPassed("setter called as expected");
+    else
+        testFailed("Unexpected, setter not called.");
+})();
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 23a3e55..1cf2c33 100644 (file)
@@ -1,3 +1,98 @@
+2019-09-30  Saam Barati  <sbarati@apple.com>
+
+        Inline caching is wrong for custom accessors and custom values
+        https://bugs.webkit.org/show_bug.cgi?id=201994
+        <rdar://problem/50850326>
+
+        Reviewed by Yusuke Suzuki.
+
+        There was an oversight in our inline caching code for custom accessors and
+        custom values. We used to assume that if an object O had a custom function for
+        property P, then O will forever respond to the same custom function for
+        property P.
+        
+        This assumption was very wrong. These custom accessors/values might be
+        properties in JS which are configurable, so they can be rewritten to be
+        other properties. Our inline caching code would be wrong in the scenarios
+        where these property descriptors got redefined.
+        
+        This patch makes it so that we now properly watchpoint for custom functions
+        being changed. If the custom accessor has been materialized, we place an
+        Equivalence watchpoint on the custom accessor. This patch also teaches
+        StructureStubInfo how to watchpoint on property value equivalence. Before,
+        we just watchpointed on structure transitions.
+        
+        This patch also adds a new property condition kind for when the custom function
+        exists inside the static property table. This case is really easy to test for
+        because we just need to see if the structure still has static properties and
+        the static property table has the entry for a particular property. This
+        property condition kind just needs to watch for structure transitions because
+        an entry in the static property table can't be mutated.
+        
+        This patch is neutral on the microbenchmarks I've added.
+
+        * bytecode/AccessCase.cpp:
+        (JSC::AccessCase::AccessCase):
+        (JSC::AccessCase::couldStillSucceed const):
+        (JSC::AccessCase::generateImpl):
+        * bytecode/AdaptiveInferredPropertyValueWatchpointBase.h:
+        * bytecode/ObjectPropertyCondition.cpp:
+        (JSC::ObjectPropertyCondition::structureEnsuresValidityAssumingImpurePropertyWatchpoint const):
+        * bytecode/ObjectPropertyCondition.h:
+        (JSC::ObjectPropertyCondition::customFunctionEquivalence):
+        * bytecode/ObjectPropertyConditionSet.cpp:
+        (JSC::ObjectPropertyConditionSet::hasOneSlotBaseCondition const):
+        (JSC::ObjectPropertyConditionSet::slotBaseCondition const):
+        (JSC::generateConditionsForPrototypePropertyHitCustom):
+        * bytecode/ObjectPropertyConditionSet.h:
+        * bytecode/PolyProtoAccessChain.cpp:
+        (JSC::PolyProtoAccessChain::create):
+        * bytecode/PolymorphicAccess.cpp:
+        (JSC::AccessGenerationState::installWatchpoint):
+        (JSC::PolymorphicAccess::commit):
+        (JSC::AccessGenerationState::addWatchpoint): Deleted.
+        * bytecode/PolymorphicAccess.h:
+        * bytecode/PropertyCondition.cpp:
+        (JSC::PropertyCondition::dumpInContext const):
+        (JSC::PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint const):
+        (JSC::PropertyCondition::validityRequiresImpurePropertyWatchpoint const):
+        (JSC::PropertyCondition::isStillValid const):
+        (JSC::PropertyCondition::isWatchableWhenValid const):
+        (WTF::printInternal):
+        * bytecode/PropertyCondition.h:
+        (JSC::PropertyCondition::customFunctionEquivalence):
+        (JSC::PropertyCondition::hash const):
+        (JSC::PropertyCondition::operator== const):
+        * bytecode/StructureStubClearingWatchpoint.cpp:
+        (JSC::StructureTransitionStructureStubClearingWatchpoint::fireInternal):
+        (JSC::WatchpointsOnStructureStubInfo::addWatchpoint):
+        (JSC::WatchpointsOnStructureStubInfo::ensureReferenceAndInstallWatchpoint):
+        (JSC::WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint):
+        (JSC::AdaptiveValueStructureStubClearingWatchpoint::handleFire):
+        (JSC::StructureStubClearingWatchpoint::fireInternal): Deleted.
+        * bytecode/StructureStubClearingWatchpoint.h:
+        * bytecode/Watchpoint.h:
+        * jit/Repatch.cpp:
+        (JSC::tryCacheGetByID):
+        (JSC::tryCachePutByID):
+        * runtime/ClassInfo.h:
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::findPropertyHashEntry const):
+        * runtime/JSObject.h:
+        * runtime/ObjectPropertyChangeAdaptiveWatchpoint.h:
+        * runtime/Structure.cpp:
+        (JSC::Structure::findPropertyHashEntry const):
+        * runtime/Structure.h:
+        * tools/JSDollarVM.cpp:
+        (JSC::testStaticAccessorGetter):
+        (JSC::testStaticAccessorPutter):
+        (JSC::StaticCustomAccessor::StaticCustomAccessor):
+        (JSC::StaticCustomAccessor::createStructure):
+        (JSC::StaticCustomAccessor::create):
+        (JSC::StaticCustomAccessor::getOwnPropertySlot):
+        (JSC::functionCreateStaticCustomAccessor):
+        (JSC::JSDollarVM::finishCreation):
+
 2019-09-30  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] AI folds CompareEq wrongly when it sees proven Boolean and Number
index ffccf59..f2e4306 100644 (file)
@@ -61,6 +61,7 @@ AccessCase::AccessCase(VM& vm, JSCell* owner, AccessType type, PropertyOffset of
 {
     m_structure.setMayBeNull(vm, owner, structure);
     m_conditionSet = conditionSet;
+    RELEASE_ASSERT(m_conditionSet.isValid());
 }
 
 std::unique_ptr<AccessCase> AccessCase::create(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
@@ -310,7 +311,16 @@ bool AccessCase::doesCalls(Vector<JSCell*>* cellsToMarkIfDoesCalls) const
 
 bool AccessCase::couldStillSucceed() const
 {
-    return m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint();
+    for (const ObjectPropertyCondition& condition : m_conditionSet) {
+        if (condition.condition().kind() == PropertyCondition::Equivalence) {
+            if (!condition.isWatchableAssumingImpurePropertyWatchpoint(PropertyCondition::WatchabilityEffort::EnsureWatchability))
+                return false;
+        } else {
+            if (!condition.structureEnsuresValidityAssumingImpurePropertyWatchpoint())
+                return false;
+        }
+    }
+    return true;
 }
 
 bool AccessCase::canReplace(const AccessCase& other) const
@@ -709,19 +719,18 @@ void AccessCase::generateImpl(AccessGenerationState& state)
     GPRReg thisGPR = state.thisGPR != InvalidGPRReg ? state.thisGPR : baseGPR;
     GPRReg scratchGPR = state.scratchGPR;
 
-    ASSERT(m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint());
-
     for (const ObjectPropertyCondition& condition : m_conditionSet) {
         RELEASE_ASSERT(!m_polyProtoAccessChain);
 
-        Structure* structure = condition.object()->structure(vm);
-
-        if (condition.isWatchableAssumingImpurePropertyWatchpoint()) {
-            structure->addTransitionWatchpoint(state.addWatchpoint(condition));
+        if (condition.isWatchableAssumingImpurePropertyWatchpoint(PropertyCondition::WatchabilityEffort::EnsureWatchability)) {
+            state.installWatchpoint(condition);
             continue;
         }
 
-        if (!condition.structureEnsuresValidityAssumingImpurePropertyWatchpoint(structure)) {
+        // For now, we only allow equivalence when it's watchable.
+        RELEASE_ASSERT(condition.condition().kind() != PropertyCondition::Equivalence);
+
+        if (!condition.structureEnsuresValidityAssumingImpurePropertyWatchpoint()) {
             // The reason why this cannot happen is that we require that PolymorphicAccess calls
             // AccessCase::generate() only after it has verified that
             // AccessCase::couldStillSucceed() returned true.
@@ -731,6 +740,7 @@ void AccessCase::generateImpl(AccessGenerationState& state)
         }
 
         // We will emit code that has a weak reference that isn't otherwise listed anywhere.
+        Structure* structure = condition.object()->structure(vm);
         state.weakReferences.append(WriteBarrier<JSCell>(vm, codeBlock, structure));
 
         jit.move(CCallHelpers::TrustedImmPtr(condition.object()), scratchGPR);
index ace8a09..fa4798b 100644 (file)
@@ -32,6 +32,8 @@
 
 namespace JSC {
 
+// FIXME: This isn't actually a Watchpoint. We should probably have a name which better reflects that:
+// https://bugs.webkit.org/show_bug.cgi?id=202381
 class AdaptiveInferredPropertyValueWatchpointBase {
     WTF_MAKE_NONCOPYABLE(AdaptiveInferredPropertyValueWatchpointBase);
     WTF_MAKE_FAST_ALLOCATED;
index 950045e..e3d2dee 100644 (file)
@@ -46,18 +46,12 @@ void ObjectPropertyCondition::dump(PrintStream& out) const
     dumpInContext(out, nullptr);
 }
 
-bool ObjectPropertyCondition::structureEnsuresValidityAssumingImpurePropertyWatchpoint(
-    Structure* structure) const
-{
-    return m_condition.isStillValidAssumingImpurePropertyWatchpoint(structure);
-}
-
 bool ObjectPropertyCondition::structureEnsuresValidityAssumingImpurePropertyWatchpoint() const
 {
     if (!*this)
         return false;
     
-    return structureEnsuresValidityAssumingImpurePropertyWatchpoint(m_object->structure());
+    return m_condition.isStillValidAssumingImpurePropertyWatchpoint(m_object->structure(), nullptr);
 }
 
 bool ObjectPropertyCondition::validityRequiresImpurePropertyWatchpoint(Structure* structure) const
index 39797f3..d81415e 100644 (file)
@@ -121,6 +121,17 @@ public:
             vm.heap.writeBarrier(owner);
         return equivalenceWithoutBarrier(object, uid, value);
     }
+
+    static ObjectPropertyCondition customFunctionEquivalence(
+        VM& vm, JSCell* owner, JSObject* object, UniquedStringImpl* uid)
+    {
+        ObjectPropertyCondition result;
+        result.m_object = object;
+        result.m_condition = PropertyCondition::customFunctionEquivalence(uid);
+        if (owner)
+            vm.heap.writeBarrier(owner);
+        return result;
+    }
     
     static ObjectPropertyCondition hasPrototypeWithoutBarrier(JSObject* object, JSObject* prototype)
     {
@@ -193,7 +204,6 @@ public:
     
     // Checks if the object's structure claims that the property won't be intercepted. Validity
     // does not require watchpoints on the object.
-    bool structureEnsuresValidityAssumingImpurePropertyWatchpoint(Structure*) const;
     bool structureEnsuresValidityAssumingImpurePropertyWatchpoint() const;
     
     // Returns true if we need an impure property watchpoint to ensure validity even if
@@ -227,7 +237,7 @@ public:
         PropertyCondition::WatchabilityEffort = PropertyCondition::MakeNoChanges) const;
 
     // This means that it's still valid and we could enforce validity by setting a transition
-    // watchpoint on the structure.
+    // watchpoint on the structure, and a value change watchpoint if we're Equivalence.
     bool isWatchable(
         Structure*,
         PropertyCondition::WatchabilityEffort = PropertyCondition::MakeNoChanges) const;
index 2cd691e..a3fa474 100644 (file)
@@ -62,7 +62,22 @@ unsigned ObjectPropertyConditionSet::numberOfConditionsWithKind(PropertyConditio
 
 bool ObjectPropertyConditionSet::hasOneSlotBaseCondition() const
 {
-    return (numberOfConditionsWithKind(PropertyCondition::Presence) == 1) != (numberOfConditionsWithKind(PropertyCondition::Equivalence) == 1);
+    bool sawBase = false;
+    for (const ObjectPropertyCondition& condition : *this) {
+        switch (condition.kind()) {
+        case PropertyCondition::Presence:
+        case PropertyCondition::Equivalence:
+        case PropertyCondition::CustomFunctionEquivalence:
+            if (sawBase)
+                return false;
+            sawBase = true;
+            break;
+        default:
+            break;
+        }
+    }
+
+    return sawBase;
 }
 
 ObjectPropertyCondition ObjectPropertyConditionSet::slotBaseCondition() const
@@ -71,7 +86,8 @@ ObjectPropertyCondition ObjectPropertyConditionSet::slotBaseCondition() const
     unsigned numFound = 0;
     for (const ObjectPropertyCondition& condition : *this) {
         if (condition.kind() == PropertyCondition::Presence
-            || condition.kind() == PropertyCondition::Equivalence) {
+            || condition.kind() == PropertyCondition::Equivalence
+            || condition.kind() == PropertyCondition::CustomFunctionEquivalence) {
             result = condition;
             numFound++;
         }
@@ -228,6 +244,13 @@ ObjectPropertyCondition generateCondition(
         result = ObjectPropertyCondition::equivalence(vm, owner, object, uid, value);
         break;
     }
+    case PropertyCondition::CustomFunctionEquivalence: {
+        auto entry = object->findPropertyHashEntry(vm, uid);
+        if (!entry)
+            return ObjectPropertyCondition();
+        result = ObjectPropertyCondition::customFunctionEquivalence(vm, owner, object, uid);
+        break;
+    }
     default:
         RELEASE_ASSERT_NOT_REACHED();
         return ObjectPropertyCondition();
@@ -378,15 +401,39 @@ ObjectPropertyConditionSet generateConditionsForPrototypePropertyHit(
 
 ObjectPropertyConditionSet generateConditionsForPrototypePropertyHitCustom(
     VM& vm, JSCell* owner, ExecState* exec, Structure* headStructure, JSObject* prototype,
-    UniquedStringImpl* uid)
+    UniquedStringImpl* uid, unsigned attributes)
 {
     return generateConditions(
         vm, exec->lexicalGlobalObject(), headStructure, prototype,
         [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
-            if (object == prototype)
-                return true;
-            ObjectPropertyCondition result =
-                generateCondition(vm, owner, object, uid, PropertyCondition::Absence);
+            auto kind = PropertyCondition::Absence;
+            if (object == prototype) {
+                Structure* structure = object->structure(vm);
+                PropertyOffset offset = structure->get(vm, uid);
+                if (isValidOffset(offset)) {
+                    // When we reify custom accessors, we wrap them in a JSFunction that we shove
+                    // inside a GetterSetter. So, once we've reified a custom accessor, we will
+                    // no longer see it as a "custom" accessor/value. Hence, if our property access actually
+                    // notices a custom, it must be a CustomGetterSetterType cell or something
+                    // in the static property table. Custom values get reified into CustomGetterSetters.
+                    JSValue value = object->getDirect(offset);
+                    ASSERT_UNUSED(value, value.isCell() && value.asCell()->type() == CustomGetterSetterType);
+                    kind = PropertyCondition::Equivalence;
+                } else if (structure->findPropertyHashEntry(uid))
+                    kind = PropertyCondition::CustomFunctionEquivalence;
+                else if (attributes & PropertyAttribute::DontDelete) {
+                    // This can't change, so we can blindly cache it.
+                    return true;
+                } else {
+                    // This means we materialized a custom out of thin air and it's not DontDelete (i.e, it can be
+                    // redefined). This is curious. We don't actually need to crash here. We could blindly cache
+                    // the function. Or we could blindly not cache it. However, we don't actually do this in WebKit
+                    // right now, so it's reasonable to decide what to do later (or to warn people of forgetting DoneDelete.)
+                    ASSERT_NOT_REACHED();
+                    return false;
+                }
+            }
+            ObjectPropertyCondition result = generateCondition(vm, owner, object, uid, kind);
             if (!result)
                 return false;
             conditions.append(result);
index ca72d70..10cf844 100644 (file)
@@ -176,7 +176,7 @@ ObjectPropertyConditionSet generateConditionsForPrototypePropertyHit(
     UniquedStringImpl* uid);
 ObjectPropertyConditionSet generateConditionsForPrototypePropertyHitCustom(
     VM&, JSCell* owner, ExecState*, Structure* headStructure, JSObject* prototype,
-    UniquedStringImpl* uid);
+    UniquedStringImpl* uid, unsigned attributes);
 
 ObjectPropertyConditionSet generateConditionsForInstanceOf(
     VM&, JSCell* owner, ExecState*, Structure* headStructure, JSObject* prototype, bool shouldHit);
index ff43b6d..3cf776e 100644 (file)
@@ -51,12 +51,6 @@ std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObjec
     for (unsigned iterationNumber = 0; true; ++iterationNumber) {
         Structure* structure = current->structure(vm);
 
-        if (!structure->propertyAccessesAreCacheable())
-            return nullptr;
-
-        if (structure->isProxy())
-            return nullptr;
-
         if (structure->isDictionary()) {
             ASSERT(structure->isObject());
             if (structure->hasBeenFlattenedBefore())
@@ -65,6 +59,12 @@ std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObjec
             structure->flattenDictionaryStructure(vm, asObject(current));
         }
 
+        if (!structure->propertyAccessesAreCacheable())
+            return nullptr;
+
+        if (structure->isProxy())
+            return nullptr;
+
         // To save memory, we don't include the base in the chain. We let
         // AccessCase provide the base to us as needed.
         if (iterationNumber)
index 6647eec..8c4b9b8 100644 (file)
@@ -55,9 +55,9 @@ void AccessGenerationResult::dump(PrintStream& out) const
         out.print(":", m_code);
 }
 
-Watchpoint* AccessGenerationState::addWatchpoint(const ObjectPropertyCondition& condition)
+void AccessGenerationState::installWatchpoint(const ObjectPropertyCondition& condition)
 {
-    return WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint(
+    WatchpointsOnStructureStubInfo::ensureReferenceAndInstallWatchpoint(
         watchpoints, jit->codeBlock(), stubInfo, condition);
 }
 
@@ -373,7 +373,7 @@ void PolymorphicAccess::commit(
     for (WatchpointSet* set : accessCase.commit(vm, ident)) {
         Watchpoint* watchpoint =
             WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint(
-                watchpoints, codeBlock, &stubInfo, ObjectPropertyCondition());
+                watchpoints, codeBlock, &stubInfo);
         
         set->add(watchpoint);
     }
index 97a4e13..1606278 100644 (file)
@@ -212,7 +212,7 @@ struct AccessGenerationState {
     std::unique_ptr<WatchpointsOnStructureStubInfo> watchpoints;
     Vector<WriteBarrier<JSCell>> weakReferences;
 
-    Watchpoint* addWatchpoint(const ObjectPropertyCondition& = ObjectPropertyCondition());
+    void installWatchpoint(const ObjectPropertyCondition&);
 
     void restoreScratch();
     void succeed();
index 3da89aa..3e78333 100644 (file)
@@ -54,6 +54,9 @@ void PropertyCondition::dumpInContext(PrintStream& out, DumpContext* context) co
     case Equivalence:
         out.print(m_header.type(), " of ", m_header.pointer(), " with ", inContext(requiredValue(), context));
         return;
+    case CustomFunctionEquivalence:
+        out.print(m_header.type(), " of ", m_header.pointer());
+        return;
     case HasPrototype:
         out.print(m_header.type(), " with prototype ", inContext(JSValue(prototype()), context));
         return;
@@ -86,6 +89,7 @@ bool PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint(
     case Absence:
     case AbsenceOfSetEffect:
     case Equivalence:
+    case CustomFunctionEquivalence:
         if (!structure->propertyAccessesAreCacheable()) {
             if (PropertyConditionInternal::verbose)
                 dataLog("Invalid because property accesses are not cacheable.\n");
@@ -248,7 +252,13 @@ bool PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint(
         }
         
         return true;
-    } }
+    } 
+    case CustomFunctionEquivalence: {
+        if (structure->staticPropertiesReified())
+            return false;
+        return !!structure->findPropertyHashEntry(uid());
+    }
+    }
     
     RELEASE_ASSERT_NOT_REACHED();
     return false;
@@ -263,6 +273,7 @@ bool PropertyCondition::validityRequiresImpurePropertyWatchpoint(Structure* stru
     case Presence:
     case Absence:
     case Equivalence:
+    case CustomFunctionEquivalence:
         return structure->needImpurePropertyWatchpoint();
     case AbsenceOfSetEffect:
     case HasPrototype:
@@ -288,6 +299,7 @@ bool PropertyCondition::isStillValid(Structure* structure, JSObject* base) const
         break;
     case Presence:
     case Equivalence:
+    case CustomFunctionEquivalence:
         if (structure->typeInfo().getOwnPropertySlotIsImpure())
             return false;
         break;
@@ -329,6 +341,21 @@ bool PropertyCondition::isWatchableWhenValid(
         
         break;
     }
+
+    case CustomFunctionEquivalence: {
+        // We just use the structure transition watchpoint for this. A structure S starts
+        // off with a property P in the static property hash table. If S transitions to
+        // S', either P remains in the static property table or not. If not, then we
+        // are no longer valid. So the above check of transitionWatchpointSetHasBeenInvalidated
+        // is sufficient.
+        //
+        // We could make this smarter in the future, since we sometimes reify static properties.
+        // We could make this adapt to looking at the object's storage for such reified custom
+        // functions, but we don't do that right now. We just allow this property condition to
+        // invalidate and create an Equivalence watchpoint for the materialized property sometime
+        // in the future.
+        break;
+    }
         
     default:
         break;
@@ -403,6 +430,9 @@ void printInternal(PrintStream& out, JSC::PropertyCondition::Kind condition)
     case JSC::PropertyCondition::Equivalence:
         out.print("Equivalence");
         return;
+    case JSC::PropertyCondition::CustomFunctionEquivalence:
+        out.print("CustomFunctionEquivalence");
+        return;
     case JSC::PropertyCondition::HasPrototype:
         out.print("HasPrototype");
         return;
index 333572f..d320a4d 100644 (file)
@@ -40,6 +40,7 @@ public:
         Absence,
         AbsenceOfSetEffect,
         Equivalence, // An adaptive watchpoint on this will be a pair of watchpoints, and when the structure transitions, we will set the replacement watchpoint on the new structure.
+        CustomFunctionEquivalence, // Custom value or accessor.
         HasPrototype
     };
 
@@ -122,6 +123,13 @@ public:
             vm.heap.writeBarrier(owner);
         return equivalenceWithoutBarrier(uid, value);
     }
+
+    static PropertyCondition customFunctionEquivalence(UniquedStringImpl* uid)
+    {
+        PropertyCondition result;
+        result.m_header = Header(uid, CustomFunctionEquivalence);
+        return result;
+    }
     
     static PropertyCondition hasPrototypeWithoutBarrier(JSObject* prototype)
     {
@@ -193,6 +201,8 @@ public:
         case Equivalence:
             result ^= EncodedJSValueHash::hash(u.equivalence.value);
             break;
+        case CustomFunctionEquivalence:
+            break;
         }
         return result;
     }
@@ -213,6 +223,8 @@ public:
             return u.prototype.prototype == other.u.prototype.prototype;
         case Equivalence:
             return u.equivalence.value == other.u.equivalence.value;
+        case CustomFunctionEquivalence:
+            return true;
         }
         RELEASE_ASSERT_NOT_REACHED();
         return false;
@@ -279,12 +291,12 @@ public:
     // This means that it's still valid and we could enforce validity by setting a transition
     // watchpoint on the structure and possibly an impure property watchpoint.
     bool isWatchableAssumingImpurePropertyWatchpoint(
-        Structure*, JSObject* base = nullptr, WatchabilityEffort = MakeNoChanges) const;
+        Structure*, JSObject* base, WatchabilityEffort = MakeNoChanges) const;
     
     // This means that it's still valid and we could enforce validity by setting a transition
     // watchpoint on the structure.
     bool isWatchable(
-        Structure*, JSObject* base = nullptr, WatchabilityEffort = MakeNoChanges) const;
+        Structure*, JSObject*, WatchabilityEffort = MakeNoChanges) const;
     
     bool watchingRequiresStructureTransitionWatchpoint() const
     {
index fcc2911..8d630d9 100644 (file)
@@ -34,7 +34,7 @@
 
 namespace JSC {
 
-void StructureStubClearingWatchpoint::fireInternal(VM& vm, const FireDetail&)
+void StructureTransitionStructureStubClearingWatchpoint::fireInternal(VM& vm, const FireDetail&)
 {
     if (!m_holder->isValid())
         return;
@@ -62,12 +62,15 @@ inline bool WatchpointsOnStructureStubInfo::isValid() const
     return m_codeBlock->isLive();
 }
 
-StructureStubClearingWatchpoint* WatchpointsOnStructureStubInfo::addWatchpoint(const ObjectPropertyCondition& key)
+WatchpointsOnStructureStubInfo::Node& WatchpointsOnStructureStubInfo::addWatchpoint(const ObjectPropertyCondition& key)
 {
-    return m_watchpoints.add(key, *this);
+    if (!key || key.condition().kind() != PropertyCondition::Equivalence)
+        return *m_watchpoints.add(WTF::in_place<StructureTransitionStructureStubClearingWatchpoint>, key, *this);
+    ASSERT(key.condition().kind() == PropertyCondition::Equivalence);
+    return *m_watchpoints.add(WTF::in_place<AdaptiveValueStructureStubClearingWatchpoint>, key, *this);
 }
 
-StructureStubClearingWatchpoint* WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint(
+void WatchpointsOnStructureStubInfo::ensureReferenceAndInstallWatchpoint(
     std::unique_ptr<WatchpointsOnStructureStubInfo>& holderRef, CodeBlock* codeBlock,
     StructureStubInfo* stubInfo, const ObjectPropertyCondition& key)
 {
@@ -78,7 +81,41 @@ StructureStubClearingWatchpoint* WatchpointsOnStructureStubInfo::ensureReference
         ASSERT(holderRef->m_stubInfo == stubInfo);
     }
     
-    return holderRef->addWatchpoint(key);
+    ASSERT(!!key);
+    auto& watchpointVariant = holderRef->addWatchpoint(key);
+    if (key.kind() == PropertyCondition::Equivalence) {
+        auto& adaptiveWatchpoint = WTF::get<AdaptiveValueStructureStubClearingWatchpoint>(watchpointVariant);
+        adaptiveWatchpoint.install(codeBlock->vm());
+    } else {
+        auto* structureTransitionWatchpoint = &WTF::get<StructureTransitionStructureStubClearingWatchpoint>(watchpointVariant);
+        key.object()->structure()->addTransitionWatchpoint(structureTransitionWatchpoint);
+    }
+}
+
+Watchpoint* WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint(
+    std::unique_ptr<WatchpointsOnStructureStubInfo>& holderRef, CodeBlock* codeBlock,
+    StructureStubInfo* stubInfo)
+{
+    if (!holderRef)
+        holderRef = makeUnique<WatchpointsOnStructureStubInfo>(codeBlock, stubInfo);
+    else {
+        ASSERT(holderRef->m_codeBlock == codeBlock);
+        ASSERT(holderRef->m_stubInfo == stubInfo);
+    }
+    
+    return &WTF::get<StructureTransitionStructureStubClearingWatchpoint>(holderRef->addWatchpoint(ObjectPropertyCondition()));
+}
+
+void AdaptiveValueStructureStubClearingWatchpoint::handleFire(VM&, const FireDetail&)
+{
+    if (!m_holder->isValid())
+        return;
+
+    // This will implicitly cause my own demise: stub reset removes all watchpoints.
+    // That works, because deleting a watchpoint removes it from the set's list, and
+    // the set's list traversal for firing is robust against the set changing.
+    ConcurrentJSLocker locker(m_holder->codeBlock()->m_lock);
+    m_holder->stubInfo()->reset(m_holder->codeBlock());
 }
 
 } // namespace JSC
index 31f6aa4..aeb3456 100644 (file)
 
 #pragma once
 
-#include "ObjectPropertyCondition.h"
-#include "Watchpoint.h"
-
 #if ENABLE(JIT)
 
+#include "AdaptiveInferredPropertyValueWatchpointBase.h"
+#include "ObjectPropertyCondition.h"
+#include "Watchpoint.h"
 #include <wtf/Bag.h>
 #include <wtf/FastMalloc.h>
 #include <wtf/Noncopyable.h>
@@ -40,12 +40,12 @@ class CodeBlock;
 class StructureStubInfo;
 class WatchpointsOnStructureStubInfo;
 
-class StructureStubClearingWatchpoint final : public Watchpoint {
-    WTF_MAKE_NONCOPYABLE(StructureStubClearingWatchpoint);
+class StructureTransitionStructureStubClearingWatchpoint final : public Watchpoint {
+    WTF_MAKE_NONCOPYABLE(StructureTransitionStructureStubClearingWatchpoint);
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    StructureStubClearingWatchpoint(const ObjectPropertyCondition& key, WatchpointsOnStructureStubInfo& holder)
-        : Watchpoint(Watchpoint::Type::StructureStubClearing)
+    StructureTransitionStructureStubClearingWatchpoint(const ObjectPropertyCondition& key, WatchpointsOnStructureStubInfo& holder)
+        : Watchpoint(Watchpoint::Type::StructureTransitionStructureStubClearing)
         , m_holder(&holder)
         , m_key(key)
     {
@@ -59,6 +59,26 @@ private:
     JSC_WATCHPOINT_FIELD(ObjectPropertyCondition, m_key);
 };
 
+class AdaptiveValueStructureStubClearingWatchpoint final : public AdaptiveInferredPropertyValueWatchpointBase {
+    using Base = AdaptiveInferredPropertyValueWatchpointBase;
+    WTF_MAKE_NONCOPYABLE(AdaptiveValueStructureStubClearingWatchpoint);
+    WTF_MAKE_FAST_ALLOCATED;
+
+    void handleFire(VM&, const FireDetail&) override;
+
+public:
+    AdaptiveValueStructureStubClearingWatchpoint(const ObjectPropertyCondition& key, WatchpointsOnStructureStubInfo& holder)
+        : Base(key)
+        , m_holder(&holder)
+    {
+        RELEASE_ASSERT(key.condition().kind() == PropertyCondition::Equivalence);
+    }
+
+
+private:
+    PackedPtr<WatchpointsOnStructureStubInfo> m_holder;
+};
+
 class WatchpointsOnStructureStubInfo {
     WTF_MAKE_NONCOPYABLE(WatchpointsOnStructureStubInfo);
     WTF_MAKE_FAST_ALLOCATED;
@@ -69,11 +89,16 @@ public:
     {
     }
     
-    StructureStubClearingWatchpoint* addWatchpoint(const ObjectPropertyCondition& key);
+    using Node = Variant<StructureTransitionStructureStubClearingWatchpoint, AdaptiveValueStructureStubClearingWatchpoint>;
+
+    Node& addWatchpoint(const ObjectPropertyCondition& key);
     
-    static StructureStubClearingWatchpoint* ensureReferenceAndAddWatchpoint(
+    static void ensureReferenceAndInstallWatchpoint(
         std::unique_ptr<WatchpointsOnStructureStubInfo>& holderRef,
         CodeBlock*, StructureStubInfo*, const ObjectPropertyCondition& key);
+    static Watchpoint* ensureReferenceAndAddWatchpoint(
+        std::unique_ptr<WatchpointsOnStructureStubInfo>& holderRef,
+        CodeBlock*, StructureStubInfo*);
     
     CodeBlock* codeBlock() const { return m_codeBlock; }
     StructureStubInfo* stubInfo() const { return m_stubInfo; }
@@ -83,7 +108,9 @@ public:
 private:
     CodeBlock* m_codeBlock;
     StructureStubInfo* m_stubInfo;
-    Bag<StructureStubClearingWatchpoint> m_watchpoints;
+    // FIXME: use less memory for the entries in this Bag:
+    // https://bugs.webkit.org/show_bug.cgi?id=202380
+    Bag<WTF::Variant<StructureTransitionStructureStubClearingWatchpoint, AdaptiveValueStructureStubClearingWatchpoint>> m_watchpoints;
 };
 
 } // namespace JSC
index a4cd604..740d1b5 100644 (file)
@@ -115,7 +115,7 @@ class WatchpointSet;
 #if ENABLE(JIT)
 #define JSC_WATCHPOINT_TYPES_WITHOUT_DFG(macro) \
     JSC_WATCHPOINT_TYPES_WITHOUT_JIT(macro) \
-    macro(StructureStubClearing, StructureStubClearingWatchpoint)
+    macro(StructureTransitionStructureStubClearing, StructureTransitionStructureStubClearingWatchpoint)
 
 #if ENABLE(DFG_JIT)
 #define JSC_WATCHPOINT_TYPES(macro) \
index 2c4c565..f7173c7 100644 (file)
@@ -321,7 +321,7 @@ static InlineCacheAction tryCacheGetByID(ExecState* exec, JSValue baseValue, con
                         } else {
                             conditionSet = generateConditionsForPrototypePropertyHitCustom(
                                 vm, codeBlock, exec, structure, slot.slotBase(),
-                                propertyName.impl());
+                                propertyName.impl(), slot.attributes());
                         }
 
                         if (!conditionSet.isValid())
@@ -549,7 +549,7 @@ static InlineCacheAction tryCachePutByID(ExecState* exec, JSValue baseValue, Str
                         prototypeAccessChain = nullptr;
                         conditionSet =
                             generateConditionsForPrototypePropertyHitCustom(
-                                vm, codeBlock, exec, structure, slot.base(), ident.impl());
+                                vm, codeBlock, exec, structure, slot.base(), ident.impl(), static_cast<unsigned>(PropertyAttribute::None));
                         if (!conditionSet.isValid())
                             return GiveUpOnCache;
                     }
index 3957bc9..2dea963 100644 (file)
@@ -189,12 +189,18 @@ struct MethodTable {
     sizeof(ClassName)
 
 struct ClassInfo {
+    using CheckSubClassSnippetFunctionPtr = Ref<Snippet> (*)(void);
+
     // A string denoting the class name. Example: "Window".
     const char* className;
-
     // Pointer to the class information of the base class.
     // nullptrif there is none.
     const ClassInfo* parentClass;
+    const HashTable* staticPropHashTable;
+    CheckSubClassSnippetFunctionPtr checkSubClassSnippet;
+    MethodTable methodTable;
+    TypedArrayType typedArrayStorageType;
+    unsigned staticClassSize;
 
     static ptrdiff_t offsetOfParentClass()
     {
@@ -213,16 +219,6 @@ struct ClassInfo {
     JS_EXPORT_PRIVATE void dump(PrintStream&) const;
 
     JS_EXPORT_PRIVATE bool hasStaticSetterOrReadonlyProperties() const;
-
-    const HashTable* staticPropHashTable;
-
-    using CheckSubClassSnippetFunctionPtr = Ref<Snippet> (*)(void);
-    CheckSubClassSnippetFunctionPtr checkSubClassSnippet;
-
-    MethodTable methodTable;
-
-    TypedArrayType typedArrayStorageType;
-    unsigned staticClassSize;
 };
 
 } // namespace JSC
index 259fe72..c4670f8 100644 (file)
@@ -2222,15 +2222,9 @@ bool JSObject::getOwnStaticPropertySlot(VM& vm, PropertyName propertyName, Prope
     return false;
 }
 
-auto JSObject::findPropertyHashEntry(VM& vm, PropertyName propertyName) const -> Optional<PropertyHashEntry>
+Optional<Structure::PropertyHashEntry> JSObject::findPropertyHashEntry(VM& vm, PropertyName propertyName) const
 {
-    for (const ClassInfo* info = classInfo(vm); info; info = info->parentClass) {
-        if (const HashTable* propHashTable = info->staticPropHashTable) {
-            if (const HashTableValue* entry = propHashTable->entry(propertyName))
-                return PropertyHashEntry { propHashTable, entry };
-        }
-    }
-    return WTF::nullopt;
+    return structure(vm)->findPropertyHashEntry(propertyName);
 }
 
 bool JSObject::hasInstance(ExecState* exec, JSValue value, JSValue hasInstanceValue)
index 691e830..d84f3f6 100644 (file)
@@ -903,6 +903,8 @@ public:
     bool mayBePrototype() const;
     void didBecomePrototype();
 
+    Optional<Structure::PropertyHashEntry> findPropertyHashEntry(VM&, PropertyName) const;
+
     DECLARE_EXPORT_INFO;
 
 protected:
@@ -1032,7 +1034,7 @@ protected:
         
     // This is relevant to undecided, int32, double, and contiguous.
     unsigned countElements();
-        
+
 private:
     friend class LLIntOffsetsExtractor;
     friend class VMInspector;
@@ -1060,11 +1062,6 @@ private:
     void fillCustomGetterPropertySlot(VM&, PropertySlot&, CustomGetterSetter*, unsigned, Structure*);
 
     JS_EXPORT_PRIVATE bool getOwnStaticPropertySlot(VM&, PropertyName, PropertySlot&);
-    struct PropertyHashEntry {
-        const HashTable* table;
-        const HashTableValue* value;
-    };
-    Optional<PropertyHashEntry> findPropertyHashEntry(VM&, PropertyName) const;
         
     bool putByIndexBeyondVectorLength(ExecState*, unsigned propertyName, JSValue, bool shouldThrow);
     bool putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState*, unsigned propertyName, JSValue, unsigned attributes, PutDirectIndexMode, ArrayStorage*);
index 541f345..ecc4d06 100644 (file)
 
 namespace JSC {
 
-template<typename Watchpoint>
+template<typename WatchpointSet>
 class ObjectPropertyChangeAdaptiveWatchpoint final : public AdaptiveInferredPropertyValueWatchpointBase {
 public:
     using Base = AdaptiveInferredPropertyValueWatchpointBase;
-    ObjectPropertyChangeAdaptiveWatchpoint(JSCell* owner, const ObjectPropertyCondition& condition, Watchpoint& watchpoint)
+    ObjectPropertyChangeAdaptiveWatchpoint(JSCell* owner, const ObjectPropertyCondition& condition, WatchpointSet& watchpointSet)
         : Base(condition)
         , m_owner(owner)
-        , m_watchpoint(watchpoint)
+        , m_watchpointSet(watchpointSet)
     {
-        RELEASE_ASSERT(watchpoint.stateOnJSThread() == IsWatched);
+        RELEASE_ASSERT(watchpointSet.stateOnJSThread() == IsWatched);
     }
 
 private:
@@ -49,11 +49,11 @@ private:
 
     void handleFire(VM& vm, const FireDetail&) override
     {
-        m_watchpoint.fireAll(vm, StringFireDetail("Object Property is changed."));
+        m_watchpointSet.fireAll(vm, StringFireDetail("Object Property is changed."));
     }
 
     JSCell* m_owner;
-    Watchpoint& m_watchpoint;
+    WatchpointSet& m_watchpointSet;
 };
 
 } // namespace JSC
index 39d3866..7ebd090 100644 (file)
@@ -1238,4 +1238,15 @@ bool Structure::canAccessPropertiesQuicklyForEnumeration() const
     return true;
 }
 
+auto Structure::findPropertyHashEntry(PropertyName propertyName) const -> Optional<PropertyHashEntry>
+{
+    for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
+        if (const HashTable* propHashTable = info->staticPropHashTable) {
+            if (const HashTableValue* entry = propHashTable->entry(propertyName))
+                return PropertyHashEntry { propHashTable, entry };
+        }
+    }
+    return WTF::nullopt;
+}
+
 } // namespace JSC
index 33f1a7c..22b3310 100644 (file)
@@ -61,6 +61,8 @@ class StructureShape;
 class SlotVisitor;
 class JSString;
 struct DumpContext;
+struct HashTable;
+struct HashTableValue;
 
 // The out-of-line property storage capacity to use when first allocating out-of-line
 // storage. Note that all objects start out without having any out-of-line storage;
@@ -615,6 +617,12 @@ public:
     unsigned propertyHash() const { return m_propertyHash; }
 
     static bool shouldConvertToPolyProto(const Structure* a, const Structure* b);
+
+    struct PropertyHashEntry {
+        const HashTable* table;
+        const HashTableValue* value;
+    };
+    Optional<PropertyHashEntry> findPropertyHashEntry(PropertyName) const;
     
     DECLARE_EXPORT_INFO;
 
index 6f9fafd..421df4e 100644 (file)
@@ -586,6 +586,79 @@ private:
     Vector<int> m_vector;
 };
 
+static const struct CompactHashIndex staticCustomAccessorTableIndex[2] = {
+    { 0, -1 },
+    { -1, -1 },
+};
+
+static EncodedJSValue testStaticAccessorGetter(ExecState* exec, EncodedJSValue thisValue, PropertyName)
+{
+    DollarVMAssertScope assertScope;
+    VM& vm = exec->vm();
+    
+    JSObject* thisObject = jsDynamicCast<JSObject*>(vm, JSValue::decode(thisValue));
+    RELEASE_ASSERT(thisObject);
+
+    if (JSValue result = thisObject->getDirect(vm, PropertyName(Identifier::fromString(vm, "testField"))))
+        return JSValue::encode(result);
+    return JSValue::encode(jsUndefined());
+}
+
+static bool testStaticAccessorPutter(ExecState* exec, EncodedJSValue thisValue, EncodedJSValue value)
+{
+    DollarVMAssertScope assertScope;
+    VM& vm = exec->vm();
+    
+    JSObject* thisObject = jsDynamicCast<JSObject*>(vm, JSValue::decode(thisValue));
+    RELEASE_ASSERT(thisObject);
+
+    return thisObject->putDirect(vm, PropertyName(Identifier::fromString(vm, "testField")), JSValue::decode(value));
+}
+
+static const struct HashTableValue staticCustomAccessorTableValues[1] = {
+    { "testStaticAccessor", static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(testStaticAccessorGetter), (intptr_t)static_cast<PutPropertySlot::PutValueFunc>(testStaticAccessorPutter) } },
+};
+
+static const struct HashTable staticCustomAccessorTable =
+    { 1, 1, true, nullptr, staticCustomAccessorTableValues, staticCustomAccessorTableIndex };
+
+class StaticCustomAccessor : public JSNonFinalObject {
+    using Base = JSNonFinalObject;
+public:
+    StaticCustomAccessor(VM& vm, Structure* structure)
+        : Base(vm, structure)
+    {
+        DollarVMAssertScope assertScope;
+    }
+
+    DECLARE_INFO;
+
+    static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable | OverridesGetOwnPropertySlot;
+
+    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+    {
+        DollarVMAssertScope assertScope;
+        return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
+    }
+
+    static StaticCustomAccessor* create(VM& vm, Structure* structure)
+    {
+        DollarVMAssertScope assertScope;
+        StaticCustomAccessor* accessor = new (NotNull, allocateCell<StaticCustomAccessor>(vm.heap)) StaticCustomAccessor(vm, structure);
+        accessor->finishCreation(vm);
+        return accessor;
+    }
+
+    static bool getOwnPropertySlot(JSObject* thisObject, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
+    {
+        if (String(propertyName.uid()) == "thinAirCustomGetter") {
+            slot.setCacheableCustom(thisObject, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum | PropertyAttribute::CustomAccessor, testStaticAccessorGetter);
+            return true;
+        }
+        return JSNonFinalObject::getOwnPropertySlot(thisObject, exec, propertyName, slot);
+    }
+};
+
 class DOMJITNode : public JSNonFinalObject {
 public:
     DOMJITNode(VM& vm, Structure* structure)
@@ -729,6 +802,7 @@ void DOMJITGetter::finishCreation(VM& vm)
     putDirectCustomAccessor(vm, Identifier::fromString(vm, "customGetter"), customGetterSetter, PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor);
 }
 
+
 class DOMJITGetterComplex : public DOMJITNode {
 public:
     DOMJITGetterComplex(VM& vm, Structure* structure)
@@ -1199,6 +1273,8 @@ const ClassInfo DOMJITFunctionObject::s_info = { "DOMJITFunctionObject", &Base::
 const ClassInfo DOMJITCheckSubClassObject::s_info = { "DOMJITCheckSubClassObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DOMJITCheckSubClassObject) };
 const ClassInfo JSTestCustomGetterSetter::s_info = { "JSTestCustomGetterSetter", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTestCustomGetterSetter) };
 
+const ClassInfo StaticCustomAccessor::s_info = { "StaticCustomAccessor", &Base::s_info, &staticCustomAccessorTable, nullptr, CREATE_METHOD_TABLE(StaticCustomAccessor) };
+
 ElementHandleOwner* Element::handleOwner()
 {
     DollarVMAssertScope assertScope;
@@ -2015,6 +2091,16 @@ static EncodedJSValue JSC_HOST_CALL functionCreateWasmStreamingParser(ExecState*
 }
 #endif
 
+static EncodedJSValue JSC_HOST_CALL functionCreateStaticCustomAccessor(ExecState* exec)
+{
+    DollarVMAssertScope assertScope;
+    VM& vm = exec->vm();
+    JSLockHolder lock(vm);
+    Structure* structure = StaticCustomAccessor::createStructure(vm, exec->lexicalGlobalObject(), jsNull());
+    auto* result = StaticCustomAccessor::create(vm, structure);
+    return JSValue::encode(result);
+}
+
 static EncodedJSValue JSC_HOST_CALL functionSetImpureGetterDelegate(ExecState* exec)
 {
     DollarVMAssertScope assertScope;
@@ -2537,6 +2623,7 @@ void JSDollarVM::finishCreation(VM& vm)
 #if ENABLE(WEBASSEMBLY)
     addFunction(vm, "createWasmStreamingParser", functionCreateWasmStreamingParser, 0);
 #endif
+    addFunction(vm, "createStaticCustomAccessor", functionCreateStaticCustomAccessor, 0);
     addFunction(vm, "getPrivateProperty", functionGetPrivateProperty, 2);
     addFunction(vm, "setImpureGetterDelegate", functionSetImpureGetterDelegate, 2);