Title: [255947] releases/WebKitGTK/webkit-2.28
Revision
255947
Author
[email protected]
Date
2020-02-06 07:11:52 -0800 (Thu, 06 Feb 2020)

Log Message

Merge r255897 - Deleting a property should not turn structures into uncacheable dictionaries
https://bugs.webkit.org/show_bug.cgi?id=206430

Reviewed by Yusuke Suzuki.

JSTests:

* microbenchmarks/delete-property-from-prototype-chain.js: Added.
(assert):
(noInline.assert.getZ):
(noInline.getZ.C):
(doTest):
(delete.C.prototype.z):
* microbenchmarks/delete-property-keeps-cacheable-structure.js: Added.
(assert):
(C):
(doTest):
* stress/cache-put-by-id-different-attributes.js:
(makePrototypeDict):
(set x):
* stress/cache-put-by-id-different-offset.js:
(makePrototypeDict):
(set x):
* stress/cache-put-by-id-poly-proto.js:
(makePrototypeDict):
(set _):
* stress/delete-property-check-structure-transition.js: Added.
(assert):
(assert_eq):
(assert_neq):
(sd):
(sid):
(testDeleteIsNotUncacheable):
(testCanMaterializeDeletes):
(testCanFlatten):
(testDeleteWithInlineCache.Object.prototype.globalProperty.42.makeFoo):
(testDeleteWithInlineCache.noInline.doTest):
(testDeleteWithInlineCache):
* stress/flatten-object-zero-unused-inline-properties.js:

Source/_javascript_Core:

Right now, deleteProperty/removePropertyTransition causes a structure transition to uncacheable dictionary. Instead, we should allow it to transition to a new regular structure like adding a property does. This means that we have to:

1) Break the assumption that structure transition offsets increase monotonically

We add a new flag to tell that a structure has deleted its property, and update materializePropertyTable to use it.

2) Add a new transition map and transition kind for deletes

We cache the delete transition. We will not transition back to a previous structure if you add then immediately remove a property.

3) Find some heuristic for when we should actually transition to uncacheable dictionary.

Since deleting properties is expected to be rare, we just walk the structure list and count its size on removal.

This patch also fixes a related bug in addProperty, where we did not use a GCSafeConcurrentJSLocker, and adds an option to trigger the bug. Finally, we add some helper methods to dollarVM to test.

This gives a 24x speedup on delete-property-keeps-cacheable-structure.js, and is neutral on delete-property-from-prototype-chain.js (which was already generating code using the inline cache).

* heap/HeapInlines.h:
(JSC::Heap::decrementDeferralDepthAndGCIfNeeded):
* runtime/JSObject.cpp:
(JSC::JSObject::deleteProperty):
* runtime/OptionsList.h:
* runtime/PropertyMapHashTable.h:
(JSC::PropertyTable::get):
(JSC::PropertyTable::add):
(JSC::PropertyTable::addDeletedOffset):
(JSC::PropertyTable::reinsert):
* runtime/Structure.cpp:
(JSC::StructureTransitionTable::contains const):
(JSC::StructureTransitionTable::get const):
(JSC::StructureTransitionTable::add):
(JSC::Structure::Structure):
(JSC::Structure::materializePropertyTable):
(JSC::Structure::addNewPropertyTransition):
(JSC::Structure::removePropertyTransition):
(JSC::Structure::removePropertyTransitionFromExistingStructure):
(JSC::Structure::removeNewPropertyTransition):
(JSC::Structure::toUncacheableDictionaryTransition):
(JSC::Structure::remove):
(JSC::Structure::visitChildren):
* runtime/Structure.h:
* runtime/StructureInlines.h:
(JSC::Structure::forEachPropertyConcurrently):
(JSC::Structure::add):
(JSC::Structure::remove):
(JSC::Structure::removePropertyWithoutTransition):
* runtime/StructureTransitionTable.h:
(JSC::StructureTransitionTable::Hash::hash):
* tools/JSDollarVM.cpp:
(JSC::JSDollarVMHelper::functionGetStructureTransitionList):
(JSC::functionGetConcurrently):
(JSC::JSDollarVM::finishCreation):

Modified Paths

Added Paths

Diff

Modified: releases/WebKitGTK/webkit-2.28/JSTests/ChangeLog (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/ChangeLog	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/JSTests/ChangeLog	2020-02-06 15:11:52 UTC (rev 255947)
@@ -1,3 +1,43 @@
+2020-02-05  Justin Michaud  <[email protected]>
+
+        Deleting a property should not turn structures into uncacheable dictionaries
+        https://bugs.webkit.org/show_bug.cgi?id=206430
+
+        Reviewed by Yusuke Suzuki.
+
+        * microbenchmarks/delete-property-from-prototype-chain.js: Added.
+        (assert):
+        (noInline.assert.getZ):
+        (noInline.getZ.C):
+        (doTest):
+        (delete.C.prototype.z):
+        * microbenchmarks/delete-property-keeps-cacheable-structure.js: Added.
+        (assert):
+        (C):
+        (doTest):
+        * stress/cache-put-by-id-different-attributes.js:
+        (makePrototypeDict):
+        (set x):
+        * stress/cache-put-by-id-different-offset.js:
+        (makePrototypeDict):
+        (set x):
+        * stress/cache-put-by-id-poly-proto.js:
+        (makePrototypeDict):
+        (set _):
+        * stress/delete-property-check-structure-transition.js: Added.
+        (assert):
+        (assert_eq):
+        (assert_neq):
+        (sd):
+        (sid):
+        (testDeleteIsNotUncacheable):
+        (testCanMaterializeDeletes):
+        (testCanFlatten):
+        (testDeleteWithInlineCache.Object.prototype.globalProperty.42.makeFoo):
+        (testDeleteWithInlineCache.noInline.doTest):
+        (testDeleteWithInlineCache):
+        * stress/flatten-object-zero-unused-inline-properties.js:
+
 2020-02-04  Alexey Shvayka  <[email protected]>
 
         Quantifiers after lookahead assertions should be syntax errors in Unicode patterns only

Added: releases/WebKitGTK/webkit-2.28/JSTests/microbenchmarks/delete-property-from-prototype-chain.js (0 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/microbenchmarks/delete-property-from-prototype-chain.js	                        (rev 0)
+++ releases/WebKitGTK/webkit-2.28/JSTests/microbenchmarks/delete-property-from-prototype-chain.js	2020-02-06 15:11:52 UTC (rev 255947)
@@ -0,0 +1,43 @@
+//@ skip if $model == "Apple Watch Series 3" # added by mark-jsc-stress-test.py
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+noInline(assert)
+
+function getZ(o) {
+    return o.z
+}
+noInline(getZ)
+
+function C() {
+    this.x = 42;
+}
+
+let objs = [];
+for (let i = 0; i < 50; ++i) {
+    objs.push(new C);
+}
+
+function doTest(zVal) {
+    for (let i = 0; i < objs.length; ++i) {
+        let o = objs[i];
+        assert(o.x === 42);
+        assert(getZ(o) === zVal)
+    }
+}
+noInline(doTest);
+
+for (let i=0; i<10000; ++i) {
+    const X = { i }
+    C.prototype.z = X
+    doTest(X)
+}
+
+delete C.prototype.z
+
+for (let i=0; i<1000000; ++i) {
+    getZ({z: i})
+    doTest(undefined)
+}
+

Added: releases/WebKitGTK/webkit-2.28/JSTests/microbenchmarks/delete-property-keeps-cacheable-structure.js (0 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/microbenchmarks/delete-property-keeps-cacheable-structure.js	                        (rev 0)
+++ releases/WebKitGTK/webkit-2.28/JSTests/microbenchmarks/delete-property-keeps-cacheable-structure.js	2020-02-06 15:11:52 UTC (rev 255947)
@@ -0,0 +1,28 @@
+//@ skip if $model == "Apple Watch Series 3" # added by mark-jsc-stress-test.py
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function C() {
+    this.x = 42;
+    this.y = 1337;
+    delete this.y;
+}
+
+let objs = [];
+for (let i = 0; i < 50; ++i) {
+    objs.push(new C);
+}
+
+function doTest() {
+    for (let i = 0; i < objs.length; ++i) {
+        let o = objs[i];
+        assert(o.y === undefined);
+        assert(o.x === 42);
+    }
+}
+noInline(doTest);
+
+for (let i=0; i<10000000; ++i) doTest()
+

Modified: releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-different-attributes.js (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-different-attributes.js	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-different-attributes.js	2020-02-06 15:11:52 UTC (rev 255947)
@@ -4,6 +4,16 @@
 
 Foo.prototype.x = 0;
 
+function makePrototypeDict() {
+    for (let i=0; i<100; ++i) {
+        Foo.prototype.a = i
+        Foo.prototype.b = i
+        delete Foo.prototype.a
+        delete Foo.prototype.b
+    }
+}
+noInline(makePrototypeDict)
+
 Object.defineProperty(Foo.prototype, 'y', {
     set(x) {
         if (Foo.prototype.x++ === 9) {
@@ -11,8 +21,10 @@
                 value: 13,
                 writable: true,
             });
-            if (typeof $vm !== 'undefined')
+            if (typeof $vm !== 'undefined') {
+                makePrototypeDict()
                 $vm.flattenDictionaryObject(Foo.prototype);
+            }
         }
     },
     configurable: true,

Modified: releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-different-offset.js (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-different-offset.js	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-different-offset.js	2020-02-06 15:11:52 UTC (rev 255947)
@@ -4,12 +4,24 @@
 
 Foo.prototype.x = 0;
 
+function makePrototypeDict() {
+    for (let i=0; i<100; ++i) {
+        Foo.prototype.a = i
+        Foo.prototype.b = i
+        delete Foo.prototype.a
+        delete Foo.prototype.b
+    }
+}
+noInline(makePrototypeDict)
+
 Object.defineProperty(Foo.prototype, 'y', {
     set(x) {
         if (Foo.prototype.x++ === 1) {
             delete Foo.prototype.x;
-            if (typeof $vm !== 'undefined')
+            if (typeof $vm !== 'undefined') {
+                makePrototypeDict()
                 $vm.flattenDictionaryObject(Foo.prototype);
+            }
         }
     }
 });

Modified: releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-poly-proto.js (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-poly-proto.js	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/JSTests/stress/cache-put-by-id-poly-proto.js	2020-02-06 15:11:52 UTC (rev 255947)
@@ -4,6 +4,16 @@
 
 let x = 0;
 
+function makePrototypeDict() {
+    for (let i=0; i<100; ++i) {
+        Foo.prototype.a = i
+        Foo.prototype.b = i
+        delete Foo.prototype.a
+        delete Foo.prototype.b
+    }
+}
+noInline(makePrototypeDict)
+
 Object.defineProperty(Foo.prototype, 'y', {
     set(_) {
         if (x++ === 9) {
@@ -11,8 +21,10 @@
                 value: 13,
                 writable: true,
             });
-            if (typeof $vm !== 'undefined')
+            if (typeof $vm !== 'undefined') {
+                makePrototypeDict()
                 $vm.flattenDictionaryObject(Foo.prototype);
+            }
         }
     },
     configurable: true,

Added: releases/WebKitGTK/webkit-2.28/JSTests/stress/delete-property-check-structure-transition.js (0 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/stress/delete-property-check-structure-transition.js	                        (rev 0)
+++ releases/WebKitGTK/webkit-2.28/JSTests/stress/delete-property-check-structure-transition.js	2020-02-06 15:11:52 UTC (rev 255947)
@@ -0,0 +1,189 @@
+function assert(condition) {
+    if (!condition)
+        throw new Error("assertion failed");
+}
+
+function assert_eq(a, b) {
+    if (a !== b)
+        throw new Error("assertion failed: " + a + " === " + b);
+}
+
+function assert_neq(a, b) {
+    if (a === b)
+        throw new Error("assertion failed: " + a + " !== " + b);
+}
+
+function sd(obj) {
+    let data = ""
+    let result = []
+
+    if (!data)
+        return result
+
+    for (let i = 0; i < data.length/5; ++i) {
+        result.push({ id: data[i*5+0], offset: data[i*5+1], max: data[i*5+2], property: data[i*5+3], type: data[i*5+4] == 0 ? "added" : "deleted" })
+    }
+
+    return result
+}
+
+function sid(obj) {
+    let data = ""
+    return data[data.length-1].id
+}
+
+function testDeleteIsNotUncacheable(i) {
+    let foo = {}
+    foo["bar" + i] = 1
+    foo["baz" + i] = 2
+    assert(foo["bar" + i] === 1)
+    assert(foo["baz" + i] === 2)
+    let oldSid = sid(foo)
+
+    assert_eq($vm.getConcurrently(foo, "baz"+i), 1)
+    assert_eq($vm.getConcurrently(foo, "bar"+i), 1)
+    assert_eq($vm.getConcurrently(foo, "foo"+i), 0)
+
+    assert(delete foo["bar" + i])
+    assert_neq(oldSid, sid(foo))
+    assert(!(("bar" + i) in foo));
+    assert(foo["baz" + i] === 2)
+
+    assert_eq($vm.getConcurrently(foo, "baz"+i), 1)
+    assert_eq($vm.getConcurrently(foo, "bar"+i), 0)
+
+    assert_eq(Object.keys(foo).length, 1)
+
+    let data = ""
+    assert_eq(data[data.length-1].property, "bar" + i)
+
+    foo["bar" + i] = 1
+    assert_eq($vm.getConcurrently(foo, "baz"+i), 1)
+    assert_eq($vm.getConcurrently(foo, "bar"+i), 1)
+    assert(foo["bar" + i] === 1)
+}
+
+function testCanMaterializeDeletes(i) {
+    let foo = {}
+    foo["bar" + i] = 1
+    foo["baz" + i] = 2
+
+    assert(foo["bar" + i] === 1)
+    assert(foo["baz" + i] === 2)
+    assert(delete foo["bar" + i])
+    assert(!("bar" + i in foo))
+    assert(foo["baz" + i] === 2)
+    assert_eq(Object.keys(foo).length, 1)
+
+    let foo2 = {}
+    foo2["bar" + i] = 3
+    foo2["baz" + i] = 4
+    assert(delete foo2["bar" + i])
+
+    assert_eq(sid(foo2), sid(foo))
+    foo2["fun" + i] = 3
+    assert_neq(sid(foo2), sid(foo))
+
+    assert(foo2["fun" + i] === 3)
+    assert(foo2["baz" + i] === 4)
+    assert(!("bar" + i in foo2))
+    assert_eq(Object.keys(foo2).length, 2)
+    assert(foo["baz" + i] === 2)
+    assert(!("bar" + i in foo))
+    assert_eq(Object.keys(foo).length, 1)
+
+    let data = ""
+    assert_eq(data[data.length-1].property, "bar" + i)
+
+    data = ""
+    assert_eq(data[data.length-1].property, "fun" + i)
+    assert_eq(data[data.length-2].property, "bar" + i)
+}
+
+function testCanFlatten(i) {
+    let foo = {}
+    for (let j=0; j<500; ++j) {
+        const oldId = sid(foo)
+
+        foo["x" + 1000*j + i] = j
+        if (j > 0)
+            delete foo["x" + 1000*(j - 1) + i]
+
+        if (j > 100)
+            assert_eq(sid(foo), oldId)
+    }
+
+    for (let j=0; j<500; ++j) {
+        const val = foo["x" + 1000*j + i]
+        if (j == 499)
+            assert_eq(val, j)
+        else
+            assert_eq(val, undefined)
+    }
+
+    $vm.flattenDictionaryObject(foo)
+
+    for (let j=0; j<500; ++j) {
+        const val = foo["x" + 1000*j + i]
+        if (j == 499)
+            assert_eq(val, j)
+        else
+            assert_eq(val, undefined)
+    }
+}
+
+function testDeleteWithInlineCache() {
+    Object.prototype.globalProperty = 42
+
+    function makeFoo() {
+        let foo = {}
+        foo.baz = 1
+        assert(foo.globalProperty === 42)
+
+        return foo
+    }
+    noInline(makeFoo)
+
+    function doTest(xVal) {
+        for (let j=0; j<50; ++j) {
+            for (let z=0; z<10000; ++z) {
+                const foo = arr[j]
+
+                assert(foo.baz === 1)
+                assert_eq(Object.keys(foo).length, 1)
+                assert_eq(foo.globalProperty, xVal)
+            }
+        }
+    }
+    noInline(doTest)
+
+    arr = new Array(50)
+
+    for (let j=0; j<50; ++j) {
+        arr[j] = makeFoo()
+        if (j > 0)
+            assert_eq(sid(arr[j-1]), sid(arr[j]))
+    }
+
+    doTest(42)
+
+    Object.prototype.globalProperty = 43
+
+    doTest(43)
+
+    delete Object.prototype.globalProperty
+
+    doTest(undefined)
+}
+
+testDeleteWithInlineCache()
+
+for (let i = 0; i < 1000; ++i) {
+    testDeleteIsNotUncacheable(i)
+    testCanMaterializeDeletes(1000+i)
+}
+
+for (let i = 0; i < 100; ++i) {
+    testCanFlatten(2000+i)
+}
+

Modified: releases/WebKitGTK/webkit-2.28/JSTests/stress/flatten-object-zero-unused-inline-properties.js (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/JSTests/stress/flatten-object-zero-unused-inline-properties.js	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/JSTests/stress/flatten-object-zero-unused-inline-properties.js	2020-02-06 15:11:52 UTC (rev 255947)
@@ -1,4 +1,10 @@
 let o = { foo: 1, bar: 2, baz: 3 };
+for (let i=0; i<100; ++i) {
+    o.a = i
+    o.b = i
+    delete o.a
+    delete o.b
+}
 if ($vm.inlineCapacity(o) <= 3)
     throw new Error("There should be inline capacity");
 

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/ChangeLog (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/ChangeLog	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/ChangeLog	2020-02-06 15:11:52 UTC (rev 255947)
@@ -1,3 +1,64 @@
+2020-02-05  Justin Michaud  <[email protected]>
+
+        Deleting a property should not turn structures into uncacheable dictionaries
+        https://bugs.webkit.org/show_bug.cgi?id=206430
+
+        Reviewed by Yusuke Suzuki.
+
+        Right now, deleteProperty/removePropertyTransition causes a structure transition to uncacheable dictionary. Instead, we should allow it to transition to a new regular structure like adding a property does. This means that we have to:
+
+        1) Break the assumption that structure transition offsets increase monotonically
+
+        We add a new flag to tell that a structure has deleted its property, and update materializePropertyTable to use it.
+
+        2) Add a new transition map and transition kind for deletes
+
+        We cache the delete transition. We will not transition back to a previous structure if you add then immediately remove a property.
+
+        3) Find some heuristic for when we should actually transition to uncacheable dictionary.
+
+        Since deleting properties is expected to be rare, we just walk the structure list and count its size on removal. 
+
+        This patch also fixes a related bug in addProperty, where we did not use a GCSafeConcurrentJSLocker, and adds an option to trigger the bug. Finally, we add some helper methods to dollarVM to test.
+
+        This gives a 24x speedup on delete-property-keeps-cacheable-structure.js, and is neutral on delete-property-from-prototype-chain.js (which was already generating code using the inline cache).
+
+        * heap/HeapInlines.h:
+        (JSC::Heap::decrementDeferralDepthAndGCIfNeeded):
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::deleteProperty):
+        * runtime/OptionsList.h:
+        * runtime/PropertyMapHashTable.h:
+        (JSC::PropertyTable::get):
+        (JSC::PropertyTable::add):
+        (JSC::PropertyTable::addDeletedOffset):
+        (JSC::PropertyTable::reinsert):
+        * runtime/Structure.cpp:
+        (JSC::StructureTransitionTable::contains const):
+        (JSC::StructureTransitionTable::get const):
+        (JSC::StructureTransitionTable::add):
+        (JSC::Structure::Structure):
+        (JSC::Structure::materializePropertyTable):
+        (JSC::Structure::addNewPropertyTransition):
+        (JSC::Structure::removePropertyTransition):
+        (JSC::Structure::removePropertyTransitionFromExistingStructure):
+        (JSC::Structure::removeNewPropertyTransition):
+        (JSC::Structure::toUncacheableDictionaryTransition):
+        (JSC::Structure::remove):
+        (JSC::Structure::visitChildren):
+        * runtime/Structure.h:
+        * runtime/StructureInlines.h:
+        (JSC::Structure::forEachPropertyConcurrently):
+        (JSC::Structure::add):
+        (JSC::Structure::remove):
+        (JSC::Structure::removePropertyWithoutTransition):
+        * runtime/StructureTransitionTable.h:
+        (JSC::StructureTransitionTable::Hash::hash):
+        * tools/JSDollarVM.cpp:
+        (JSC::JSDollarVMHelper::functionGetStructureTransitionList):
+        (JSC::functionGetConcurrently):
+        (JSC::JSDollarVM::finishCreation):
+
 2020-02-05  Devin Rousso  <[email protected]>
 
         Web Inspector: Sources: add a special breakpoint for controlling whether `debugger` statements pause

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/heap/HeapInlines.h (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/heap/HeapInlines.h	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/heap/HeapInlines.h	2020-02-06 15:11:52 UTC (rev 255947)
@@ -188,7 +188,7 @@
     ASSERT(!Thread::mayBeGCThread() || m_worldIsStopped);
     m_deferralDepth--;
     
-    if (UNLIKELY(m_didDeferGCWork)) {
+    if (UNLIKELY(m_didDeferGCWork) || Options::forceDidDeferGCWork()) {
         decrementDeferralDepthAndGCIfNeededSlow();
         
         // Here are the possible relationships between m_deferralDepth and m_didDeferGCWork.

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/JSObject.cpp (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/JSObject.cpp	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/JSObject.cpp	2020-02-06 15:11:52 UTC (rev 255947)
@@ -2001,13 +2001,23 @@
     if (propertyIsPresent) {
         if (attributes & PropertyAttribute::DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable)
             return false;
+        DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
 
-        PropertyOffset offset;
+        PropertyOffset offset = invalidOffset;
         if (structure->isUncacheableDictionary())
-            offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { });
-        else
-            thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
+            offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset) { });
+        else {
+            structure = Structure::removePropertyTransition(vm, structure, propertyName, offset, &deferredWatchpointFire);
+            if (thisObject->m_butterfly && !structure->outOfLineCapacity() && !structure->hasIndexingHeader(thisObject)) {
+                thisObject->nukeStructureAndSetButterfly(vm, thisObject->structureID(), nullptr);
+                offset = invalidOffset;
+                ASSERT(structure->maxOffset() == invalidOffset);
+            }
+            thisObject->setStructure(vm, structure);
+        }
 
+        ASSERT(!isValidOffset(structure->get(vm, propertyName, attributes)));
+
         if (offset != invalidOffset)
             thisObject->locationForOffset(offset)->clear();
     }

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/OptionsList.h (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/OptionsList.h	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/OptionsList.h	2020-02-06 15:11:52 UTC (rev 255947)
@@ -347,6 +347,7 @@
     v(Bool, useGC, true, Normal, nullptr) \
     v(Bool, gcAtEnd, false, Normal, "If true, the jsc CLI will do a GC before exiting") \
     v(Bool, forceGCSlowPaths, false, Normal, "If true, we will force all JIT fast allocations down their slow paths.") \
+    v(Bool, forceDidDeferGCWork, false, Normal, "If true, we will force all DeferGC destructions to perform a GC.") \
     v(Unsigned, gcMaxHeapSize, 0, Normal, nullptr) \
     v(Unsigned, forceRAMSize, 0, Normal, nullptr) \
     v(Bool, recordGCPauseTimes, false, Normal, nullptr) \

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/PropertyMapHashTable.h (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/PropertyMapHashTable.h	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/PropertyMapHashTable.h	2020-02-06 15:11:52 UTC (rev 255947)
@@ -169,8 +169,7 @@
     find_iterator find(const KeyType&);
     ValueType* get(const KeyType&);
     // Add a value to the table
-    enum EffectOnPropertyOffset { PropertyOffsetMayChange, PropertyOffsetMustNotChange };
-    std::pair<find_iterator, bool> add(const ValueType& entry, PropertyOffset&, EffectOnPropertyOffset);
+    std::pair<find_iterator, bool> WARN_UNUSED_RETURN add(const ValueType& entry);
     // Remove a value from the table.
     void remove(const find_iterator& iter);
     void remove(const KeyType& key);
@@ -321,6 +320,7 @@
 {
     ASSERT(key);
     ASSERT(key->isAtom() || key->isSymbol());
+    ASSERT(key != PROPERTY_MAP_DELETED_ENTRY_KEY);
 
     if (!m_keyCount)
         return nullptr;
@@ -335,8 +335,10 @@
         unsigned entryIndex = m_index[hash & m_indexMask];
         if (entryIndex == EmptyEntryIndex)
             return nullptr;
-        if (key == table()[entryIndex - 1].key)
+        if (key == table()[entryIndex - 1].key) {
+            ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(table()[entryIndex - 1].offset));
             return &table()[entryIndex - 1];
+        }
 
 #if DUMP_PROPERTYMAP_STATS
         ++propertyMapHashTableStats->numLookupProbing;
@@ -346,14 +348,14 @@
     }
 }
 
-inline std::pair<PropertyTable::find_iterator, bool> PropertyTable::add(const ValueType& entry, PropertyOffset& offset, EffectOnPropertyOffset offsetEffect)
+inline std::pair<PropertyTable::find_iterator, bool> WARN_UNUSED_RETURN PropertyTable::add(const ValueType& entry)
 {
+    ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(entry.offset));
+
     // Look for a value with a matching key already in the array.
     find_iterator iter = find(entry.key);
-    if (iter.first) {
-        RELEASE_ASSERT(iter.first->offset <= offset);
+    if (iter.first)
         return std::make_pair(iter, false);
-    }
 
 #if DUMP_PROPERTYMAP_STATS
     ++propertyMapHashTableStats->numAdds;
@@ -377,11 +379,6 @@
 
     ++m_keyCount;
     
-    if (offsetEffect == PropertyOffsetMayChange)
-        offset = std::max(offset, entry.offset);
-    else
-        RELEASE_ASSERT(offset >= entry.offset);
-    
     return std::make_pair(iter, true);
 }
 
@@ -451,6 +448,7 @@
 {
     if (!m_deletedOffsets)
         m_deletedOffsets = makeUnique<Vector<PropertyOffset>>();
+    ASSERT(!m_deletedOffsets->contains(offset));
     m_deletedOffsets->append(offset);
 }
 

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/Structure.cpp (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/Structure.cpp	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/Structure.cpp	2020-02-06 15:11:52 UTC (rev 255947)
@@ -87,22 +87,22 @@
     m_data = bitwise_cast<intptr_t>(impl) | UsingSingleSlotFlag;
 }
 
-bool StructureTransitionTable::contains(UniquedStringImpl* rep, unsigned attributes) const
+bool StructureTransitionTable::contains(UniquedStringImpl* rep, unsigned attributes, bool isAddition) const
 {
     if (isUsingSingleSlot()) {
         Structure* transition = singleTransition();
-        return transition && transition->m_transitionPropertyName == rep && transition->transitionPropertyAttributes() == attributes;
+        return transition && transition->m_transitionPropertyName == rep && transition->transitionPropertyAttributes() == attributes && transition->isPropertyDeletionTransition() == !isAddition;
     }
-    return map()->get(std::make_pair(rep, attributes));
+    return map()->get(std::make_tuple(rep, attributes, isAddition));
 }
 
-inline Structure* StructureTransitionTable::get(UniquedStringImpl* rep, unsigned attributes) const
+inline Structure* StructureTransitionTable::get(UniquedStringImpl* rep, unsigned attributes, bool isAddition) const
 {
     if (isUsingSingleSlot()) {
         Structure* transition = singleTransition();
-        return (transition && transition->m_transitionPropertyName == rep && transition->transitionPropertyAttributes() == attributes) ? transition : 0;
+        return (transition && transition->m_transitionPropertyName == rep && transition->transitionPropertyAttributes() == attributes && transition->isPropertyDeletionTransition() == !isAddition) ? transition : 0;
     }
-    return map()->get(std::make_pair(rep, attributes));
+    return map()->get(std::make_tuple(rep, attributes, isAddition));
 }
 
 void StructureTransitionTable::add(VM& vm, Structure* structure)
@@ -127,7 +127,7 @@
     // Newer versions of the STL have an std::make_pair function that takes rvalue references.
     // When either of the parameters are bitfields, the C++ compiler will try to bind them as lvalues, which is invalid. To work around this, use unary "+" to make the parameter an rvalue.
     // See https://bugs.webkit.org/show_bug.cgi?id=59261 for more details
-    map()->set(std::make_pair(structure->m_transitionPropertyName.get(), +structure->transitionPropertyAttributes()), structure);
+    map()->set(std::make_tuple(structure->m_transitionPropertyName.get(), +structure->transitionPropertyAttributes(), !structure->isPropertyDeletionTransition()), structure);
 }
 
 void Structure::dumpStatistics()
@@ -200,7 +200,8 @@
     setStaticPropertiesReified(false);
     setTransitionWatchpointIsLikelyToBeFired(false);
     setHasBeenDictionary(false);
-    setIsAddingPropertyForTransition(false);
+    setProtectPropertyTableWhileTransitioning(false);
+    setIsPropertyDeletionTransition(false);
     setTransitionOffset(vm, invalidOffset);
     setMaxOffset(vm, invalidOffset);
  
@@ -236,7 +237,8 @@
     setStaticPropertiesReified(false);
     setTransitionWatchpointIsLikelyToBeFired(false);
     setHasBeenDictionary(false);
-    setIsAddingPropertyForTransition(false);
+    setProtectPropertyTableWhileTransitioning(false);
+    setIsPropertyDeletionTransition(false);
     setTransitionOffset(vm, invalidOffset);
     setMaxOffset(vm, invalidOffset);
  
@@ -272,7 +274,8 @@
     setDidTransition(true);
     setStaticPropertiesReified(previous->staticPropertiesReified());
     setHasBeenDictionary(previous->hasBeenDictionary());
-    setIsAddingPropertyForTransition(false);
+    setProtectPropertyTableWhileTransitioning(false);
+    setIsPropertyDeletionTransition(false);
     setTransitionOffset(vm, invalidOffset);
     setMaxOffset(vm, invalidOffset);
  
@@ -356,7 +359,7 @@
 PropertyTable* Structure::materializePropertyTable(VM& vm, bool setPropertyTable)
 {
     ASSERT(structure(vm)->classInfo() == info());
-    ASSERT(!isAddingPropertyForTransition());
+    ASSERT(!protectPropertyTableWhileTransitioning());
     
     DeferGC deferGC(vm.heap);
     
@@ -384,10 +387,19 @@
         structure = structures[i];
         if (!structure->m_transitionPropertyName)
             continue;
-        ASSERT(i == structures.size() - 1 || structure->maxOffset() > structures[i + 1]->maxOffset());
+        if (structure->isPropertyDeletionTransition()) {
+            auto item = table->find(structure->m_transitionPropertyName.get());
+            ASSERT(item.first);
+            table->remove(item);
+            table->addDeletedOffset(structure->transitionOffset());
+            continue;
+        }
         PropertyMapEntry entry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes());
-        auto maxOffset = this->maxOffset();
-        table->add(entry, maxOffset, PropertyTable::PropertyOffsetMustNotChange);
+        auto nextOffset = table->nextOffset(structure->inlineCapacity());
+        ASSERT_UNUSED(nextOffset, nextOffset == structure->transitionOffset());
+        auto result = table->add(entry);
+        ASSERT_UNUSED(result, result.second);
+        ASSERT_UNUSED(result, result.first.first->offset == nextOffset);
     }
     
     checkOffsetConsistency(
@@ -410,7 +422,8 @@
     ASSERT(!structure->isDictionary());
     ASSERT(structure->isObject());
 
-    if (Structure* existingTransition = structure->m_transitionTable.get(uid, attributes)) {
+    constexpr bool isAddition = true;
+    if (Structure* existingTransition = structure->m_transitionTable.get(uid, attributes, isAddition)) {
         validateOffset(existingTransition->transitionOffset(), existingTransition->inlineCapacity());
         offset = existingTransition->transitionOffset();
         return existingTransition;
@@ -484,7 +497,6 @@
         Structure* transition = toCacheableDictionaryTransition(vm, structure, deferred);
         ASSERT(structure != transition);
         offset = transition->add(vm, propertyName, attributes);
-        transition->setTransitionOffset(vm, offset);
         return transition;
     }
     
@@ -500,10 +512,10 @@
     // Holding the lock ensures that we either do this before the GC starts scanning the structure, in
     // which case the GC will not blow the table away, or we do it after the GC already ran in which
     // case all is well.  If it wasn't for the lock, the GC would have TOCTOU: if could read
-    // isAddingPropertyForTransition before we set it to true, and then blow the table away after.
+    // protectPropertyTableWhileTransitioning before we set it to true, and then blow the table away after.
     {
         ConcurrentJSLocker locker(transition->m_lock);
-        transition->setIsAddingPropertyForTransition(true);
+        transition->setProtectPropertyTableWhileTransitioning(true);
     }
 
     transition->m_blob.setIndexingModeIncludingHistory(structure->indexingModeIncludingHistory() & ~CopyOnWrite);
@@ -518,12 +530,11 @@
     // Now that everything is fine with the new structure's bookkeeping, the GC is free to blow the
     // table away if it wants. We can now rebuild it fine.
     WTF::storeStoreFence();
-    transition->setIsAddingPropertyForTransition(false);
+    transition->setProtectPropertyTableWhileTransitioning(false);
 
     checkOffset(transition->transitionOffset(), transition->inlineCapacity());
     {
-        ConcurrentJSLocker locker(structure->m_lock);
-        DeferGC deferGC(vm.heap);
+        GCSafeConcurrentJSLocker locker(structure->m_lock, vm.heap);
         structure->m_transitionTable.add(vm, transition);
     }
     transition->checkOffsetConsistency();
@@ -531,27 +542,85 @@
     return transition;
 }
 
-Structure* Structure::removePropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset)
+Structure* Structure::removePropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset, DeferredStructureTransitionWatchpointFire* deferred)
 {
-    // NOTE: There are some good reasons why this goes directly to uncacheable dictionary rather than
-    // caching the removal. We can fix all of these things, but we must remember to do so, if we ever try
-    // to optimize this case.
-    //
-    // - Cached transitions usually steal the property table, and assume that this is possible because they
-    //   can just rebuild the table by looking at past transitions. That code assumes that the table only
-    //   grew and never shrank. To support removals, we'd have to change the property table materialization
-    //   code to handle deletions. Also, we have logic to get the list of properties on a structure that
-    //   lacks a property table by just looking back through the set of transitions since the last
-    //   structure that had a pinned table. That logic would also have to be changed to handle cached
-    //   removals.
-    //
+    Structure* newStructure = removePropertyTransitionFromExistingStructure(
+        vm, structure, propertyName, offset, deferred);
+    if (newStructure)
+        return newStructure;
+
+    return removeNewPropertyTransition(
+        vm, structure, propertyName, offset, deferred);
+}
+
+Structure* Structure::removePropertyTransitionFromExistingStructure(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset, DeferredStructureTransitionWatchpointFire*)
+{
+    ASSERT(!isCompilationThread());
     ASSERT(!structure->isUncacheableDictionary());
+    ASSERT(structure->isObject());
 
-    Structure* transition = toUncacheableDictionaryTransition(vm, structure);
+    unsigned attributes;
+    structure->get(vm, propertyName, attributes);
 
-    offset = transition->remove(propertyName);
+    constexpr bool isAddition = false;
+    if (Structure* existingTransition = structure->m_transitionTable.get(propertyName.uid(), attributes, isAddition)) {
+        validateOffset(existingTransition->transitionOffset(), existingTransition->inlineCapacity());
+        offset = existingTransition->transitionOffset();
+        return existingTransition;
+    }
 
+    return nullptr;
+}
+
+Structure* Structure::removeNewPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset, DeferredStructureTransitionWatchpointFire* deferred)
+{
+    ASSERT(!structure->isUncacheableDictionary());
+    ASSERT(structure->isObject());
+    ASSERT(!Structure::removePropertyTransitionFromExistingStructure(vm, structure, propertyName, offset, deferred));
+
+    int transitionCount = 0;
+    for (auto* s = structure; s && transitionCount <= s_maxTransitionLength; s = s->previousID())
+        ++transitionCount;
+
+    if (transitionCount > s_maxTransitionLength) {
+        ASSERT(!isCopyOnWrite(structure->indexingMode()));
+        Structure* transition = toUncacheableDictionaryTransition(vm, structure, deferred);
+        ASSERT(structure != transition);
+        offset = transition->remove(vm, propertyName);
+        return transition;
+    }
+
+    Structure* transition = create(vm, structure, deferred);
+    transition->m_cachedPrototypeChain.setMayBeNull(vm, transition, structure->m_cachedPrototypeChain.get());
+
+    // While we are deleting the property, we need to make sure the table is not cleared.
+    {
+        ConcurrentJSLocker locker(transition->m_lock);
+        transition->setProtectPropertyTableWhileTransitioning(true);
+    }
+
+    transition->m_blob.setIndexingModeIncludingHistory(structure->indexingModeIncludingHistory() & ~CopyOnWrite);
+    transition->m_transitionPropertyName = propertyName.uid();
+    transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm));
+    transition->setMaxOffset(vm, structure->maxOffset());
+    transition->setIsPropertyDeletionTransition(true);
+
+    offset = transition->remove(vm, propertyName);
+    ASSERT(offset != invalidOffset);
+    transition->setTransitionOffset(vm, offset);
+
+    // Now that everything is fine with the new structure's bookkeeping, the GC is free to blow the
+    // table away if it wants. We can now rebuild it fine.
+    WTF::storeStoreFence();
+    transition->setProtectPropertyTableWhileTransitioning(false);
+
+    checkOffset(transition->transitionOffset(), transition->inlineCapacity());
+    {
+        GCSafeConcurrentJSLocker locker(structure->m_lock, vm.heap);
+        structure->m_transitionTable.add(vm, transition);
+    }
     transition->checkOffsetConsistency();
+    structure->checkOffsetConsistency();
     return transition;
 }
 
@@ -614,9 +683,9 @@
     return toDictionaryTransition(vm, structure, CachedDictionaryKind, deferred);
 }
 
-Structure* Structure::toUncacheableDictionaryTransition(VM& vm, Structure* structure)
+Structure* Structure::toUncacheableDictionaryTransition(VM& vm, Structure* structure, DeferredStructureTransitionWatchpointFire* deferred)
 {
-    return toDictionaryTransition(vm, structure, UncachedDictionaryKind);
+    return toDictionaryTransition(vm, structure, UncachedDictionaryKind, deferred);
 }
 
 Structure* Structure::sealTransition(VM& vm, Structure* structure)
@@ -655,7 +724,8 @@
     IndexingType indexingModeIncludingHistory = newIndexingType(structure->indexingModeIncludingHistory(), transitionKind);
     
     Structure* existingTransition;
-    if (!structure->isDictionary() && (existingTransition = structure->m_transitionTable.get(0, attributes))) {
+    constexpr bool isAddition = true;
+    if (!structure->isDictionary() && (existingTransition = structure->m_transitionTable.get(0, attributes, isAddition))) {
         ASSERT(existingTransition->transitionPropertyAttributes() == attributes);
         ASSERT(existingTransition->indexingModeIncludingHistory() == indexingModeIncludingHistory);
         return existingTransition;
@@ -977,9 +1047,11 @@
         });
 }
 
-PropertyOffset Structure::remove(PropertyName propertyName)
+PropertyOffset Structure::remove(VM& vm, PropertyName propertyName)
 {
-    return remove(propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { });
+    return remove<ShouldPin::No>(vm, propertyName, [this, &vm] (const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset newMaxOffset) {
+        setMaxOffset(vm, newMaxOffset);
+    });
 }
 
 void Structure::getPropertyNamesFromStructure(VM& vm, PropertyNameArray& propertyNames, EnumerationMode mode)
@@ -1059,7 +1131,7 @@
     }
     visitor.append(thisObject->m_previousOrRareData);
 
-    if (thisObject->isPinnedPropertyTable() || thisObject->isAddingPropertyForTransition()) {
+    if (thisObject->isPinnedPropertyTable() || thisObject->protectPropertyTableWhileTransitioning()) {
         // NOTE: This can interleave in pin(), in which case it may see a null property table.
         // That's fine, because then the barrier will fire and we will scan this again.
         visitor.append(thisObject->m_propertyTableUnsafe);

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/Structure.h (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/Structure.h	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/Structure.h	2020-02-06 15:11:52 UTC (rev 255947)
@@ -189,11 +189,13 @@
     JS_EXPORT_PRIVATE static Structure* addNewPropertyTransition(VM&, Structure*, PropertyName, unsigned attributes, PropertyOffset&, PutPropertySlot::Context = PutPropertySlot::UnknownContext, DeferredStructureTransitionWatchpointFire* = nullptr);
     static Structure* addPropertyTransitionToExistingStructureConcurrently(Structure*, UniquedStringImpl* uid, unsigned attributes, PropertyOffset&);
     JS_EXPORT_PRIVATE static Structure* addPropertyTransitionToExistingStructure(Structure*, PropertyName, unsigned attributes, PropertyOffset&);
-    static Structure* removePropertyTransition(VM&, Structure*, PropertyName, PropertyOffset&);
+    static Structure* removeNewPropertyTransition(VM&, Structure*, PropertyName, PropertyOffset&, DeferredStructureTransitionWatchpointFire* = nullptr);
+    static Structure* removePropertyTransition(VM&, Structure*, PropertyName, PropertyOffset&, DeferredStructureTransitionWatchpointFire* = nullptr);
+    static Structure* removePropertyTransitionFromExistingStructure(VM&, Structure*, PropertyName, PropertyOffset&, DeferredStructureTransitionWatchpointFire* = nullptr);
     static Structure* changePrototypeTransition(VM&, Structure*, JSValue prototype, DeferredStructureTransitionWatchpointFire&);
     JS_EXPORT_PRIVATE static Structure* attributeChangeTransition(VM&, Structure*, PropertyName, unsigned attributes);
     JS_EXPORT_PRIVATE static Structure* toCacheableDictionaryTransition(VM&, Structure*, DeferredStructureTransitionWatchpointFire* = nullptr);
-    static Structure* toUncacheableDictionaryTransition(VM&, Structure*);
+    static Structure* toUncacheableDictionaryTransition(VM&, Structure*, DeferredStructureTransitionWatchpointFire* = nullptr);
     JS_EXPORT_PRIVATE static Structure* sealTransition(VM&, Structure*);
     JS_EXPORT_PRIVATE static Structure* freezeTransition(VM&, Structure*);
     static Structure* preventExtensionsTransition(VM&, Structure*);
@@ -450,10 +452,6 @@
     {
         return std::min<unsigned>(maxOffset() + 1, m_inlineCapacity);
     }
-    unsigned totalStorageSize() const
-    {
-        return numberOfSlotsForMaxOffset(maxOffset(), m_inlineCapacity);
-    }
     unsigned totalStorageCapacity() const
     {
         ASSERT(structure()->classInfo() == info());
@@ -701,8 +699,9 @@
     DEFINE_BITFIELD(bool, didWatchInternalProperties, DidWatchInternalProperties, 1, 25);
     DEFINE_BITFIELD(bool, transitionWatchpointIsLikelyToBeFired, TransitionWatchpointIsLikelyToBeFired, 1, 26);
     DEFINE_BITFIELD(bool, hasBeenDictionary, HasBeenDictionary, 1, 27);
-    DEFINE_BITFIELD(bool, isAddingPropertyForTransition, IsAddingPropertyForTransition, 1, 28);
+    DEFINE_BITFIELD(bool, protectPropertyTableWhileTransitioning, ProtectPropertyTableWhileTransitioning, 1, 28);
     DEFINE_BITFIELD(bool, hasUnderscoreProtoPropertyExcludingOriginalProto, HasUnderscoreProtoPropertyExcludingOriginalProto, 1, 29);
+    DEFINE_BITFIELD(bool, isPropertyDeletionTransition, IsPropertyDeletionTransition, 1, 30);
 
 private:
     friend class LLIntOffsetsExtractor;
@@ -727,9 +726,9 @@
     template<ShouldPin, typename Func>
     PropertyOffset add(VM&, PropertyName, unsigned attributes, const Func&);
     PropertyOffset add(VM&, PropertyName, unsigned attributes);
-    template<typename Func>
-    PropertyOffset remove(PropertyName, const Func&);
-    PropertyOffset remove(PropertyName);
+    template<ShouldPin, typename Func>
+    PropertyOffset remove(VM&, PropertyName, const Func&);
+    PropertyOffset remove(VM&, PropertyName);
 
     void checkConsistency();
 
@@ -841,6 +840,7 @@
     TinyBloomFilter m_seenProperties;
 
     friend class VMInspector;
+    friend class JSDollarVMHelper;
 };
 
 } // namespace JSC

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/StructureInlines.h (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/StructureInlines.h	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/StructureInlines.h	2020-02-06 15:11:52 UTC (rev 255947)
@@ -163,29 +163,41 @@
 void Structure::forEachPropertyConcurrently(const Functor& functor)
 {
     Vector<Structure*, 8> structures;
-    Structure* structure;
+    Structure* tableStructure;
     PropertyTable* table;
     
-    findStructuresAndMapForMaterialization(structures, structure, table);
+    findStructuresAndMapForMaterialization(structures, tableStructure, table);
+
+    HashSet<UniquedStringImpl*> seenProperties;
+
+    for (auto* structure : structures) {
+        if (!structure->m_transitionPropertyName || seenProperties.contains(structure->m_transitionPropertyName.get()))
+            continue;
+
+        seenProperties.add(structure->m_transitionPropertyName.get());
+
+        if (structure->isPropertyDeletionTransition())
+            continue;
+
+        if (!functor(PropertyMapEntry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes()))) {
+            if (table)
+                tableStructure->m_lock.unlock();
+            return;
+        }
+    }
     
     if (table) {
         for (auto& entry : *table) {
+            if (seenProperties.contains(entry.key))
+                continue;
+
             if (!functor(entry)) {
-                structure->m_lock.unlock();
+                tableStructure->m_lock.unlock();
                 return;
             }
         }
-        structure->m_lock.unlock();
+        tableStructure->m_lock.unlock();
     }
-    
-    for (unsigned i = structures.size(); i--;) {
-        structure = structures[i];
-        if (!structure->m_transitionPropertyName)
-            continue;
-        
-        if (!functor(PropertyMapEntry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes())))
-            return;
-    }
 }
 
 template<typename Functor>
@@ -451,12 +463,13 @@
     PropertyOffset newOffset = table->nextOffset(m_inlineCapacity);
 
     m_propertyHash = m_propertyHash ^ rep->existingSymbolAwareHash();
+    m_seenProperties.add(bitwise_cast<uintptr_t>(rep));
 
-    m_seenProperties.add(bitwise_cast<uintptr_t>(rep));
+    auto result = table->add(PropertyMapEntry(rep, newOffset, attributes));
+    ASSERT_UNUSED(result, result.second);
+    ASSERT_UNUSED(result, result.first.first->offset == newOffset);
+    auto newMaxOffset = std::max(newOffset, maxOffset());
     
-    PropertyOffset newMaxOffset = maxOffset();
-    table->add(PropertyMapEntry(rep, newOffset, attributes), newMaxOffset, PropertyTable::PropertyOffsetMayChange);
-    
     func(locker, newOffset, newMaxOffset);
     
     ASSERT(maxOffset() == newMaxOffset);
@@ -465,25 +478,32 @@
     return newOffset;
 }
 
-template<typename Func>
-inline PropertyOffset Structure::remove(PropertyName propertyName, const Func& func)
+template<Structure::ShouldPin shouldPin, typename Func>
+inline PropertyOffset Structure::remove(VM& vm, PropertyName propertyName, const Func& func)
 {
-    ConcurrentJSLocker locker(m_lock);
-    
+    PropertyTable* table = ensurePropertyTable(vm);
+    GCSafeConcurrentJSLocker locker(m_lock, vm.heap);
+
+    switch (shouldPin) {
+    case ShouldPin::Yes:
+        pin(locker, vm, table);
+        break;
+    case ShouldPin::No:
+        setPropertyTable(vm, table);
+        break;
+    }
+
+    ASSERT(JSC::isValidOffset(get(vm, propertyName)));
+
     checkConsistency();
 
     auto rep = propertyName.uid();
-    
-    // We ONLY remove from uncacheable dictionaries, which will have a pinned property table.
-    // The only way for them not to have a table is if they are empty.
-    PropertyTable* table = propertyTableOrNull();
 
-    if (!table)
-        return invalidOffset;
-
     PropertyTable::find_iterator position = table->find(rep);
     if (!position.first)
         return invalidOffset;
+
+    setIsQuickPropertyAccessAllowedForEnumeration(false);
     
     PropertyOffset offset = position.first->offset;
 
@@ -490,9 +510,14 @@
     table->remove(position);
     table->addDeletedOffset(offset);
 
+    PropertyOffset newMaxOffset = maxOffset();
+
+    func(locker, offset, newMaxOffset);
+
+    ASSERT(maxOffset() == newMaxOffset);
+    ASSERT(!JSC::isValidOffset(get(vm, propertyName)));
+
     checkConsistency();
-
-    func(locker, offset);
     return offset;
 }
 
@@ -503,13 +528,13 @@
 }
 
 template<typename Func>
-inline PropertyOffset Structure::removePropertyWithoutTransition(VM&, PropertyName propertyName, const Func& func)
+inline PropertyOffset Structure::removePropertyWithoutTransition(VM& vm, PropertyName propertyName, const Func& func)
 {
     ASSERT(isUncacheableDictionary());
     ASSERT(isPinnedPropertyTable());
     ASSERT(propertyTableOrNull());
     
-    return remove(propertyName, func);
+    return remove<ShouldPin::Yes>(vm, propertyName, func);
 }
 
 ALWAYS_INLINE void Structure::setPrototypeWithoutTransition(VM& vm, JSValue prototype)

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/StructureTransitionTable.h (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/StructureTransitionTable.h	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/runtime/StructureTransitionTable.h	2020-02-06 15:11:52 UTC (rev 255947)
@@ -144,11 +144,11 @@
 
     
     struct Hash {
-        typedef std::pair<UniquedStringImpl*, unsigned> Key;
+        typedef std::tuple<UniquedStringImpl*, unsigned, bool> Key;
         
         static unsigned hash(const Key& p)
         {
-            return PtrHash<UniquedStringImpl*>::hash(p.first) + p.second;
+            return PtrHash<UniquedStringImpl*>::hash(std::get<0>(p)) + std::get<1>(p);
         }
 
         static bool equal(const Key& a, const Key& b)
@@ -181,8 +181,8 @@
     }
 
     void add(VM&, Structure*);
-    bool contains(UniquedStringImpl*, unsigned attributes) const;
-    Structure* get(UniquedStringImpl*, unsigned attributes) const;
+    bool contains(UniquedStringImpl*, unsigned attributes, bool isAddition) const;
+    Structure* get(UniquedStringImpl*, unsigned attributes, bool isAddition) const;
 
 private:
     friend class SingleSlotTransitionWeakOwner;

Modified: releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/tools/JSDollarVM.cpp (255946 => 255947)


--- releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/tools/JSDollarVM.cpp	2020-02-06 15:11:43 UTC (rev 255946)
+++ releases/WebKitGTK/webkit-2.28/Source/_javascript_Core/tools/JSDollarVM.cpp	2020-02-06 15:11:52 UTC (rev 255947)
@@ -81,6 +81,8 @@
 
     void updateVMStackLimits() { return m_vm.updateStackLimits(); };
 
+    static EncodedJSValue JSC_HOST_CALL functionGetStructureTransitionList(JSGlobalObject*, CallFrame*);
+
 private:
     VM& m_vm;
 };
@@ -2769,6 +2771,63 @@
     return JSValue::encode(jsString(vm, String::adopt(WTFMove(buffer))));
 }
 
+EncodedJSValue JSC_HOST_CALL JSDollarVMHelper::functionGetStructureTransitionList(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    DollarVMAssertScope assertScope;
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+    JSObject* obj = callFrame->argument(0).toObject(globalObject);
+    RETURN_IF_EXCEPTION(scope, { });
+    if (!obj)
+        return JSValue::encode(jsNull());
+    Vector<Structure*, 8> structures;
+
+    for (auto* structure = obj->structure(); structure; structure = structure->previousID())
+        structures.append(structure);
+
+    JSArray* result = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    for (size_t i = 0; i < structures.size(); ++i) {
+        auto* structure = structures[structures.size() - i - 1];
+        result->push(globalObject, JSValue(structure->id()));
+        RETURN_IF_EXCEPTION(scope, { });
+        result->push(globalObject, JSValue(structure->transitionOffset()));
+        RETURN_IF_EXCEPTION(scope, { });
+        result->push(globalObject, JSValue(structure->maxOffset()));
+        RETURN_IF_EXCEPTION(scope, { });
+        if (structure->m_transitionPropertyName)
+            result->push(globalObject, jsString(vm, String(*structure->m_transitionPropertyName)));
+        else
+            result->push(globalObject, jsNull());
+        RETURN_IF_EXCEPTION(scope, { });
+        result->push(globalObject, JSValue(structure->isPropertyDeletionTransition()));
+        RETURN_IF_EXCEPTION(scope, { });
+    }
+
+    return JSValue::encode(result);
+}
+
+static EncodedJSValue JSC_HOST_CALL functionGetConcurrently(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    DollarVMAssertScope assertScope;
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+    JSObject* obj = callFrame->argument(0).toObject(globalObject);
+    RETURN_IF_EXCEPTION(scope, { });
+    if (!obj)
+        return JSValue::encode(jsNull());
+    String property = callFrame->argument(1).toWTFString(globalObject);
+    RETURN_IF_EXCEPTION(scope, { });
+    auto name = PropertyName(Identifier::fromString(vm, property));
+    auto offset = obj->structure()->getConcurrently(name.uid());
+    if (offset != invalidOffset)
+        ASSERT(JSValue::encode(obj->getDirect(offset)));
+    JSValue result = JSValue(offset != invalidOffset);
+    RETURN_IF_EXCEPTION(scope, { });
+    return JSValue::encode(result);
+}
+
 void JSDollarVM::finishCreation(VM& vm)
 {
     DollarVMAssertScope assertScope;
@@ -2897,6 +2956,9 @@
     addFunction(vm, "isWasmSupported", functionIsWasmSupported, 0);
     addFunction(vm, "make16BitStringIfPossible", functionMake16BitStringIfPossible, 1);
 
+    addFunction(vm, "getStructureTransitionList", JSDollarVMHelper::functionGetStructureTransitionList, 1);
+    addFunction(vm, "getConcurrently", functionGetConcurrently, 2);
+
     m_objectDoingSideEffectPutWithoutCorrectSlotStatusStructure.set(vm, this, ObjectDoingSideEffectPutWithoutCorrectSlotStatus::createStructure(vm, globalObject, jsNull()));
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to