[JSC] Optimize Object.keys by caching own keys results in StructureRareData
authoryusukesuzuki@slowstart.org <yusukesuzuki@slowstart.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 13 Dec 2018 07:13:38 +0000 (07:13 +0000)
committeryusukesuzuki@slowstart.org <yusukesuzuki@slowstart.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 13 Dec 2018 07:13:38 +0000 (07:13 +0000)
https://bugs.webkit.org/show_bug.cgi?id=190047

Reviewed by Keith Miller.

JSTests:

* stress/object-keys-cached-zero.js: Added.
(shouldBe):
(test):
* stress/object-keys-changed-attribute.js: Added.
(shouldBe):
(test):
* stress/object-keys-changed-index.js: Added.
(shouldBe):
(test):
* stress/object-keys-changed.js: Added.
(shouldBe):
(test):
* stress/object-keys-indexed-non-cache.js: Added.
(shouldBe):
(test):
* stress/object-keys-overrides-get-property-names.js: Added.
(shouldBe):
(test):
(noInline):

Source/JavaScriptCore:

Object.keys is one of the most frequently used function in web-tooling-benchmarks (WTB).
Object.keys is dominant in lebab of WTB, and frequently called in babel and others.
Since our Structure knows the shape of JSObject, we can cache the result of Object.keys
in Structure (StructureRareData) as we cache JSPropertyNameEnumerator in StructureRareData.

This patch caches the result of Object.keys in StructureRareData. The cached array is created
as JSImmutableButterfly. And Object.keys creates CoW from this data. Currently, the lifetime
strategy of this JSImmutableButterfly is the same to cached JSPropertyNameEnumerator. It is
referenced from Structure, and collected when Structure is collected.

This improves several benchmarks in SixSpeed.

                                baseline                  patched

    object-assign.es5      350.1710+-3.6303     ^    226.0368+-4.7558        ^ definitely 1.5492x faster
    for-of-object.es6      269.1941+-3.3430     ^    127.9317+-2.3875        ^ definitely 2.1042x faster

And it improves WTB lebab by 11.8%.

    Before: lebab:  6.10 runs/s
    After:  lebab:  6.82 runs/s

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNode.cpp:
(JSC::DFG::Node::convertToNewArrayBuffer):
* dfg/DFGNode.h:
* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileObjectKeys):
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLAbstractHeapRepository.h:
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileObjectKeys):
* runtime/Intrinsic.cpp:
(JSC::intrinsicName):
* runtime/Intrinsic.h:
* runtime/JSImmutableButterfly.h:
(JSC::JSImmutableButterfly::createSentinel):
* runtime/ObjectConstructor.cpp:
(JSC::ownPropertyKeys):
* runtime/Structure.cpp:
(JSC::Structure::canCachePropertyNameEnumerator const):
* runtime/Structure.h:
* runtime/StructureInlines.h:
(JSC::Structure::setCachedOwnKeys):
(JSC::Structure::cachedOwnKeys const):
(JSC::Structure::canCacheOwnKeys const):
* runtime/StructureRareData.cpp:
(JSC::StructureRareData::visitChildren):
(JSC::StructureRareData::cachedPropertyNameEnumerator const): Deleted.
(JSC::StructureRareData::setCachedPropertyNameEnumerator): Deleted.
* runtime/StructureRareData.h:
* runtime/StructureRareDataInlines.h:
(JSC::StructureRareData::cachedPropertyNameEnumerator const):
(JSC::StructureRareData::setCachedPropertyNameEnumerator):
(JSC::StructureRareData::cachedOwnKeys const):
(JSC::StructureRareData::cachedOwnKeysConcurrently const):
(JSC::StructureRareData::setCachedOwnKeys):
(JSC::StructureRareData::previousID const): Deleted.
* runtime/VM.cpp:
(JSC::VM::VM):
* runtime/VM.h:

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

40 files changed:
JSTests/ChangeLog
JSTests/stress/object-keys-cached-zero.js [new file with mode: 0644]
JSTests/stress/object-keys-changed-attribute.js [new file with mode: 0644]
JSTests/stress/object-keys-changed-index.js [new file with mode: 0644]
JSTests/stress/object-keys-changed.js [new file with mode: 0644]
JSTests/stress/object-keys-indexed-non-cache.js [new file with mode: 0644]
JSTests/stress/object-keys-overrides-get-property-names.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNode.cpp
Source/JavaScriptCore/dfg/DFGNode.h
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGOperations.cpp
Source/JavaScriptCore/dfg/DFGOperations.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/runtime/Intrinsic.cpp
Source/JavaScriptCore/runtime/Intrinsic.h
Source/JavaScriptCore/runtime/JSImmutableButterfly.h
Source/JavaScriptCore/runtime/ObjectConstructor.cpp
Source/JavaScriptCore/runtime/Structure.cpp
Source/JavaScriptCore/runtime/Structure.h
Source/JavaScriptCore/runtime/StructureInlines.h
Source/JavaScriptCore/runtime/StructureRareData.cpp
Source/JavaScriptCore/runtime/StructureRareData.h
Source/JavaScriptCore/runtime/StructureRareDataInlines.h
Source/JavaScriptCore/runtime/VM.cpp
Source/JavaScriptCore/runtime/VM.h

index 2302345..c183857 100644 (file)
@@ -1,3 +1,30 @@
+2018-12-10  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
+
+        [JSC] Optimize Object.keys by caching own keys results in StructureRareData
+        https://bugs.webkit.org/show_bug.cgi?id=190047
+
+        Reviewed by Keith Miller.
+
+        * stress/object-keys-cached-zero.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-keys-changed-attribute.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-keys-changed-index.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-keys-changed.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-keys-indexed-non-cache.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-keys-overrides-get-property-names.js: Added.
+        (shouldBe):
+        (test):
+        (noInline):
+
 2018-12-12  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
 
         [DFG][FTL] Add NewSymbol
diff --git a/JSTests/stress/object-keys-cached-zero.js b/JSTests/stress/object-keys-cached-zero.js
new file mode 100644 (file)
index 0000000..f7cc96a
--- /dev/null
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(object)
+{
+    return Object.keys(object);
+}
+noInline(test);
+
+var object = {};
+for (var i = 0; i < 1e6; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 0);
+    shouldBe(result[0], undefined);
+    result[0] = i;
+    shouldBe(result.length, 1);
+    shouldBe(result[0], i);
+}
diff --git a/JSTests/stress/object-keys-changed-attribute.js b/JSTests/stress/object-keys-changed-attribute.js
new file mode 100644 (file)
index 0000000..c3f143e
--- /dev/null
@@ -0,0 +1,28 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(object)
+{
+    return Object.keys(object);
+}
+noInline(test);
+
+var object = { Cocoa: 42 };
+for (var i = 0; i < 1e6; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 1);
+    shouldBe(result[0], 'Cocoa');
+}
+
+Reflect.defineProperty(object, 'Cocoa', {
+    enumerable: false
+});
+
+for (var i = 0; i < 1e6; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 0);
+    shouldBe(result[0], undefined);
+}
diff --git a/JSTests/stress/object-keys-changed-index.js b/JSTests/stress/object-keys-changed-index.js
new file mode 100644 (file)
index 0000000..423e092
--- /dev/null
@@ -0,0 +1,28 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(object)
+{
+    return Object.keys(object);
+}
+noInline(test);
+
+var object = {};
+for (var i = 0; i < 1e6; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 0);
+    shouldBe(result[0], undefined);
+    result[0] = i;
+    shouldBe(result.length, 1);
+    shouldBe(result[0], i);
+}
+
+object[0] = 42;
+for (var i = 0; i < 1e6; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 1);
+    shouldBe(result[0], '0');
+}
diff --git a/JSTests/stress/object-keys-changed.js b/JSTests/stress/object-keys-changed.js
new file mode 100644 (file)
index 0000000..1d3a305
--- /dev/null
@@ -0,0 +1,28 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(object)
+{
+    return Object.keys(object);
+}
+noInline(test);
+
+var object = {};
+for (var i = 0; i < 1e6; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 0);
+    shouldBe(result[0], undefined);
+    result[0] = i;
+    shouldBe(result.length, 1);
+    shouldBe(result[0], i);
+}
+
+object.Cocoa = 42;
+for (var i = 0; i < 1e6; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 1);
+    shouldBe(result[0], 'Cocoa');
+}
diff --git a/JSTests/stress/object-keys-indexed-non-cache.js b/JSTests/stress/object-keys-indexed-non-cache.js
new file mode 100644 (file)
index 0000000..09c9cf4
--- /dev/null
@@ -0,0 +1,25 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(object)
+{
+    return Object.keys(object);
+}
+noInline(test);
+
+var object = {0: 42};
+for (var i = 0; i < 1e3; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 1);
+    shouldBe(result[0], '0');
+}
+object[1] = 44;
+for (var i = 0; i < 1e3; ++i) {
+    var result = test(object);
+    shouldBe(result.length, 2);
+    shouldBe(result[0], '0');
+    shouldBe(result[1], '1');
+}
diff --git a/JSTests/stress/object-keys-overrides-get-property-names.js b/JSTests/stress/object-keys-overrides-get-property-names.js
new file mode 100644 (file)
index 0000000..530d8bb
--- /dev/null
@@ -0,0 +1,57 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(object)
+{
+    return Object.keys(object);
+}
+noInline(test);
+
+{
+    let object = new String("Cocoa");
+    for (let i = 0; i < 1e3; ++i) {
+        let result = test(object);
+        shouldBe(result.length, 5);
+        shouldBe(result[0], '0');
+        shouldBe(result[1], '1');
+        shouldBe(result[2], '2');
+        shouldBe(result[3], '3');
+        shouldBe(result[4], '4');
+    }
+
+    object.Cocoa = 42;
+    let result = test(object);
+    shouldBe(result.length, 6);
+    shouldBe(result[0], '0');
+    shouldBe(result[1], '1');
+    shouldBe(result[2], '2');
+    shouldBe(result[3], '3');
+    shouldBe(result[4], '4');
+    shouldBe(result[5], 'Cocoa');
+}
+
+{
+    let object = new String("Cocoa");
+    for (let i = 0; i < 1e3; ++i) {
+        let result = test(object);
+        shouldBe(result.length, 5);
+        shouldBe(result[0], '0');
+        shouldBe(result[1], '1');
+        shouldBe(result[2], '2');
+        shouldBe(result[3], '3');
+        shouldBe(result[4], '4');
+    }
+
+    object[8] = 42;
+    let result = test(object);
+    shouldBe(result.length, 6);
+    shouldBe(result[0], '0');
+    shouldBe(result[1], '1');
+    shouldBe(result[2], '2');
+    shouldBe(result[3], '3');
+    shouldBe(result[4], '4');
+    shouldBe(result[5], '8');
+}
index 3756656..875a52a 100644 (file)
@@ -1,3 +1,96 @@
+2018-12-10  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
+
+        [JSC] Optimize Object.keys by caching own keys results in StructureRareData
+        https://bugs.webkit.org/show_bug.cgi?id=190047
+
+        Reviewed by Keith Miller.
+
+        Object.keys is one of the most frequently used function in web-tooling-benchmarks (WTB).
+        Object.keys is dominant in lebab of WTB, and frequently called in babel and others.
+        Since our Structure knows the shape of JSObject, we can cache the result of Object.keys
+        in Structure (StructureRareData) as we cache JSPropertyNameEnumerator in StructureRareData.
+
+        This patch caches the result of Object.keys in StructureRareData. The cached array is created
+        as JSImmutableButterfly. And Object.keys creates CoW from this data. Currently, the lifetime
+        strategy of this JSImmutableButterfly is the same to cached JSPropertyNameEnumerator. It is
+        referenced from Structure, and collected when Structure is collected.
+
+        This improves several benchmarks in SixSpeed.
+
+                                        baseline                  patched
+
+            object-assign.es5      350.1710+-3.6303     ^    226.0368+-4.7558        ^ definitely 1.5492x faster
+            for-of-object.es6      269.1941+-3.3430     ^    127.9317+-2.3875        ^ definitely 2.1042x faster
+
+        And it improves WTB lebab by 11.8%.
+
+            Before: lebab:  6.10 runs/s
+            After:  lebab:  6.82 runs/s
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsicCall):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGConstantFoldingPhase.cpp:
+        (JSC::DFG::ConstantFoldingPhase::foldConstants):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGNode.cpp:
+        (JSC::DFG::Node::convertToNewArrayBuffer):
+        * dfg/DFGNode.h:
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileObjectKeys):
+        * dfg/DFGSpeculativeJIT.h:
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLAbstractHeapRepository.h:
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileObjectKeys):
+        * runtime/Intrinsic.cpp:
+        (JSC::intrinsicName):
+        * runtime/Intrinsic.h:
+        * runtime/JSImmutableButterfly.h:
+        (JSC::JSImmutableButterfly::createSentinel):
+        * runtime/ObjectConstructor.cpp:
+        (JSC::ownPropertyKeys):
+        * runtime/Structure.cpp:
+        (JSC::Structure::canCachePropertyNameEnumerator const):
+        * runtime/Structure.h:
+        * runtime/StructureInlines.h:
+        (JSC::Structure::setCachedOwnKeys):
+        (JSC::Structure::cachedOwnKeys const):
+        (JSC::Structure::canCacheOwnKeys const):
+        * runtime/StructureRareData.cpp:
+        (JSC::StructureRareData::visitChildren):
+        (JSC::StructureRareData::cachedPropertyNameEnumerator const): Deleted.
+        (JSC::StructureRareData::setCachedPropertyNameEnumerator): Deleted.
+        * runtime/StructureRareData.h:
+        * runtime/StructureRareDataInlines.h:
+        (JSC::StructureRareData::cachedPropertyNameEnumerator const):
+        (JSC::StructureRareData::setCachedPropertyNameEnumerator):
+        (JSC::StructureRareData::cachedOwnKeys const):
+        (JSC::StructureRareData::cachedOwnKeysConcurrently const):
+        (JSC::StructureRareData::setCachedOwnKeys):
+        (JSC::StructureRareData::previousID const): Deleted.
+        * runtime/VM.cpp:
+        (JSC::VM::VM):
+        * runtime/VM.h:
+
 2018-12-12  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
 
         [DFG][FTL] Add NewSymbol
index f592dd1..9c7fdaa 100644 (file)
@@ -43,6 +43,7 @@
 #include "Operations.h"
 #include "PutByIdStatus.h"
 #include "StringObject.h"
+#include "StructureRareDataInlines.h"
 #include <wtf/BooleanLattice.h>
 #include <wtf/CheckedArithmetic.h>
 
@@ -2575,6 +2576,30 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         break;
     }
 
+    case ObjectKeys: {
+        if (node->child1().useKind() == ObjectUse) {
+            auto& structureSet = forNode(node->child1()).m_structure;
+            if (structureSet.isFinite() && structureSet.size() == 1) {
+                RegisteredStructure structure = structureSet.onlyStructure();
+                if (auto* rareData = structure->rareDataConcurrently()) {
+                    auto* immutableButterfly = rareData->cachedOwnKeysConcurrently();
+                    if (immutableButterfly && immutableButterfly != m_vm.sentinelImmutableButterfly.get()) {
+                        if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) {
+                            m_state.setFoundConstants(true);
+                            didFoldClobberWorld();
+                            setTypeForNode(node, SpecArray);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        clobberWorld();
+        setTypeForNode(node, SpecArray);
+        break;
+    }
+
     case ToObject:
     case CallObjectConstructor: {
         AbstractValue& source = forNode(node->child1());
index edb0c64..a94ee0d 100644 (file)
@@ -2688,6 +2688,15 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, VirtualRegister result, I
         return true;
     }
 
+    case ObjectKeysIntrinsic: {
+        if (argumentCountIncludingThis < 2)
+            return false;
+
+        insertChecks();
+        set(result, addToGraph(ObjectKeys, get(virtualRegisterForArgument(1, registerOffset))));
+        return true;
+    }
+
     case ReflectGetPrototypeOfIntrinsic: {
         if (argumentCountIncludingThis != 2)
             return false;
index 166e419..baaaad6 100644 (file)
@@ -667,6 +667,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case CreateThis:
     case InstanceOf:
     case StringValueOf:
+    case ObjectKeys:
         read(World);
         write(Heap);
         return;
@@ -1528,7 +1529,6 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         }
     }
 
-
     case NewObject:
     case NewRegexp:
     case NewSymbol:
index fb1f6dc..03f84f5 100644 (file)
@@ -766,6 +766,26 @@ private:
                 break;
             }
 
+            case ObjectKeys: {
+                if (node->child1().useKind() == ObjectUse) {
+                    auto& structureSet = m_state.forNode(node->child1()).m_structure;
+                    if (structureSet.isFinite() && structureSet.size() == 1) {
+                        RegisteredStructure structure = structureSet.onlyStructure();
+                        if (auto* rareData = structure->rareDataConcurrently()) {
+                            auto* immutableButterfly = rareData->cachedOwnKeysConcurrently();
+                            if (immutableButterfly && immutableButterfly != m_graph.m_vm.sentinelImmutableButterfly.get()) {
+                                if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) {
+                                    node->convertToNewArrayBuffer(m_graph.freeze(immutableButterfly));
+                                    changed = true;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+
             case ToNumber: {
                 if (m_state.forNode(node->child1()).m_type & ~SpecBytecodeNumber)
                     break;
index 808932f..208e41d 100644 (file)
@@ -338,6 +338,7 @@ bool doesGC(Graph& graph, Node* node)
     case ToThis:
     case CreateThis:
     case ObjectCreate:
+    case ObjectKeys:
     case AllocatePropertyStorage:
     case ReallocatePropertyStorage:
     case Arrayify:
index 593ddf2..9037634 100644 (file)
@@ -1575,6 +1575,14 @@ private:
             break;
         }
 
+        case ObjectKeys: {
+            if (node->child1()->shouldSpeculateObject()) {
+                watchHavingABadTime(node);
+                fixEdge<ObjectUse>(node->child1());
+            }
+            break;
+        }
+
         case CheckStringIdent: {
             fixEdge<StringIdentUse>(node->child1());
             break;
index dd967c8..3d1c3ba 100644 (file)
@@ -31,6 +31,7 @@
 #include "DFGGraph.h"
 #include "DFGPromotedHeapLocation.h"
 #include "JSCInlines.h"
+#include "JSImmutableButterfly.h"
 
 namespace JSC { namespace DFG {
 
@@ -223,6 +224,17 @@ void Node::convertToLazyJSConstant(Graph& graph, LazyJSValue value)
     children.reset();
 }
 
+void Node::convertToNewArrayBuffer(FrozenValue* immutableButterfly)
+{
+    setOpAndDefaultFlags(NewArrayBuffer);
+    NewArrayBufferData data { };
+    data.indexingMode = immutableButterfly->cast<JSImmutableButterfly*>()->indexingMode();
+    data.vectorLengthHint = immutableButterfly->cast<JSImmutableButterfly*>()->toButterfly()->vectorLength();
+    children.reset();
+    m_opInfo = immutableButterfly;
+    m_opInfo2 = data.asQuadWord;
+}
+
 void Node::convertToDirectCall(FrozenValue* executable)
 {
     NodeType newOp = LastNodeType;
index 5aa83a9..327650d 100644 (file)
@@ -761,6 +761,8 @@ public:
         m_opInfo = structure;
         m_opInfo2 = OpInfoWrapper();
     }
+
+    void convertToNewArrayBuffer(FrozenValue* immutableButterfly);
     
     void convertToDirectCall(FrozenValue*);
 
index 3817fb2..07b773a 100644 (file)
@@ -264,6 +264,7 @@ namespace JSC { namespace DFG {
     macro(ParseInt, NodeMustGenerate | NodeResultJS) \
     macro(GetPrototypeOf, NodeMustGenerate | NodeResultJS) \
     macro(ObjectCreate, NodeMustGenerate | NodeResultJS) \
+    macro(ObjectKeys, NodeMustGenerate | NodeResultJS) \
     \
     /* Atomics object functions. */\
     macro(AtomicsAdd, NodeResultJS | NodeMustGenerate | NodeHasVarArgs) \
index 81f16c5..e821280 100644 (file)
@@ -248,6 +248,25 @@ EncodedJSValue JIT_OPERATION operationToThisStrict(ExecState* exec, EncodedJSVal
     return JSValue::encode(JSValue::decode(encodedOp).toThis(exec, StrictMode));
 }
 
+JSArray* JIT_OPERATION operationObjectKeys(ExecState* exec, EncodedJSValue encodedObject)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSObject* object = JSValue::decode(encodedObject).toObject(exec);
+    RETURN_IF_EXCEPTION(scope, nullptr);
+    scope.release();
+    return ownPropertyKeys(exec, object, PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude);
+}
+
+JSArray* JIT_OPERATION operationObjectKeysObject(ExecState* exec, JSObject* object)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+    return ownPropertyKeys(exec, object, PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude);
+}
+
 JSCell* JIT_OPERATION operationObjectCreate(ExecState* exec, EncodedJSValue encodedPrototype)
 {
     VM& vm = exec->vm();
index 4e35634..96953bd 100644 (file)
@@ -43,6 +43,8 @@ EncodedJSValue JIT_OPERATION operationStringFromCharCodeUntyped(ExecState*, Enco
 // These routines provide callbacks out to C++ implementations of operations too complex to JIT.
 JSCell* JIT_OPERATION operationCallObjectConstructor(ExecState*, JSGlobalObject*, EncodedJSValue encodedTarget) WTF_INTERNAL;
 JSCell* JIT_OPERATION operationToObject(ExecState*, JSGlobalObject*, EncodedJSValue encodedTarget, UniquedStringImpl*) WTF_INTERNAL;
+JSArray* JIT_OPERATION operationObjectKeys(ExecState*, EncodedJSValue) WTF_INTERNAL;
+JSArray* JIT_OPERATION operationObjectKeysObject(ExecState*, JSObject*) WTF_INTERNAL;
 JSCell* JIT_OPERATION operationObjectCreate(ExecState*, EncodedJSValue) WTF_INTERNAL;
 JSCell* JIT_OPERATION operationObjectCreateObject(ExecState*, JSObject*) WTF_INTERNAL;
 JSCell* JIT_OPERATION operationCreateThis(ExecState*, JSObject* constructor, uint32_t inlineCapacity) WTF_INTERNAL;
index 72e15fa..dcdc1f7 100644 (file)
@@ -975,7 +975,8 @@ private:
         case NewArray:
         case NewArrayWithSize:
         case CreateRest:
-        case NewArrayBuffer: {
+        case NewArrayBuffer:
+        case ObjectKeys: {
             setPrediction(SpecArray);
             break;
         }
index dc751e5..1726867 100644 (file)
@@ -178,6 +178,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node, bool igno
     case ToThis:
     case CreateThis:
     case ObjectCreate:
+    case ObjectKeys:
     case GetCallee:
     case SetCallee:
     case GetArgumentCountIncludingThis:
index ea659a8..a186433 100644 (file)
@@ -12345,6 +12345,94 @@ void SpeculativeJIT::compileToThis(Node* node)
     jsValueResult(tempRegs, node);
 }
 
+void SpeculativeJIT::compileObjectKeys(Node* node)
+{
+    switch (node->child1().useKind()) {
+    case ObjectUse: {
+        if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) {
+            SpeculateCellOperand object(this, node->child1());
+            GPRTemporary structure(this);
+            GPRTemporary scratch(this);
+            GPRTemporary scratch2(this);
+            GPRTemporary scratch3(this);
+            GPRTemporary result(this);
+
+            GPRReg objectGPR = object.gpr();
+            GPRReg structureGPR = structure.gpr();
+            GPRReg scratchGPR = scratch.gpr();
+            GPRReg scratch2GPR = scratch2.gpr();
+            GPRReg scratch3GPR = scratch3.gpr();
+            GPRReg resultGPR = result.gpr();
+
+            speculateObject(node->child1(), objectGPR);
+
+            CCallHelpers::JumpList slowCases;
+            m_jit.emitLoadStructure(*m_jit.vm(), objectGPR, structureGPR, scratchGPR);
+            m_jit.loadPtr(CCallHelpers::Address(structureGPR, Structure::previousOrRareDataOffset()), scratchGPR);
+
+            slowCases.append(m_jit.branchTestPtr(CCallHelpers::Zero, scratchGPR));
+            slowCases.append(m_jit.branch32(CCallHelpers::Equal, CCallHelpers::Address(scratchGPR, JSCell::structureIDOffset()), TrustedImm32(bitwise_cast<int32_t>(m_jit.vm()->structureStructure->structureID()))));
+
+            m_jit.loadPtr(CCallHelpers::Address(scratchGPR, StructureRareData::offsetOfCachedOwnKeys()), scratchGPR);
+
+            slowCases.append(m_jit.branchTestPtr(CCallHelpers::Zero, scratchGPR));
+            slowCases.append(m_jit.branchPtr(CCallHelpers::Equal, scratchGPR, TrustedImmPtr::weakPointer(m_jit.graph(), m_jit.vm()->sentinelImmutableButterfly.get())));
+
+            MacroAssembler::JumpList slowButArrayBufferCases;
+
+            JSGlobalObject* globalObject = m_jit.graph().globalObjectFor(node->origin.semantic);
+            RegisteredStructure arrayStructure = m_jit.graph().registerStructure(globalObject->arrayStructureForIndexingTypeDuringAllocation(CopyOnWriteArrayWithContiguous));
+
+            m_jit.move(scratchGPR, scratch3GPR);
+            m_jit.addPtr(TrustedImmPtr(JSImmutableButterfly::offsetOfData()), scratchGPR);
+
+            emitAllocateJSObject<JSArray>(resultGPR, TrustedImmPtr(arrayStructure), scratchGPR, structureGPR, scratch2GPR, slowButArrayBufferCases);
+
+            addSlowPathGenerator(slowPathCall(slowButArrayBufferCases, this, operationNewArrayBuffer, resultGPR, arrayStructure, scratch3GPR));
+
+            addSlowPathGenerator(slowPathCall(slowCases, this, operationObjectKeysObject, resultGPR, objectGPR));
+
+            cellResult(resultGPR, node);
+            break;
+        }
+
+        SpeculateCellOperand object(this, node->child1());
+
+        GPRReg objectGPR = object.gpr();
+
+        speculateObject(node->child1(), objectGPR);
+
+        flushRegisters();
+        GPRFlushedCallResult result(this);
+        GPRReg resultGPR = result.gpr();
+        callOperation(operationObjectKeysObject, resultGPR, objectGPR);
+        m_jit.exceptionCheck();
+
+        cellResult(resultGPR, node);
+        break;
+    }
+
+    case UntypedUse: {
+        JSValueOperand object(this, node->child1());
+
+        JSValueRegs objectRegs = object.jsValueRegs();
+
+        flushRegisters();
+        GPRFlushedCallResult result(this);
+        GPRReg resultGPR = result.gpr();
+        callOperation(operationObjectKeys, resultGPR, objectRegs);
+        m_jit.exceptionCheck();
+
+        cellResult(resultGPR, node);
+        break;
+    }
+
+    default:
+        RELEASE_ASSERT_NOT_REACHED();
+        break;
+    }
+}
+
 void SpeculativeJIT::compileObjectCreate(Node* node)
 {
     switch (node->child1().useKind()) {
index 8177e4a..a2dbee7 100644 (file)
@@ -1478,6 +1478,7 @@ public:
     void compileNewArrayWithSize(Node*);
     void compileNewTypedArray(Node*);
     void compileToThis(Node*);
+    void compileObjectKeys(Node*);
     void compileObjectCreate(Node*);
     void compileCreateThis(Node*);
     void compileNewObject(Node*);
index 0c973fc..b91af26 100644 (file)
@@ -3162,6 +3162,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case ObjectKeys: {
+        compileObjectKeys(node);
+        break;
+    }
+
     case CreateThis: {
         compileCreateThis(node);
         break;
index 25215e3..e2ee3bd 100644 (file)
@@ -3404,6 +3404,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case ObjectKeys: {
+        compileObjectKeys(node);
+        break;
+    }
+
     case CreateThis: {
         compileCreateThis(node);
         break;
index bbd4713..9f3aa2f 100644 (file)
@@ -116,8 +116,10 @@ namespace JSC { namespace FTL {
     macro(Structure_globalObject, Structure::globalObjectOffset()) \
     macro(Structure_indexingModeIncludingHistory, Structure::indexingModeIncludingHistoryOffset()) \
     macro(Structure_inlineCapacity, Structure::inlineCapacityOffset()) \
+    macro(Structure_previousOrRareData, Structure::previousOrRareDataOffset()) \
     macro(Structure_prototype, Structure::prototypeOffset()) \
     macro(Structure_structureID, Structure::structureIDOffset()) \
+    macro(StructureRareData_cachedOwnKeys, StructureRareData::offsetOfCachedOwnKeys()) \
     macro(HashMapImpl_capacity, HashMapImpl<HashMapBucket<HashMapBucketDataKey>>::offsetOfCapacity()) \
     macro(HashMapImpl_buffer,  HashMapImpl<HashMapBucket<HashMapBucketDataKey>>::offsetOfBuffer()) \
     macro(HashMapImpl_head,  HashMapImpl<HashMapBucket<HashMapBucketDataKey>>::offsetOfHead()) \
index c91d155..33fdf84 100644 (file)
@@ -200,6 +200,7 @@ inline CapabilityLevel canCompile(Node* node)
     case CallObjectConstructor:
     case CallStringConstructor:
     case ObjectCreate:
+    case ObjectKeys:
     case MakeRope:
     case NewArrayWithSize:
     case TryGetById:
index 85f40ed..df19a1f 100644 (file)
@@ -863,6 +863,9 @@ private:
         case ObjectCreate:
             compileObjectCreate();
             break;
+        case ObjectKeys:
+            compileObjectKeys();
+            break;
         case NewObject:
             compileNewObject();
             break;
@@ -5485,6 +5488,75 @@ private:
         setInt32(m_out.phi(Int32, zeroLengthResult, nonZeroLengthResult));
     }
 
+    void compileObjectKeys()
+    {
+        switch (m_node->child1().useKind()) {
+        case ObjectUse: {
+            if (m_graph.isWatchingHavingABadTimeWatchpoint(m_node)) {
+                LBasicBlock notNullCase = m_out.newBlock();
+                LBasicBlock rareDataCase = m_out.newBlock();
+                LBasicBlock notNullCacheCase = m_out.newBlock();
+                LBasicBlock useCacheCase = m_out.newBlock();
+                LBasicBlock slowButArrayBufferCase = m_out.newBlock();
+                LBasicBlock slowCase = m_out.newBlock();
+                LBasicBlock continuation = m_out.newBlock();
+
+                LValue object = lowObject(m_node->child1());
+                LValue structure = loadStructure(object);
+                LValue previousOrRareData = m_out.loadPtr(structure, m_heaps.Structure_previousOrRareData);
+                m_out.branch(m_out.notNull(previousOrRareData), unsure(notNullCase), unsure(slowCase));
+
+                LBasicBlock lastNext = m_out.appendTo(notNullCase, rareDataCase);
+                m_out.branch(
+                    m_out.notEqual(m_out.load32(previousOrRareData, m_heaps.JSCell_structureID), m_out.constInt32(m_graph.m_vm.structureStructure->structureID())),
+                    unsure(rareDataCase), unsure(slowCase));
+
+                m_out.appendTo(rareDataCase, notNullCacheCase);
+                LValue cachedOwnKeys = m_out.loadPtr(previousOrRareData, m_heaps.StructureRareData_cachedOwnKeys);
+                m_out.branch(m_out.notNull(cachedOwnKeys), unsure(notNullCacheCase), unsure(slowCase));
+
+                m_out.appendTo(notNullCacheCase, useCacheCase);
+                m_out.branch(m_out.notEqual(cachedOwnKeys, weakPointer(m_graph.m_vm.sentinelImmutableButterfly.get())), unsure(useCacheCase), unsure(slowCase));
+
+                m_out.appendTo(useCacheCase, slowButArrayBufferCase);
+                JSGlobalObject* globalObject = m_graph.globalObjectFor(m_node->origin.semantic);
+                RegisteredStructure arrayStructure = m_graph.registerStructure(globalObject->arrayStructureForIndexingTypeDuringAllocation(CopyOnWriteArrayWithContiguous));
+                LValue fastArray = allocateObject<JSArray>(arrayStructure, m_out.addPtr(cachedOwnKeys, JSImmutableButterfly::offsetOfData()), slowButArrayBufferCase);
+                ValueFromBlock fastResult = m_out.anchor(fastArray);
+                m_out.jump(continuation);
+
+                m_out.appendTo(slowButArrayBufferCase, slowCase);
+                LValue slowArray = vmCall(Int64, m_out.operation(operationNewArrayBuffer), m_callFrame, weakStructure(arrayStructure), cachedOwnKeys);
+                ValueFromBlock slowButArrayBufferResult = m_out.anchor(slowArray);
+                m_out.jump(continuation);
+
+                m_out.appendTo(slowCase, continuation);
+                VM& vm = this->vm();
+                LValue slowResultValue = lazySlowPath(
+                    [=, &vm] (const Vector<Location>& locations) -> RefPtr<LazySlowPath::Generator> {
+                        return createLazyCallGenerator(vm,
+                            operationObjectKeysObject, locations[0].directGPR(), locations[1].directGPR());
+                    },
+                    object);
+                ValueFromBlock slowResult = m_out.anchor(slowResultValue);
+                m_out.jump(continuation);
+
+                m_out.appendTo(continuation, lastNext);
+                setJSValue(m_out.phi(pointerType(), fastResult, slowButArrayBufferResult, slowResult));
+                break;
+            }
+            setJSValue(vmCall(Int64, m_out.operation(operationObjectKeysObject), m_callFrame, lowObject(m_node->child1())));
+            break;
+        }
+        case UntypedUse:
+            setJSValue(vmCall(Int64, m_out.operation(operationObjectKeys), m_callFrame, lowJSValue(m_node->child1())));
+            break;
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+            break;
+        }
+    }
+
     void compileObjectCreate()
     {
         switch (m_node->child1().useKind()) {
index abe6f17..fbc4ef3 100644 (file)
@@ -119,6 +119,8 @@ const char* intrinsicName(Intrinsic intrinsic)
         return "ObjectGetPrototypeOfIntrinsic";
     case ObjectIsIntrinsic:
         return "ObjectIsIntrinsic";
+    case ObjectKeysIntrinsic:
+        return "ObjectKeysIntrinsic";
     case ReflectGetPrototypeOfIntrinsic:
         return "ReflectGetPrototypeOfIntrinsic";
     case StringPrototypeValueOfIntrinsic:
index cfbd184..b47f46d 100644 (file)
@@ -72,6 +72,7 @@ enum Intrinsic {
     ObjectCreateIntrinsic,
     ObjectGetPrototypeOfIntrinsic,
     ObjectIsIntrinsic,
+    ObjectKeysIntrinsic,
     ReflectGetPrototypeOfIntrinsic,
     StringPrototypeValueOfIntrinsic,
     StringPrototypeReplaceIntrinsic,
index 284783c..1ff4d27 100644 (file)
@@ -67,6 +67,11 @@ public:
         return array;
     }
 
+    static JSImmutableButterfly* createSentinel(VM& vm)
+    {
+        return create(vm, CopyOnWriteArrayWithContiguous, 0);
+    }
+
     unsigned publicLength() const { return m_header.publicLength(); }
     unsigned vectorLength() const { return m_header.vectorLength(); }
     unsigned length() const { return m_header.publicLength(); }
index 90a36a9..fcf99b8 100644 (file)
@@ -30,6 +30,7 @@
 #include "JSFunction.h"
 #include "JSGlobalObject.h"
 #include "JSGlobalObjectFunctions.h"
+#include "JSImmutableButterfly.h"
 #include "Lookup.h"
 #include "ObjectPrototype.h"
 #include "PropertyDescriptor.h"
@@ -73,7 +74,7 @@ const ClassInfo ObjectConstructor::s_info = { "Function", &InternalFunction::s_i
   getOwnPropertyDescriptors objectConstructorGetOwnPropertyDescriptors  DontEnum|Function 1
   getOwnPropertyNames       objectConstructorGetOwnPropertyNames        DontEnum|Function 1
   getOwnPropertySymbols     objectConstructorGetOwnPropertySymbols      DontEnum|Function 1
-  keys                      objectConstructorKeys                       DontEnum|Function 1
+  keys                      objectConstructorKeys                       DontEnum|Function 1 ObjectKeysIntrinsic
   defineProperty            objectConstructorDefineProperty             DontEnum|Function 3
   defineProperties          objectConstructorDefineProperties           DontEnum|Function 2
   create                    objectConstructorCreate                     DontEnum|Function 2 ObjectCreateIntrinsic
@@ -271,7 +272,6 @@ EncodedJSValue JSC_HOST_CALL objectConstructorGetOwnPropertySymbols(ExecState* e
     RELEASE_AND_RETURN(scope, JSValue::encode(ownPropertyKeys(exec, object, PropertyNameMode::Symbols, DontEnumPropertiesMode::Include)));
 }
 
-// FIXME: Use the enumeration cache.
 EncodedJSValue JSC_HOST_CALL objectConstructorKeys(ExecState* exec)
 {
     VM& vm = exec->vm();
@@ -892,11 +892,25 @@ EncodedJSValue JSC_HOST_CALL objectConstructorIs(ExecState* exec)
     return JSValue::encode(jsBoolean(sameValue(exec, exec->argument(0), exec->argument(1))));
 }
 
-// FIXME: Use the enumeration cache.
 JSArray* ownPropertyKeys(ExecState* exec, JSObject* object, PropertyNameMode propertyNameMode, DontEnumPropertiesMode dontEnumPropertiesMode)
 {
     VM& vm = exec->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* globalObject = exec->lexicalGlobalObject();
+    bool isObjectKeys = propertyNameMode == PropertyNameMode::Strings && dontEnumPropertiesMode == DontEnumPropertiesMode::Exclude;
+    // We attempt to look up own property keys cache in Object.keys case.
+    if (isObjectKeys) {
+        if (LIKELY(!globalObject->isHavingABadTime())) {
+            if (auto* immutableButterfly = object->structure(vm)->cachedOwnKeys()) {
+                if (immutableButterfly != vm.sentinelImmutableButterfly.get()) {
+                    Structure* arrayStructure = globalObject->originalArrayStructureForIndexingType(immutableButterfly->indexingMode());
+                    return JSArray::createWithButterfly(vm, nullptr, arrayStructure, immutableButterfly->toButterfly());
+                }
+            }
+        }
+    }
+
     PropertyNameArray properties(&vm, propertyNameMode, PrivateSymbolMode::Exclude);
     object->methodTable(vm)->getOwnPropertyNames(object, exec, properties, EnumerationMode(dontEnumPropertiesMode));
     RETURN_IF_EXCEPTION(scope, nullptr);
@@ -918,8 +932,31 @@ JSArray* ownPropertyKeys(ExecState* exec, JSObject* object, PropertyNameMode pro
     if (propertyNameMode != PropertyNameMode::StringsAndSymbols) {
         ASSERT(propertyNameMode == PropertyNameMode::Strings || propertyNameMode == PropertyNameMode::Symbols);
         if (!mustFilterProperty && properties.size() < MIN_SPARSE_ARRAY_INDEX) {
-            auto* globalObject = exec->lexicalGlobalObject();
             if (LIKELY(!globalObject->isHavingABadTime())) {
+                if (isObjectKeys) {
+                    Structure* structure = object->structure(vm);
+                    if (structure->canCacheOwnKeys()) {
+                        auto* cachedButterfly = structure->cachedOwnKeys();
+                        if (cachedButterfly == vm.sentinelImmutableButterfly.get()) {
+                            // Cache the immutable butterfly!
+                            size_t numProperties = properties.size();
+                            auto* newButterfly = JSImmutableButterfly::create(vm, CopyOnWriteArrayWithContiguous, numProperties);
+                            for (size_t i = 0; i < numProperties; i++) {
+                                const auto& identifier = properties[i];
+                                ASSERT(!identifier.isSymbol());
+                                newButterfly->setIndex(vm, i, jsOwnedString(&vm, identifier.string()));
+                            }
+
+                            structure->setCachedOwnKeys(vm, newButterfly);
+                            Structure* arrayStructure = globalObject->originalArrayStructureForIndexingType(newButterfly->indexingMode());
+                            return JSArray::createWithButterfly(vm, nullptr, arrayStructure, newButterfly->toButterfly());
+                        }
+
+                        if (cachedButterfly == nullptr)
+                            structure->setCachedOwnKeys(vm, jsCast<JSImmutableButterfly*>(vm.sentinelImmutableButterfly.get()));
+                    }
+                }
+
                 size_t numProperties = properties.size();
                 JSArray* keys = JSArray::create(vm, globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous), numProperties);
                 WriteBarrier<Unknown>* buffer = keys->butterfly()->contiguous().data();
index 58d83bc..3542827 100644 (file)
@@ -1252,17 +1252,7 @@ JSPropertyNameEnumerator* Structure::cachedPropertyNameEnumerator() const
 
 bool Structure::canCachePropertyNameEnumerator() const
 {
-    auto canCache = [] (const Structure* structure) {
-        if (structure->isDictionary())
-            return false;
-        if (hasIndexedProperties(structure->indexingType()))
-            return false;
-        if (structure->typeInfo().overridesGetPropertyNames())
-            return false;
-        return true;
-    };
-
-    if (!canCache(this))
+    if (!this->canCacheOwnKeys())
         return false;
 
     StructureChain* structureChain = m_cachedPrototypeChain.get();
@@ -1271,7 +1261,7 @@ bool Structure::canCachePropertyNameEnumerator() const
     while (true) {
         if (!structure->get())
             return true;
-        if (!canCache(structure->get()))
+        if (!structure->get()->canCacheOwnKeys())
             return false;
         structure++;
     }
index 8ded825..1e84c4d 100644 (file)
@@ -38,7 +38,6 @@
 #include "PutPropertySlot.h"
 #include "StructureIDBlob.h"
 #include "StructureRareData.h"
-#include "StructureRareDataInlines.h"
 #include "StructureTransitionTable.h"
 #include "JSTypeInfo.h"
 #include "Watchpoint.h"
@@ -326,6 +325,15 @@ public:
         return static_cast<const StructureRareData*>(m_previousOrRareData.get());
     }
 
+    const StructureRareData* rareDataConcurrently() const
+    {
+        JSCell* cell = m_previousOrRareData.get();
+        WTF::loadLoadFence();
+        if (isRareData(cell))
+            return static_cast<StructureRareData*>(cell);
+        return nullptr;
+    }
+
     StructureRareData* ensureRareData(VM& vm)
     {
         if (!hasRareData())
@@ -472,6 +480,10 @@ public:
     bool canCachePropertyNameEnumerator() const;
     bool canAccessPropertiesQuicklyForEnumeration() const;
 
+    void setCachedOwnKeys(VM&, JSImmutableButterfly*);
+    JSImmutableButterfly* cachedOwnKeys() const;
+    bool canCacheOwnKeys() const;
+
     void getPropertyNamesFromStructure(VM&, PropertyNameArray&, EnumerationMode);
 
     JSString* objectToStringValue()
@@ -520,6 +532,11 @@ public:
         return OBJECT_OFFSETOF(Structure, m_inlineCapacity);
     }
 
+    static ptrdiff_t previousOrRareDataOffset()
+    {
+        return OBJECT_OFFSETOF(Structure, m_previousOrRareData);
+    }
+
     static Structure* createStructure(VM&);
         
     bool transitionWatchpointSetHasBeenInvalidated() const
index b3eb1b4..ab7e206 100644 (file)
@@ -219,6 +219,29 @@ inline bool Structure::transitivelyTransitionedFrom(Structure* structureToFind)
     return false;
 }
 
+inline void Structure::setCachedOwnKeys(VM& vm, JSImmutableButterfly* ownKeys)
+{
+    ensureRareData(vm)->setCachedOwnKeys(vm, ownKeys);
+}
+
+inline JSImmutableButterfly* Structure::cachedOwnKeys() const
+{
+    if (!hasRareData())
+        return nullptr;
+    return rareData()->cachedOwnKeys();
+}
+
+inline bool Structure::canCacheOwnKeys() const
+{
+    if (isDictionary())
+        return false;
+    if (hasIndexedProperties(indexingType()))
+        return false;
+    if (typeInfo().overridesGetPropertyNames())
+        return false;
+    return true;
+}
+
 ALWAYS_INLINE JSValue prototypeForLookupPrimitiveImpl(JSGlobalObject* globalObject, const Structure* structure)
 {
     ASSERT(!structure->isObject());
index 8984ce9..d8b2f00 100644 (file)
@@ -27,6 +27,7 @@
 #include "StructureRareData.h"
 
 #include "AdaptiveInferredPropertyValueWatchpointBase.h"
+#include "JSImmutableButterfly.h"
 #include "JSPropertyNameEnumerator.h"
 #include "JSString.h"
 #include "JSCInlines.h"
@@ -70,16 +71,7 @@ void StructureRareData::visitChildren(JSCell* cell, SlotVisitor& visitor)
     visitor.append(thisObject->m_previous);
     visitor.append(thisObject->m_objectToStringValue);
     visitor.append(thisObject->m_cachedPropertyNameEnumerator);
-}
-
-JSPropertyNameEnumerator* StructureRareData::cachedPropertyNameEnumerator() const
-{
-    return m_cachedPropertyNameEnumerator.get();
-}
-
-void StructureRareData::setCachedPropertyNameEnumerator(VM& vm, JSPropertyNameEnumerator* enumerator)
-{
-    m_cachedPropertyNameEnumerator.set(vm, this, enumerator);
+    visitor.append(thisObject->m_cachedOwnKeys);
 }
 
 // ----------- Object.prototype.toString() helper watchpoint classes -----------
index 11c181d..d4de58b 100644 (file)
@@ -58,7 +58,10 @@ public:
 
     static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype);
 
-    Structure* previousID() const;
+    Structure* previousID() const
+    {
+        return m_previous.get();
+    }
     void setPreviousID(VM&, Structure*);
     void clearPreviousID();
 
@@ -68,11 +71,20 @@ public:
     JSPropertyNameEnumerator* cachedPropertyNameEnumerator() const;
     void setCachedPropertyNameEnumerator(VM&, JSPropertyNameEnumerator*);
 
+    JSImmutableButterfly* cachedOwnKeys() const;
+    JSImmutableButterfly* cachedOwnKeysConcurrently() const;
+    void setCachedOwnKeys(VM&, JSImmutableButterfly*);
+
     Box<InlineWatchpointSet> copySharedPolyProtoWatchpoint() const { return m_polyProtoWatchpoint; }
     const Box<InlineWatchpointSet>& sharedPolyProtoWatchpoint() const { return m_polyProtoWatchpoint; }
     void setSharedPolyProtoWatchpoint(Box<InlineWatchpointSet>&& sharedPolyProtoWatchpoint) { m_polyProtoWatchpoint = WTFMove(sharedPolyProtoWatchpoint); }
     bool hasSharedPolyProtoWatchpoint() const { return static_cast<bool>(m_polyProtoWatchpoint); }
 
+    static ptrdiff_t offsetOfCachedOwnKeys()
+    {
+        return OBJECT_OFFSETOF(StructureRareData, m_cachedOwnKeys);
+    }
+
     DECLARE_EXPORT_INFO;
 
 private:
@@ -86,7 +98,10 @@ private:
 
     WriteBarrier<Structure> m_previous;
     WriteBarrier<JSString> m_objectToStringValue;
+    // FIXME: We should have some story for clearing these property names caches in GC.
+    // https://bugs.webkit.org/show_bug.cgi?id=192659
     WriteBarrier<JSPropertyNameEnumerator> m_cachedPropertyNameEnumerator;
+    WriteBarrier<JSImmutableButterfly> m_cachedOwnKeys;
 
     typedef HashMap<PropertyOffset, RefPtr<WatchpointSet>, WTF::IntHash<PropertyOffset>, WTF::UnsignedWithZeroKeyHashTraits<PropertyOffset>> PropertyWatchpointMap;
     std::unique_ptr<PropertyWatchpointMap> m_replacementWatchpointSets;
index e4e2496..35c3b2d 100644 (file)
 
 #pragma once
 
+#include "JSImmutableButterfly.h"
+#include "JSPropertyNameEnumerator.h"
 #include "JSString.h"
 #include "StructureRareData.h"
 
 namespace JSC {
 
-inline Structure* StructureRareData::previousID() const
-{
-    return m_previous.get();
-}
-
 inline void StructureRareData::setPreviousID(VM& vm, Structure* structure)
 {
     m_previous.set(vm, this, structure);
@@ -50,4 +47,33 @@ inline JSString* StructureRareData::objectToStringValue() const
     return m_objectToStringValue.get();
 }
 
+inline JSPropertyNameEnumerator* StructureRareData::cachedPropertyNameEnumerator() const
+{
+    return m_cachedPropertyNameEnumerator.get();
+}
+
+inline void StructureRareData::setCachedPropertyNameEnumerator(VM& vm, JSPropertyNameEnumerator* enumerator)
+{
+    m_cachedPropertyNameEnumerator.set(vm, this, enumerator);
+}
+
+inline JSImmutableButterfly* StructureRareData::cachedOwnKeys() const
+{
+    ASSERT(!compilationOrGCThread())
+    return m_cachedOwnKeys.get();
+}
+
+inline JSImmutableButterfly* StructureRareData::cachedOwnKeysConcurrently() const
+{
+    auto* result = m_cachedOwnKeys.get();
+    WTF::loadLoadFence();
+    return result;
+}
+
+inline void StructureRareData::setCachedOwnKeys(VM& vm, JSImmutableButterfly* butterfly)
+{
+    WTF::storeStoreFence();
+    m_cachedOwnKeys.set(vm, this, butterfly);
+}
+
 } // namespace JSC
index 256a583..a774fa5 100644 (file)
@@ -434,6 +434,7 @@ VM::VM(VMType vmType, HeapType heapType)
     exceptionStructure.set(*this, Exception::createStructure(*this, 0, jsNull()));
     promiseDeferredStructure.set(*this, JSPromiseDeferred::createStructure(*this, 0, jsNull()));
     internalPromiseDeferredStructure.set(*this, JSInternalPromiseDeferred::createStructure(*this, 0, jsNull()));
+    nativeStdFunctionCellStructure.set(*this, NativeStdFunctionCell::createStructure(*this, 0, jsNull()));
     programCodeBlockStructure.set(*this, ProgramCodeBlock::createStructure(*this, 0, jsNull()));
     moduleProgramCodeBlockStructure.set(*this, ModuleProgramCodeBlock::createStructure(*this, 0, jsNull()));
     evalCodeBlockStructure.set(*this, EvalCodeBlock::createStructure(*this, 0, jsNull()));
@@ -447,8 +448,8 @@ VM::VM(VMType vmType, HeapType heapType)
 
     sentinelSetBucket.set(*this, JSSet::BucketType::createSentinel(*this));
     sentinelMapBucket.set(*this, JSMap::BucketType::createSentinel(*this));
+    sentinelImmutableButterfly.set(*this, JSImmutableButterfly::createSentinel(*this));
 
-    nativeStdFunctionCellStructure.set(*this, NativeStdFunctionCell::createStructure(*this, 0, jsNull()));
     smallStrings.initializeCommonStrings(*this);
 
     Thread::current().setCurrentAtomicStringTable(existingEntryAtomicStringTable);
index e1ef468..0fb7b82 100644 (file)
@@ -571,6 +571,7 @@ public:
     Strong<JSCell> emptyPropertyNameEnumerator;
     Strong<JSCell> sentinelSetBucket;
     Strong<JSCell> sentinelMapBucket;
+    Strong<JSCell> sentinelImmutableButterfly;
 
     std::unique_ptr<PromiseDeferredTimer> promiseDeferredTimer;