[DFG][FTL] Support MapSet / SetAdd intrinsics
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Nov 2017 10:40:34 +0000 (10:40 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Nov 2017 10:40:34 +0000 (10:40 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179858

Reviewed by Saam Barati.

JSTests:

* microbenchmarks/map-has-and-set.js: Added.
(test):
* stress/map-set-check-failure.js: Added.
(shouldBe):
(shouldThrow):
(target):
* stress/map-set-cse.js: Added.
(shouldBe):
(test):
* stress/set-add-check-failure.js: Added.
(shouldBe):
(shouldThrow):
(set shouldThrow):
* stress/set-add-cse.js: Added.
(shouldBe):

Source/JavaScriptCore:

Map.prototype.set and Set.prototype.add uses MapHash value anyway.
By handling them as MapSet and SetAdd DFG nodes and decoupling
MapSet and SetAdd nodes from MapHash DFG node, we have a chance to
remove duplicate MapHash calculation for the same key.

One story is *set-if-not-exists*.

    if (!map.has(key))
        map.set(key, value);

In the above code, both `has` and `set` require hash value for `key`.
If we can change `set` to the series of DFG nodes:

    1: MapHash(key)
    2: MapSet(MapObjectUse:map, Untyped:key, Untyped:value, Int32Use:@1)

we can remove duplicate @1 produced by `has` operation.

This patch improves SixSpeed map-set.es6 and map-set-object.es6 by 20.5% and 20.4% respectively,

                                 baseline                  patched

    map-set.es6             246.2413+-15.2084    ^    204.3679+-11.2408       ^ definitely 1.2049x faster
    map-set-object.es6      266.5075+-17.2289    ^    221.2792+-12.2948       ^ definitely 1.2044x faster

Microbenchmarks

    map-has-and-set         148.1522+-7.6665     ^    131.4552+-7.8846        ^ definitely 1.1270x faster

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileSetAdd):
(JSC::DFG::SpeculativeJIT::compileMapSet):
* dfg/DFGSpeculativeJIT.h:
(JSC::DFG::SpeculativeJIT::callOperation):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileSetAdd):
(JSC::FTL::DFG::LowerDFGToB3::compileMapSet):
* jit/JITOperations.h:
* runtime/HashMapImpl.h:
(JSC::HashMapImpl::addNormalized):
(JSC::HashMapImpl::addNormalizedInternal):
* runtime/Intrinsic.cpp:
(JSC::intrinsicName):
* runtime/Intrinsic.h:
* runtime/MapPrototype.cpp:
(JSC::MapPrototype::finishCreation):
* runtime/SetPrototype.cpp:
(JSC::SetPrototype::finishCreation):

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

29 files changed:
JSTests/ChangeLog
JSTests/microbenchmarks/map-has-and-set.js [new file with mode: 0644]
JSTests/stress/map-set-check-failure.js [new file with mode: 0644]
JSTests/stress/map-set-cse.js [new file with mode: 0644]
JSTests/stress/set-add-check-failure.js [new file with mode: 0644]
JSTests/stress/set-add-cse.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/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
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/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/JITOperations.h
Source/JavaScriptCore/runtime/HashMapImpl.h
Source/JavaScriptCore/runtime/Intrinsic.cpp
Source/JavaScriptCore/runtime/Intrinsic.h
Source/JavaScriptCore/runtime/MapPrototype.cpp
Source/JavaScriptCore/runtime/SetPrototype.cpp

index 96bf4cf..e636656 100644 (file)
@@ -1,5 +1,28 @@
 2017-11-21  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        [DFG][FTL] Support MapSet / SetAdd intrinsics
+        https://bugs.webkit.org/show_bug.cgi?id=179858
+
+        Reviewed by Saam Barati.
+
+        * microbenchmarks/map-has-and-set.js: Added.
+        (test):
+        * stress/map-set-check-failure.js: Added.
+        (shouldBe):
+        (shouldThrow):
+        (target):
+        * stress/map-set-cse.js: Added.
+        (shouldBe):
+        (test):
+        * stress/set-add-check-failure.js: Added.
+        (shouldBe):
+        (shouldThrow):
+        (set shouldThrow):
+        * stress/set-add-cse.js: Added.
+        (shouldBe):
+
+2017-11-21  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         [JSC] Allow poly proto for intrinsic getters
         https://bugs.webkit.org/show_bug.cgi?id=179550
 
diff --git a/JSTests/microbenchmarks/map-has-and-set.js b/JSTests/microbenchmarks/map-has-and-set.js
new file mode 100644 (file)
index 0000000..a233940
--- /dev/null
@@ -0,0 +1,12 @@
+function test()
+{
+    let map = new Map();
+    for (let i = 0; i < 1e6; ++i) {
+        if (!map.has(i))
+            map.set(i, i);
+    }
+    return map
+}
+noInline(test);
+
+test();
diff --git a/JSTests/stress/map-set-check-failure.js b/JSTests/stress/map-set-check-failure.js
new file mode 100644 (file)
index 0000000..6a95327
--- /dev/null
@@ -0,0 +1,35 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+
+var func = Map.prototype.set;
+function target(map)
+{
+    return func.call(map, 42, 42);
+}
+noInline(target);
+
+for (var i = 0; i < 1e6; ++i) {
+    var map = new Map();
+    shouldBe(target(map), map);
+    shouldBe(map.get(42), 42);
+}
+shouldThrow(() => {
+    target(new Set());
+}, `TypeError: Map operation called on non-Map object`);
diff --git a/JSTests/stress/map-set-cse.js b/JSTests/stress/map-set-cse.js
new file mode 100644 (file)
index 0000000..05495a8
--- /dev/null
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+    var map = new Map();
+    var r1 = map.get(42);
+    map.set(42, 42);
+    var r2 = map.get(42);
+    return [r1, r2];
+}
+noInline(test);
+
+for (var i = 0; i < 1e5; ++i) {
+    let [r1, r2] = test();
+    shouldBe(r1, undefined);
+    shouldBe(r2, 42);
+}
diff --git a/JSTests/stress/set-add-check-failure.js b/JSTests/stress/set-add-check-failure.js
new file mode 100644 (file)
index 0000000..32bd6c1
--- /dev/null
@@ -0,0 +1,35 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+
+var func = Set.prototype.add;
+function target(set)
+{
+    return func.call(set, 42);
+}
+noInline(target);
+
+for (var i = 0; i < 1e6; ++i) {
+    var set = new Set();
+    shouldBe(target(set), set);
+    shouldBe(set.has(42), true);
+}
+shouldThrow(() => {
+    target(new Map());
+}, `TypeError: Set operation called on non-Set object`);
diff --git a/JSTests/stress/set-add-cse.js b/JSTests/stress/set-add-cse.js
new file mode 100644 (file)
index 0000000..b2252aa
--- /dev/null
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+    var set = new Set();
+    var r1 = set.has(42);
+    set.add(42);
+    var r2 = set.has(42);
+    return [r1, r2];
+}
+noInline(test);
+
+for (var i = 0; i < 1e5; ++i) {
+    let [r1, r2] = test();
+    shouldBe(r1, false);
+    shouldBe(r2, true);
+}
index db6ac11..ae09767 100644 (file)
@@ -1,5 +1,84 @@
 2017-11-21  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        [DFG][FTL] Support MapSet / SetAdd intrinsics
+        https://bugs.webkit.org/show_bug.cgi?id=179858
+
+        Reviewed by Saam Barati.
+
+        Map.prototype.set and Set.prototype.add uses MapHash value anyway.
+        By handling them as MapSet and SetAdd DFG nodes and decoupling
+        MapSet and SetAdd nodes from MapHash DFG node, we have a chance to
+        remove duplicate MapHash calculation for the same key.
+
+        One story is *set-if-not-exists*.
+
+            if (!map.has(key))
+                map.set(key, value);
+
+        In the above code, both `has` and `set` require hash value for `key`.
+        If we can change `set` to the series of DFG nodes:
+
+            1: MapHash(key)
+            2: MapSet(MapObjectUse:map, Untyped:key, Untyped:value, Int32Use:@1)
+
+        we can remove duplicate @1 produced by `has` operation.
+
+        This patch improves SixSpeed map-set.es6 and map-set-object.es6 by 20.5% and 20.4% respectively,
+
+                                         baseline                  patched
+
+            map-set.es6             246.2413+-15.2084    ^    204.3679+-11.2408       ^ definitely 1.2049x faster
+            map-set-object.es6      266.5075+-17.2289    ^    221.2792+-12.2948       ^ definitely 1.2044x faster
+
+        Microbenchmarks
+
+            map-has-and-set         148.1522+-7.6665     ^    131.4552+-7.8846        ^ definitely 1.1270x faster
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsicCall):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileSetAdd):
+        (JSC::DFG::SpeculativeJIT::compileMapSet):
+        * dfg/DFGSpeculativeJIT.h:
+        (JSC::DFG::SpeculativeJIT::callOperation):
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileSetAdd):
+        (JSC::FTL::DFG::LowerDFGToB3::compileMapSet):
+        * jit/JITOperations.h:
+        * runtime/HashMapImpl.h:
+        (JSC::HashMapImpl::addNormalized):
+        (JSC::HashMapImpl::addNormalizedInternal):
+        * runtime/Intrinsic.cpp:
+        (JSC::intrinsicName):
+        * runtime/Intrinsic.h:
+        * runtime/MapPrototype.cpp:
+        (JSC::MapPrototype::finishCreation):
+        * runtime/SetPrototype.cpp:
+        (JSC::SetPrototype::finishCreation):
+
+2017-11-21  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         [JSC] Allow poly proto for intrinsic getters
         https://bugs.webkit.org/show_bug.cgi?id=179550
 
index 4caaf5f..cb30d02 100644 (file)
@@ -1141,6 +1141,10 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         }
         break;
 
+    case SetAdd:
+    case MapSet:
+        break;
+
     case WeakMapGet:
         forNode(node).makeHeapTop();
         break;
index 7e10a50..b841c7a 100644 (file)
@@ -2904,6 +2904,38 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, int resultOperand, Intrin
         return true;
     }
 
+    case JSSetAddIntrinsic: {
+        if (argumentCountIncludingThis != 2)
+            return false;
+
+        insertChecks();
+        Node* base = get(virtualRegisterForArgument(0, registerOffset));
+        Node* key = get(virtualRegisterForArgument(1, registerOffset));
+        Node* hash = addToGraph(MapHash, key);
+        addToGraph(SetAdd, base, key, hash);
+        set(VirtualRegister(resultOperand), base);
+        return true;
+    }
+
+    case JSMapSetIntrinsic: {
+        if (argumentCountIncludingThis != 3)
+            return false;
+
+        insertChecks();
+        Node* base = get(virtualRegisterForArgument(0, registerOffset));
+        Node* key = get(virtualRegisterForArgument(1, registerOffset));
+        Node* value = get(virtualRegisterForArgument(2, registerOffset));
+        Node* hash = addToGraph(MapHash, key);
+
+        addVarArgChild(base);
+        addVarArgChild(key);
+        addVarArgChild(value);
+        addVarArgChild(hash);
+        addToGraph(Node::VarArg, MapSet, OpInfo(0), OpInfo(0));
+        set(VirtualRegister(resultOperand), base);
+        return true;
+    }
+
     case JSSetBucketHeadIntrinsic:
     case JSMapBucketHeadIntrinsic: {
         ASSERT(argumentCountIncludingThis == 2);
index 1b98c6f..5f37293 100644 (file)
@@ -1643,6 +1643,14 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         return;
     }
 
+    case SetAdd:
+    case MapSet: {
+        // FIXME: Define defs for them to participate in CSE.
+        // https://bugs.webkit.org/show_bug.cgi?id=179911
+        write(MiscFields);
+        return;
+    }
+
     case StringSlice:
         def(PureValue(node));
         return;
index d2b75e1..fc1ab6c 100644 (file)
@@ -344,6 +344,8 @@ bool doesGC(Graph& graph, Node* node)
     case ArraySlice:
     case ArrayIndexOf:
     case ParseInt: // We might resolve a rope even though we don't clobber anything.
+    case SetAdd:
+    case MapSet:
         return true;
         
     case MultiPutByOffset:
index e4471bd..b2373fe 100644 (file)
@@ -1947,6 +1947,18 @@ private:
             break;
         }
 
+        case SetAdd: {
+            fixEdge<SetObjectUse>(node->child1());
+            fixEdge<Int32Use>(node->child3());
+            break;
+        }
+
+        case MapSet: {
+            fixEdge<MapObjectUse>(m_graph.varArgChild(node, 0));
+            fixEdge<Int32Use>(m_graph.varArgChild(node, 3));
+            break;
+        }
+
         case DefineDataProperty: {
             fixEdge<CellUse>(m_graph.varArgChild(node, 0));
             Edge& propertyEdge = m_graph.varArgChild(node, 1);
index fe08c38..6ab5cdc 100644 (file)
@@ -445,6 +445,8 @@ namespace JSC { namespace DFG {
     macro(GetMapBucketNext, NodeResultJS) \
     macro(LoadKeyFromMapBucket, NodeResultJS) \
     macro(LoadValueFromMapBucket, NodeResultJS) \
+    macro(SetAdd, NodeMustGenerate) \
+    macro(MapSet, NodeMustGenerate | NodeHasVarArgs) \
     /* Nodes for JSWeakMap and JSWeakSet */ \
     macro(WeakMapGet, NodeResultJS) \
     \
index a55711d..50802dc 100644 (file)
@@ -2556,6 +2556,22 @@ JSCell* JIT_OPERATION operationJSSetFindBucket(ExecState* exec, JSCell* map, Enc
     return *bucket;
 }
 
+// FIXME: Add NormalizeMapKey DFG node.
+// https://bugs.webkit.org/show_bug.cgi?id=179912
+void JIT_OPERATION operationSetAdd(ExecState* exec, JSCell* set, EncodedJSValue key, int32_t hash)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+    jsCast<JSSet*>(set)->addNormalized(exec, normalizeMapKey(JSValue::decode(key)), JSValue(), hash);
+}
+
+void JIT_OPERATION operationMapSet(ExecState* exec, JSCell* map, EncodedJSValue key, EncodedJSValue value, int32_t hash)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+    jsCast<JSMap*>(map)->addNormalized(exec, normalizeMapKey(JSValue::decode(key)), JSValue::decode(value), hash);
+}
+
 EncodedJSValue JIT_OPERATION operationGetPrototypeOfObject(ExecState* exec, JSObject* thisObject)
 {
     VM& vm = exec->vm();
index 5150d22..917fa15 100644 (file)
@@ -138,6 +138,8 @@ void JIT_OPERATION operationDefineAccessorProperty(ExecState*, JSObject*, Encode
 void JIT_OPERATION operationDefineAccessorPropertyString(ExecState*, JSObject*, JSString*, JSObject*, JSObject*, int32_t) WTF_INTERNAL;
 void JIT_OPERATION operationDefineAccessorPropertyStringIdent(ExecState*, JSObject*, UniquedStringImpl*, JSObject*, JSObject*, int32_t) WTF_INTERNAL;
 void JIT_OPERATION operationDefineAccessorPropertySymbol(ExecState*, JSObject*, Symbol*, JSObject*, JSObject*, int32_t) WTF_INTERNAL;
+void JIT_OPERATION operationSetAdd(ExecState*, JSCell*, EncodedJSValue, int32_t) WTF_INTERNAL;
+void JIT_OPERATION operationMapSet(ExecState*, JSCell*, EncodedJSValue, EncodedJSValue, int32_t) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationArrayPush(ExecState*, EncodedJSValue encodedValue, JSArray*) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationArrayPushMultiple(ExecState*, JSArray*, void* buffer, int32_t elementCount) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationArrayPushDouble(ExecState*, double value, JSArray*) WTF_INTERNAL;
index d830578..6d5db77 100644 (file)
@@ -1173,6 +1173,8 @@ private:
         case PutDynamicVar:
         case NukeStructureAndSetButterfly:
         case InitializeEntrypointArguments:
+        case SetAdd:
+        case MapSet:
             break;
             
         // This gets ignored because it only pretends to produce a value.
index fe708f4..d383b9a 100644 (file)
@@ -535,6 +535,10 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
         return true;
     }
 
+    case SetAdd:
+    case MapSet:
+        return false;
+
     case LastNodeType:
         RELEASE_ASSERT_NOT_REACHED();
         return false;
index 618bac1..e1621d6 100644 (file)
@@ -10836,6 +10836,44 @@ void SpeculativeJIT::compileThrowStaticError(Node* node)
     noResult(node);
 }
 
+void SpeculativeJIT::compileSetAdd(Node* node)
+{
+    SpeculateCellOperand set(this, node->child1());
+    JSValueOperand key(this, node->child2());
+    SpeculateInt32Operand hash(this, node->child3());
+
+    GPRReg setGPR = set.gpr();
+    JSValueRegs keyRegs = key.jsValueRegs();
+    GPRReg hashGPR = hash.gpr();
+
+    speculateSetObject(node->child1(), setGPR);
+
+    flushRegisters();
+    callOperation(operationSetAdd, setGPR, keyRegs, hashGPR);
+    m_jit.exceptionCheck();
+    noResult(node);
+}
+
+void SpeculativeJIT::compileMapSet(Node* node)
+{
+    SpeculateCellOperand map(this, m_jit.graph().varArgChild(node, 0));
+    JSValueOperand key(this, m_jit.graph().varArgChild(node, 1));
+    JSValueOperand value(this, m_jit.graph().varArgChild(node, 2));
+    SpeculateInt32Operand hash(this, m_jit.graph().varArgChild(node, 3));
+
+    GPRReg mapGPR = map.gpr();
+    JSValueRegs keyRegs = key.jsValueRegs();
+    JSValueRegs valueRegs = value.jsValueRegs();
+    GPRReg hashGPR = hash.gpr();
+
+    speculateMapObject(m_jit.graph().varArgChild(node, 0), mapGPR);
+
+    flushRegisters();
+    callOperation(operationMapSet, mapGPR, keyRegs, valueRegs, hashGPR);
+    m_jit.exceptionCheck();
+    noResult(node);
+}
+
 void SpeculativeJIT::compileWeakMapGet(Node* node)
 {
     SpeculateCellOperand weakMap(this, node->child1());
index 99ebf18..2956494 100644 (file)
@@ -1932,11 +1932,21 @@ public:
         m_jit.setupArgumentsWithExecState(arg1, arg2.payloadGPR());
         return appendCall(operation);
     }
+    JITCompiler::Call callOperation(V_JITOperation_ECJZ operation, GPRReg arg1, JSValueRegs arg2, GPRReg arg3)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2.payloadGPR(), arg3);
+        return appendCall(operation);
+    }
     JITCompiler::Call callOperation(V_JITOperation_ECJJ operation, GPRReg arg1, GPRReg arg2, GPRReg arg3)
     {
         m_jit.setupArgumentsWithExecState(arg1, arg2, arg3);
         return appendCall(operation);
     }
+    JITCompiler::Call callOperation(V_JITOperation_ECJJZ operation, GPRReg arg1, JSValueRegs arg2, JSValueRegs arg3, GPRReg arg4)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2.payloadGPR(), arg3.payloadGPR(), arg4);
+        return appendCall(operation);
+    }
     JITCompiler::Call callOperation(V_JITOperation_ECCJ operation, GPRReg arg1, GPRReg arg2, JSValueRegs arg3)
     {
         m_jit.setupArgumentsWithExecState(arg1, arg2, arg3.payloadGPR());
@@ -2502,6 +2512,16 @@ public:
         m_jit.setupArgumentsWithExecState(arg1, arg2.payloadGPR(), arg2.tagGPR(), arg3.payloadGPR(), arg3.tagGPR());
         return appendCall(operation);
     }
+    JITCompiler::Call callOperation(V_JITOperation_ECJZ operation, GPRReg arg1, JSValueRegs arg2, GPRReg arg3)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2.payloadGPR(), arg2.tagGPR(), arg3);
+        return appendCall(operation);
+    }
+    JITCompiler::Call callOperation(V_JITOperation_ECJJZ operation, GPRReg arg1, JSValueRegs arg2, JSValueRegs arg3, GPRReg arg4)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2.payloadGPR(), arg2.tagGPR(), arg3.payloadGPR(), arg3.tagGPR(), arg4);
+        return appendCall(operation);
+    }
     JITCompiler::Call callOperation(V_JITOperation_ECCJ operation, GPRReg arg1, GPRReg arg2, JSValueRegs arg3)
     {
         m_jit.setupArgumentsWithExecState(arg1, arg2, EABI_32BIT_DUMMY_ARG arg3.payloadGPR(), arg3.tagGPR());
@@ -2876,6 +2896,8 @@ public:
     void compileCheckSubClass(Node*);
     void compileGetMapBucketHead(Node*);
     void compileGetMapBucketNext(Node*);
+    void compileSetAdd(Node*);
+    void compileMapSet(Node*);
     void compileWeakMapGet(Node*);
     void compileLoadKeyFromMapBucket(Node*);
     void compileLoadValueFromMapBucket(Node*);
index 9dd9297..3e53f96 100644 (file)
@@ -4875,6 +4875,14 @@ void SpeculativeJIT::compile(Node* node)
         compileLoadValueFromMapBucket(node);
         break;
 
+    case SetAdd:
+        compileSetAdd(node);
+        break;
+
+    case MapSet:
+        compileMapSet(node);
+        break;
+
     case WeakMapGet:
         compileWeakMapGet(node);
         break;
index ad1a21d..b2f90dd 100644 (file)
@@ -5261,6 +5261,14 @@ void SpeculativeJIT::compile(Node* node)
         compileLoadValueFromMapBucket(node);
         break;
 
+    case SetAdd:
+        compileSetAdd(node);
+        break;
+
+    case MapSet:
+        compileMapSet(node);
+        break;
+
     case WeakMapGet:
         compileWeakMapGet(node);
         break;
index aa55239..e1b467c 100644 (file)
@@ -207,6 +207,8 @@ inline CapabilityLevel canCompile(Node* node)
     case GetMapBucketNext:
     case LoadKeyFromMapBucket:
     case LoadValueFromMapBucket:
+    case SetAdd:
+    case MapSet:
     case WeakMapGet:
     case IsEmpty:
     case IsUndefined:
index b461163..f954e87 100644 (file)
@@ -1052,6 +1052,12 @@ private:
         case LoadValueFromMapBucket:
             compileLoadValueFromMapBucket();
             break;
+        case SetAdd:
+            compileSetAdd();
+            break;
+        case MapSet:
+            compileMapSet();
+            break;
         case WeakMapGet:
             compileWeakMapGet();
             break;
@@ -8786,6 +8792,25 @@ private:
         setJSValue(m_out.load64(mapBucket, m_heaps.HashMapBucket_key));
     }
 
+    void compileSetAdd()
+    {
+        LValue set = lowSetObject(m_node->child1());
+        LValue key = lowJSValue(m_node->child2());
+        LValue hash = lowInt32(m_node->child3());
+
+        vmCall(Void, m_out.operation(operationSetAdd), m_callFrame, set, key, hash);
+    }
+
+    void compileMapSet()
+    {
+        LValue map = lowMapObject(m_graph.varArgChild(m_node, 0));
+        LValue key = lowJSValue(m_graph.varArgChild(m_node, 1));
+        LValue value = lowJSValue(m_graph.varArgChild(m_node, 2));
+        LValue hash = lowInt32(m_graph.varArgChild(m_node, 3));
+
+        vmCall(Void, m_out.operation(operationMapSet), m_callFrame, map, key, value, hash);
+    }
+
     void compileWeakMapGet()
     {
         LValue weakMap = lowWeakMapObject(m_node->child1());
index 94714bf..6a5941c 100644 (file)
@@ -265,7 +265,9 @@ typedef void (JIT_OPERATION *V_JITOperation_ECIZJJ)(ExecState*, JSCell*, Uniqued
 typedef void (JIT_OPERATION *V_JITOperation_ECJZC)(ExecState*, JSCell*, EncodedJSValue, int32_t, JSCell*);
 typedef void (JIT_OPERATION *V_JITOperation_ECCIcf)(ExecState*, JSCell*, JSCell*, InlineCallFrame*);
 typedef void (JIT_OPERATION *V_JITOperation_ECJ)(ExecState*, JSCell*, EncodedJSValue);
+typedef void (JIT_OPERATION *V_JITOperation_ECJZ)(ExecState*, JSCell*, EncodedJSValue, int32_t);
 typedef void (JIT_OPERATION *V_JITOperation_ECJJ)(ExecState*, JSCell*, EncodedJSValue, EncodedJSValue);
+typedef void (JIT_OPERATION *V_JITOperation_ECJJZ)(ExecState*, JSCell*, EncodedJSValue, EncodedJSValue, int32_t);
 typedef void (JIT_OPERATION *V_JITOperation_ECPSPS)(ExecState*, JSCell*, void*, size_t, void*, size_t);
 typedef void (JIT_OPERATION *V_JITOperation_ECZ)(ExecState*, JSCell*, int32_t);
 typedef void (JIT_OPERATION *V_JITOperation_ECC)(ExecState*, JSCell*, JSCell*);
index 2887e60..965c4e7 100644 (file)
@@ -441,6 +441,18 @@ public:
             rehash(exec);
     }
 
+    ALWAYS_INLINE void addNormalized(ExecState* exec, JSValue key, JSValue value, uint32_t hash)
+    {
+        ASSERT_WITH_MESSAGE(normalizeMapKey(key) == key, "We expect normalized values flowing into this function.");
+        ASSERT_WITH_MESSAGE(jsMapHash(exec, exec->vm(), key) == hash, "We expect hash value is what we expect.");
+
+        addNormalizedInternal(exec->vm(), key, value, hash, [&] (HashMapBucketType* bucket) {
+            return !isDeleted(bucket) && areKeysEqual(exec, key, bucket->key());
+        });
+        if (shouldRehashAfterAdd())
+            rehash(exec);
+    }
+
     ALWAYS_INLINE bool remove(ExecState* exec, JSValue key)
     {
         HashMapBucketType** bucket = findBucket(exec, key);
@@ -556,14 +568,22 @@ private:
     template<typename CanUseBucket>
     ALWAYS_INLINE void addNormalizedInternal(ExecState* exec, JSValue key, JSValue value, const CanUseBucket& canUseBucket)
     {
-        ASSERT_WITH_MESSAGE(normalizeMapKey(key) == key, "We expect normalized values flowing into this function.");
-
         VM& vm = exec->vm();
         auto scope = DECLARE_THROW_SCOPE(vm);
 
-        const uint32_t mask = m_capacity - 1;
-        uint32_t index = jsMapHash(exec, vm, key) & mask;
+        uint32_t hash = jsMapHash(exec, vm, key);
         RETURN_IF_EXCEPTION(scope, void());
+        scope.release();
+        addNormalizedInternal(vm, key, value, hash, canUseBucket);
+    }
+
+    template<typename CanUseBucket>
+    ALWAYS_INLINE void addNormalizedInternal(VM& vm, JSValue key, JSValue value, uint32_t hash, const CanUseBucket& canUseBucket)
+    {
+        ASSERT_WITH_MESSAGE(normalizeMapKey(key) == key, "We expect normalized values flowing into this function.");
+
+        const uint32_t mask = m_capacity - 1;
+        uint32_t index = hash & mask;
         HashMapBucketType** buffer = this->buffer();
         HashMapBucketType* bucket = buffer[index];
         while (!isEmpty(bucket)) {
index 74f8d9e..3931871 100644 (file)
@@ -143,6 +143,8 @@ const char* intrinsicName(Intrinsic intrinsic)
         return "JSMapGetIntrinsic";
     case JSMapHasIntrinsic:
         return "JSMapHasIntrinsic";
+    case JSMapSetIntrinsic:
+        return "JSMapSetIntrinsic";
     case JSMapBucketHeadIntrinsic:
         return "JSMapBucketHeadIntrinsic";
     case JSMapBucketNextIntrinsic:
@@ -153,6 +155,8 @@ const char* intrinsicName(Intrinsic intrinsic)
         return "JSMapBucketValueIntrinsic";
     case JSSetHasIntrinsic:
         return "JSSetHasIntrinsic";
+    case JSSetAddIntrinsic:
+        return "JSSetAddIntrinsic";
     case JSSetBucketHeadIntrinsic:
         return "JSSetBucketHeadIntrinsic";
     case JSSetBucketNextIntrinsic:
index 470d968..dca7c39 100644 (file)
@@ -84,11 +84,13 @@ enum Intrinsic {
     BoundThisNoArgsFunctionCallIntrinsic,
     JSMapGetIntrinsic,
     JSMapHasIntrinsic,
+    JSMapSetIntrinsic,
     JSMapBucketHeadIntrinsic,
     JSMapBucketNextIntrinsic,
     JSMapBucketKeyIntrinsic,
     JSMapBucketValueIntrinsic,
     JSSetHasIntrinsic,
+    JSSetAddIntrinsic,
     JSSetBucketHeadIntrinsic,
     JSSetBucketNextIntrinsic,
     JSSetBucketKeyIntrinsic,
index fddf1b0..ea9a76b 100644 (file)
@@ -66,10 +66,10 @@ void MapPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->deleteKeyword, mapProtoFuncDelete, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->get, mapProtoFuncGet, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, JSMapGetIntrinsic);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->has, mapProtoFuncHas, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, JSMapHasIntrinsic);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->set, mapProtoFuncSet, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->set, mapProtoFuncSet, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, JSMapSetIntrinsic);
 
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().getPrivateName(), mapProtoFuncGet, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, JSMapGetIntrinsic);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().setPrivateName(), mapProtoFuncSet, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().setPrivateName(), mapProtoFuncSet, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, JSMapSetIntrinsic);
 
     JSFunction* entries = JSFunction::create(vm, mapPrototypeEntriesCodeGenerator(vm), globalObject);
     putDirectWithoutTransition(vm, vm.propertyNames->builtinNames().entriesPublicName(), entries, static_cast<unsigned>(PropertyAttribute::DontEnum));
index b413901..5db52c2 100644 (file)
@@ -61,12 +61,12 @@ void SetPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
     ASSERT(inherits(vm, info()));
     didBecomePrototype();
 
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->add, setProtoFuncAdd, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->add, setProtoFuncAdd, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, JSSetAddIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->clear, setProtoFuncClear, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->deleteKeyword, setProtoFuncDelete, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->has, setProtoFuncHas, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, JSSetHasIntrinsic);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().hasPrivateName(), setProtoFuncHas, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, JSSetHasIntrinsic);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().addPrivateName(), setProtoFuncAdd, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().addPrivateName(), setProtoFuncAdd, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, JSSetAddIntrinsic);
 
     JSFunction* values = JSFunction::create(vm, setPrototypeValuesCodeGenerator(vm), globalObject);
     putDirectWithoutTransition(vm, vm.propertyNames->builtinNames().valuesPublicName(), values, static_cast<unsigned>(PropertyAttribute::DontEnum));