Title: [250540] trunk
Revision
250540
Author
sbar...@apple.com
Date
2019-09-30 17:50:46 -0700 (Mon, 30 Sep 2019)

Log Message

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.

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/_javascript_Core:

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.

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (250539 => 250540)


--- trunk/JSTests/ChangeLog	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/JSTests/ChangeLog	2019-10-01 00:50:46 UTC (rev 250540)
@@ -1,3 +1,50 @@
+2019-09-30  Saam Barati  <sbar...@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  <ysuz...@apple.com>
 
         [JSC] AI folds CompareEq wrongly when it sees proven Boolean and Number

Added: trunk/JSTests/microbenchmarks/custom-accessor-materialized.js (0 => 250540)


--- trunk/JSTests/microbenchmarks/custom-accessor-materialized.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/custom-accessor-materialized.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/microbenchmarks/custom-accessor-thin-air.js (0 => 250540)


--- trunk/JSTests/microbenchmarks/custom-accessor-thin-air.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/custom-accessor-thin-air.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/microbenchmarks/custom-accessor.js (0 => 250540)


--- trunk/JSTests/microbenchmarks/custom-accessor.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/custom-accessor.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/microbenchmarks/custom-value-2.js (0 => 250540)


--- trunk/JSTests/microbenchmarks/custom-value-2.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/custom-value-2.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/microbenchmarks/custom-value.js (0 => 250540)


--- trunk/JSTests/microbenchmarks/custom-value.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/custom-value.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/stress/custom-accessor-delete-1.js (0 => 250540)


--- trunk/JSTests/stress/custom-accessor-delete-1.js	                        (rev 0)
+++ trunk/JSTests/stress/custom-accessor-delete-1.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/stress/custom-accessor-delete-2.js (0 => 250540)


--- trunk/JSTests/stress/custom-accessor-delete-2.js	                        (rev 0)
+++ trunk/JSTests/stress/custom-accessor-delete-2.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/stress/custom-accessor-delete-3.js (0 => 250540)


--- trunk/JSTests/stress/custom-accessor-delete-3.js	                        (rev 0)
+++ trunk/JSTests/stress/custom-accessor-delete-3.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/stress/custom-value-delete-property-1.js (0 => 250540)


--- trunk/JSTests/stress/custom-value-delete-property-1.js	                        (rev 0)
+++ trunk/JSTests/stress/custom-value-delete-property-1.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/stress/custom-value-delete-property-2.js (0 => 250540)


--- trunk/JSTests/stress/custom-value-delete-property-2.js	                        (rev 0)
+++ trunk/JSTests/stress/custom-value-delete-property-2.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Added: trunk/JSTests/stress/custom-value-delete-property-3.js (0 => 250540)


--- trunk/JSTests/stress/custom-value-delete-property-3.js	                        (rev 0)
+++ trunk/JSTests/stress/custom-value-delete-property-3.js	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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();

Modified: trunk/LayoutTests/ChangeLog (250539 => 250540)


--- trunk/LayoutTests/ChangeLog	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/LayoutTests/ChangeLog	2019-10-01 00:50:46 UTC (rev 250540)
@@ -1,3 +1,14 @@
+2019-09-30  Saam Barati  <sbar...@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  <cdu...@apple.com>
 
         IDBTransaction / IDBObjectStore should not prevent a page from entering the back / forward cache

Added: trunk/LayoutTests/js/dom/custom-accessor-redefine-expected.txt (0 => 250540)


--- trunk/LayoutTests/js/dom/custom-accessor-redefine-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/custom-accessor-redefine-expected.txt	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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
+

Added: trunk/LayoutTests/js/dom/custom-accessor-redefine.html (0 => 250540)


--- trunk/LayoutTests/js/dom/custom-accessor-redefine.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/custom-accessor-redefine.html	2019-10-01 00:50:46 UTC (rev 250540)
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</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 = ""
+    }
+
+    // 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=""
+</body>
+</html>

Modified: trunk/Source/_javascript_Core/ChangeLog (250539 => 250540)


--- trunk/Source/_javascript_Core/ChangeLog	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/ChangeLog	2019-10-01 00:50:46 UTC (rev 250540)
@@ -1,3 +1,98 @@
+2019-09-30  Saam Barati  <sbar...@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  <ysuz...@apple.com>
 
         [JSC] AI folds CompareEq wrongly when it sees proven Boolean and Number

Modified: trunk/Source/_javascript_Core/bytecode/AccessCase.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/AccessCase.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/AccessCase.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -61,6 +61,7 @@
 {
     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::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 @@
     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 @@
         }
 
         // 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);

Modified: trunk/Source/_javascript_Core/bytecode/AdaptiveInferredPropertyValueWatchpointBase.h (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/AdaptiveInferredPropertyValueWatchpointBase.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/AdaptiveInferredPropertyValueWatchpointBase.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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;

Modified: trunk/Source/_javascript_Core/bytecode/ObjectPropertyCondition.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/ObjectPropertyCondition.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/ObjectPropertyCondition.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -46,18 +46,12 @@
     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

Modified: trunk/Source/_javascript_Core/bytecode/ObjectPropertyCondition.h (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/ObjectPropertyCondition.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/ObjectPropertyCondition.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -121,6 +121,17 @@
             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 @@
     
     // 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 @@
         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;

Modified: trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -62,7 +62,22 @@
 
 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 @@
     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 @@
         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 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);

Modified: trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.h (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -176,7 +176,7 @@
     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);

Modified: trunk/Source/_javascript_Core/bytecode/PolyProtoAccessChain.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/PolyProtoAccessChain.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/PolyProtoAccessChain.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -51,12 +51,6 @@
     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 @@
             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)

Modified: trunk/Source/_javascript_Core/bytecode/PolymorphicAccess.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/PolymorphicAccess.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/PolymorphicAccess.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -55,9 +55,9 @@
         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 @@
     for (WatchpointSet* set : accessCase.commit(vm, ident)) {
         Watchpoint* watchpoint =
             WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint(
-                watchpoints, codeBlock, &stubInfo, ObjectPropertyCondition());
+                watchpoints, codeBlock, &stubInfo);
         
         set->add(watchpoint);
     }

Modified: trunk/Source/_javascript_Core/bytecode/PolymorphicAccess.h (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/PolymorphicAccess.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/PolymorphicAccess.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -212,7 +212,7 @@
     std::unique_ptr<WatchpointsOnStructureStubInfo> watchpoints;
     Vector<WriteBarrier<JSCell>> weakReferences;
 
-    Watchpoint* addWatchpoint(const ObjectPropertyCondition& = ObjectPropertyCondition());
+    void installWatchpoint(const ObjectPropertyCondition&);
 
     void restoreScratch();
     void succeed();

Modified: trunk/Source/_javascript_Core/bytecode/PropertyCondition.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/PropertyCondition.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/PropertyCondition.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -54,6 +54,9 @@
     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 @@
     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 @@
         }
         
         return true;
-    } }
+    } 
+    case CustomFunctionEquivalence: {
+        if (structure->staticPropertiesReified())
+            return false;
+        return !!structure->findPropertyHashEntry(uid());
+    }
+    }
     
     RELEASE_ASSERT_NOT_REACHED();
     return false;
@@ -263,6 +273,7 @@
     case Presence:
     case Absence:
     case Equivalence:
+    case CustomFunctionEquivalence:
         return structure->needImpurePropertyWatchpoint();
     case AbsenceOfSetEffect:
     case HasPrototype:
@@ -288,6 +299,7 @@
         break;
     case Presence:
     case Equivalence:
+    case CustomFunctionEquivalence:
         if (structure->typeInfo().getOwnPropertySlotIsImpure())
             return false;
         break;
@@ -329,6 +341,21 @@
         
         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 @@
     case JSC::PropertyCondition::Equivalence:
         out.print("Equivalence");
         return;
+    case JSC::PropertyCondition::CustomFunctionEquivalence:
+        out.print("CustomFunctionEquivalence");
+        return;
     case JSC::PropertyCondition::HasPrototype:
         out.print("HasPrototype");
         return;

Modified: trunk/Source/_javascript_Core/bytecode/PropertyCondition.h (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/PropertyCondition.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/PropertyCondition.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -40,6 +40,7 @@
         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 @@
             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 @@
         case Equivalence:
             result ^= EncodedJSValueHash::hash(u.equivalence.value);
             break;
+        case CustomFunctionEquivalence:
+            break;
         }
         return result;
     }
@@ -213,6 +223,8 @@
             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 @@
     // 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
     {

Modified: trunk/Source/_javascript_Core/bytecode/StructureStubClearingWatchpoint.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/StructureStubClearingWatchpoint.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/StructureStubClearingWatchpoint.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -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 @@
     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,9 +81,43 @@
         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
 
 #endif // ENABLE(JIT)

Modified: trunk/Source/_javascript_Core/bytecode/StructureStubClearingWatchpoint.h (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/StructureStubClearingWatchpoint.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/StructureStubClearingWatchpoint.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -25,11 +25,11 @@
 
 #pragma once
 
+#if ENABLE(JIT)
+
+#include "AdaptiveInferredPropertyValueWatchpointBase.h"
 #include "ObjectPropertyCondition.h"
 #include "Watchpoint.h"
-
-#if ENABLE(JIT)
-
 #include <wtf/Bag.h>
 #include <wtf/FastMalloc.h>
 #include <wtf/Noncopyable.h>
@@ -40,12 +40,12 @@
 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 @@
     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 @@
     {
     }
     
-    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 @@
 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

Modified: trunk/Source/_javascript_Core/bytecode/Watchpoint.h (250539 => 250540)


--- trunk/Source/_javascript_Core/bytecode/Watchpoint.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/bytecode/Watchpoint.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -115,7 +115,7 @@
 #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) \

Modified: trunk/Source/_javascript_Core/jit/Repatch.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/jit/Repatch.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/jit/Repatch.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -321,7 +321,7 @@
                         } else {
                             conditionSet = generateConditionsForPrototypePropertyHitCustom(
                                 vm, codeBlock, exec, structure, slot.slotBase(),
-                                propertyName.impl());
+                                propertyName.impl(), slot.attributes());
                         }
 
                         if (!conditionSet.isValid())
@@ -549,7 +549,7 @@
                         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;
                     }

Modified: trunk/Source/_javascript_Core/runtime/ClassInfo.h (250539 => 250540)


--- trunk/Source/_javascript_Core/runtime/ClassInfo.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/runtime/ClassInfo.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -189,12 +189,18 @@
     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 @@
     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

Modified: trunk/Source/_javascript_Core/runtime/JSObject.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/runtime/JSObject.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/runtime/JSObject.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -2222,15 +2222,9 @@
     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)

Modified: trunk/Source/_javascript_Core/runtime/JSObject.h (250539 => 250540)


--- trunk/Source/_javascript_Core/runtime/JSObject.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/runtime/JSObject.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -903,6 +903,8 @@
     bool mayBePrototype() const;
     void didBecomePrototype();
 
+    Optional<Structure::PropertyHashEntry> findPropertyHashEntry(VM&, PropertyName) const;
+
     DECLARE_EXPORT_INFO;
 
 protected:
@@ -1032,7 +1034,7 @@
         
     // This is relevant to undecided, int32, double, and contiguous.
     unsigned countElements();
-        
+
 private:
     friend class LLIntOffsetsExtractor;
     friend class VMInspector;
@@ -1060,11 +1062,6 @@
     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*);

Modified: trunk/Source/_javascript_Core/runtime/ObjectPropertyChangeAdaptiveWatchpoint.h (250539 => 250540)


--- trunk/Source/_javascript_Core/runtime/ObjectPropertyChangeAdaptiveWatchpoint.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/runtime/ObjectPropertyChangeAdaptiveWatchpoint.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -29,16 +29,16 @@
 
 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 @@
 
     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

Modified: trunk/Source/_javascript_Core/runtime/Structure.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/runtime/Structure.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/runtime/Structure.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -1238,4 +1238,15 @@
     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

Modified: trunk/Source/_javascript_Core/runtime/Structure.h (250539 => 250540)


--- trunk/Source/_javascript_Core/runtime/Structure.h	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/runtime/Structure.h	2019-10-01 00:50:46 UTC (rev 250540)
@@ -61,6 +61,8 @@
 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 @@
     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;
 

Modified: trunk/Source/_javascript_Core/tools/JSDollarVM.cpp (250539 => 250540)


--- trunk/Source/_javascript_Core/tools/JSDollarVM.cpp	2019-10-01 00:47:04 UTC (rev 250539)
+++ trunk/Source/_javascript_Core/tools/JSDollarVM.cpp	2019-10-01 00:50:46 UTC (rev 250540)
@@ -586,6 +586,79 @@
     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 @@
     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 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 @@
 }
 #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 @@
 #if ENABLE(WEBASSEMBLY)
     addFunction(vm, "createWasmStreamingParser", functionCreateWasmStreamingParser, 0);
 #endif
+    addFunction(vm, "createStaticCustomAccessor", functionCreateStaticCustomAccessor, 0);
     addFunction(vm, "getPrivateProperty", functionGetPrivateProperty, 2);
     addFunction(vm, "setImpureGetterDelegate", functionSetImpureGetterDelegate, 2);
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to