[JSC] add DFG/FTL support for op_to_property_key
https://bugs.webkit.org/show_bug.cgi?id=206368

JSTests:

Reviewed by Saam Barati.

* stress/class-fields-to-property-key-const-string-ftl.js: Added.
* stress/class-fields-to-property-key-const-symbol-ftl.js: Added.
* stress/class-fields-to-property-key-slow-object-tostring-ftl.js: Added.
* stress/class-fields-to-property-key-slow-object-valueof-ftl.js: Added.
* stress/class-fields-to-property-key-string-object-ftl.js: Added.
* stress/class-fields-to-property-key-string-or-string-object-ftl.js: Added.

Source/JavaScriptCore:

Reviewed by Saam Barati.

Implement DFG/FTL support for the op_to_property_key opcode. This operates
similar to the LLInt and base JIT implementations, in which we avoid invoking
the full ToPropertyKey operation if the source operand is already a String or
Symbol at runtime.

If DFG/FTL are confident the value will be a String or Symbol at compile time,
the operation is omitted entirely in the final graph.

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* 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.h:
(JSC::DFG::Node::convertToToString):
* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileToPrimitive):
(JSC::DFG::SpeculativeJIT::compileToPropertyKey):
* dfg/DFGSpeculativeJIT.h:
* 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::compileToPropertyKey):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@254801 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog
index d0f6afc..6109d9f 100644
--- a/JSTests/ChangeLog
+++ b/JSTests/ChangeLog
@@ -1,3 +1,17 @@
+2020-01-18  Caitlin Potter  <caitp@igalia.com>
+
+        [JSC] add DFG/FTL support for op_to_property_key
+        https://bugs.webkit.org/show_bug.cgi?id=206368
+
+        Reviewed by Saam Barati.
+
+        * stress/class-fields-to-property-key-const-string-ftl.js: Added.
+        * stress/class-fields-to-property-key-const-symbol-ftl.js: Added.
+        * stress/class-fields-to-property-key-slow-object-tostring-ftl.js: Added.
+        * stress/class-fields-to-property-key-slow-object-valueof-ftl.js: Added.
+        * stress/class-fields-to-property-key-string-object-ftl.js: Added.
+        * stress/class-fields-to-property-key-string-or-string-object-ftl.js: Added.
+
 2020-01-17  Saam Barati  <sbarati@apple.com>
 
         Air O0 should have better stack allocation
diff --git a/JSTests/stress/class-fields-to-property-key-const-string-ftl.js b/JSTests/stress/class-fields-to-property-key-const-string-ftl.js
new file mode 100644
index 0000000..e64aff1
--- /dev/null
+++ b/JSTests/stress/class-fields-to-property-key-const-string-ftl.js
@@ -0,0 +1,25 @@
+//@ requireOptions("--useClassFields=true")
+//@ if isFTLEnabled then runFTLNoCJIT else skip end
+
+let ftlTrue = $vm.ftlTrue;
+let didFTLCompile = false;
+function constFoldString(i) {
+    class C {
+        ["foo"] = i;
+    }
+    didFTLCompile = ftlTrue();
+    let c = new C();
+    if (c.foo !== i)
+        throw new Error(`Failed on iteration ${i}\n${JSON.stringify(c)}`);
+}
+noInline(constFoldString);
+
+let i = 0;
+let maxTries = 10000;
+for (; i < maxTries && !numberOfDFGCompiles(constFoldString) && !didFTLCompile; ++i) {
+    optimizeNextInvocation(constFoldString);
+    constFoldString(i);
+}
+
+if (i >= maxTries)
+    throw new Error("Failed to compile constFoldString with DFG JIT");
diff --git a/JSTests/stress/class-fields-to-property-key-const-symbol-ftl.js b/JSTests/stress/class-fields-to-property-key-const-symbol-ftl.js
new file mode 100644
index 0000000..8e1b2e4
--- /dev/null
+++ b/JSTests/stress/class-fields-to-property-key-const-symbol-ftl.js
@@ -0,0 +1,26 @@
+//@ requireOptions("--useClassFields=true")
+//@ if isFTLEnabled then runFTLNoCJIT else skip end
+
+let ftlTrue = $vm.ftlTrue;
+let didFTLCompile = false;
+let symbol = Symbol("test");
+function constFoldSymbol(i) {
+    class C {
+        [symbol] = i;
+    }
+    didFTLCompile = ftlTrue();
+    let c = new C();
+    if (c[symbol] !== i)
+        throw new Error(`Failed on iteration ${i}\n${JSON.stringify(c)}`);
+}
+noInline(constFoldSymbol);
+
+let i = 0;
+let maxTries = 10000;
+for (; i < maxTries && !numberOfDFGCompiles(constFoldSymbol) && !didFTLCompile; ++i) {
+    optimizeNextInvocation(constFoldSymbol);
+    constFoldSymbol(i);
+}
+
+if (i >= maxTries)
+    throw new Error("Failed to compile with DFG JIT");
diff --git a/JSTests/stress/class-fields-to-property-key-slow-object-tostring-ftl.js b/JSTests/stress/class-fields-to-property-key-slow-object-tostring-ftl.js
new file mode 100644
index 0000000..d9b7cc4
--- /dev/null
+++ b/JSTests/stress/class-fields-to-property-key-slow-object-tostring-ftl.js
@@ -0,0 +1,33 @@
+//@ requireOptions("--useClassFields=true")
+//@ if isFTLEnabled then runFTLNoCJIT else skip end
+
+let ftlTrue = $vm.ftlTrue;
+let didFTLCompile = false;
+function slowObjectValueOf(i) {
+    let getToStringCalled = false;
+    let valueOfCalled = false;
+    let slowObject = {
+        get toString() { getToStringCalled = true; },
+        valueOf() { valueOfCalled = true; return "test"; }
+    };
+
+    class C {
+        [slowObject] = i;
+    }
+    didFTLCompile = ftlTrue();
+    if (!getToStringCalled || !valueOfCalled)
+        throw new Error(`Failed on iteration ${i} (getToStringCalled === ${getToStringCalled}, valueOfCalled == ${valueOfCalled})`);
+    let c = new C();
+    if (c.test !== i)
+        throw new Error(`Failed on iteration ${i}\n${JSON.stringify(c)}`);
+}
+
+let i = 0;
+let maxTries = 10000;
+for (i = 0; i < maxTries && !numberOfDFGCompiles(slowObjectValueOf) && !didFTLCompile; ++i) {
+    optimizeNextInvocation(slowObjectValueOf);
+    slowObjectValueOf(i);
+}
+
+if (i >= maxTries)
+    throw new Error("Failed to compile slowObjectValueOf with DFG JIT");
diff --git a/JSTests/stress/class-fields-to-property-key-slow-object-valueof-ftl.js b/JSTests/stress/class-fields-to-property-key-slow-object-valueof-ftl.js
new file mode 100644
index 0000000..2168b52
--- /dev/null
+++ b/JSTests/stress/class-fields-to-property-key-slow-object-valueof-ftl.js
@@ -0,0 +1,33 @@
+//@ requireOptions("--useClassFields=true")
+//@ if isFTLEnabled then runFTLNoCJIT else skip end
+
+let ftlTrue = $vm.ftlTrue;
+let didFTLCompile = false;
+function slowObjectValueOf(i) {
+    let getToStringCalled = false;
+    let valueOfCalled = false;
+    let slowObject = {
+        get toString() { getToStringCalled = true; },
+        valueOf() { valueOfCalled = true; return "test"; }
+    };
+
+    class C {
+        [slowObject] = i;
+    }
+    didFTLCompile = ftlTrue();
+    if (!getToStringCalled || !valueOfCalled)
+        throw new Error(`Failed on iteration ${i} (getToStringCalled === ${getToStringCalled}, valueOfCalled == ${valueOfCalled})`);
+    let c = new C();
+    if (c.test !== i)
+        throw new Error(`Failed on iteration ${i}\n${JSON.stringify(c)}`);
+}
+
+let i = 0;
+let maxTries = 10000;
+for (i = 0; i < !maxTries && !numberOfDFGCompiles(slowObjectValueOf) && !didFTLCompile; ++i) {
+    optimizeNextInvocation(slowObjectValueOf);
+    slowObjectValueOf(i);
+}
+
+if (i >= maxTries)
+    throw new Error("Failed to compile slowObjectValueOf with DFG JIT");
diff --git a/JSTests/stress/class-fields-to-property-key-string-object-ftl.js b/JSTests/stress/class-fields-to-property-key-string-object-ftl.js
new file mode 100644
index 0000000..86a089c
--- /dev/null
+++ b/JSTests/stress/class-fields-to-property-key-string-object-ftl.js
@@ -0,0 +1,25 @@
+//@ requireOptions("--useClassFields=true")
+//@ if isFTLEnabled then runFTLNoCJIT else skip end
+
+let ftlTrue = $vm.ftlTrue;
+let didFTLCompile = false;
+function stringObjectToString(i) {
+    class C {
+        [new String("foo")] = i;
+    }
+    didFTLCompile = ftlTrue();
+    let c = new C();
+    if (c.foo !== i)
+        throw new Error(`Failed on iteration ${i}\n${JSON.stringify(c)}`);
+}
+noInline(stringObjectToString);
+
+let i = 0;
+let maxTries = 30000;
+for (; i < maxTries && !numberOfDFGCompiles(stringObjectToString) && !didFTLCompile; ++i) {
+    optimizeNextInvocation(stringObjectToString);
+    stringObjectToString(i);
+}
+
+if (i >= maxTries)
+    throw new Error("Failed to compile stringObjectToString with DFG JIT");
diff --git a/JSTests/stress/class-fields-to-property-key-string-or-string-object-ftl.js b/JSTests/stress/class-fields-to-property-key-string-or-string-object-ftl.js
new file mode 100644
index 0000000..c53f725
--- /dev/null
+++ b/JSTests/stress/class-fields-to-property-key-string-or-string-object-ftl.js
@@ -0,0 +1,28 @@
+//@ requireOptions("--useClassFields=true")
+//@ if isFTLEnabled then runFTLNoCJIT else skip end
+
+let ftlTrue = $vm.ftlTrue;
+let didFTLCompile = false;
+function key(i) {
+    return (i & 1) ? new String("foo") : "foo";
+}
+function stringOrStringObjectToString(i) {
+    class C {
+        [key(i)] = i;
+    }
+    didFTLCompile = ftlTrue();
+    let c = new C();
+    if (c.foo !== i)
+        throw new Error(`Failed on iteration ${i}\n${JSON.stringify(c)}`);
+}
+noInline(stringOrStringObjectToString);
+
+let i = 0;
+let maxTries = 30000;
+for (; i < maxTries && !numberOfDFGCompiles(stringOrStringObjectToString) && !didFTLCompile; ++i) {
+    optimizeNextInvocation(stringOrStringObjectToString);
+    stringOrStringObjectToString(i);
+}
+
+if (i >= maxTries)
+    throw new Error("Failed to compile stringOrStringObjectToString with DFG JIT");
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index a68f127..01ba9f8 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,54 @@
+2020-01-18  Caitlin Potter  <caitp@igalia.com>
+
+        [JSC] add DFG/FTL support for op_to_property_key
+        https://bugs.webkit.org/show_bug.cgi?id=206368
+
+        Reviewed by Saam Barati. 
+
+        Implement DFG/FTL support for the op_to_property_key opcode. This operates
+        similar to the LLInt and base JIT implementations, in which we avoid invoking
+        the full ToPropertyKey operation if the source operand is already a String or
+        Symbol at runtime.
+
+        If DFG/FTL are confident the value will be a String or Symbol at compile time,
+        the operation is omitted entirely in the final graph.
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGCapabilities.cpp:
+        (JSC::DFG::capabilityLevel):
+        * 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.h:
+        (JSC::DFG::Node::convertToToString):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileToPrimitive):
+        (JSC::DFG::SpeculativeJIT::compileToPropertyKey):
+        * dfg/DFGSpeculativeJIT.h:
+        * 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::compileToPropertyKey):
+
 2020-01-17  Saam Barati  <sbarati@apple.com>
 
         Air O0 should have better stack allocation
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index e5284ad..7be7e2c 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -2531,6 +2531,20 @@
         break;
     }
 
+    case ToPropertyKey: {
+        if (!(forNode(node->child1()).m_type & ~(SpecString | SpecSymbol))) {
+            m_state.setShouldTryConstantFolding(true);
+            didFoldClobberWorld();
+            setForNode(node, forNode(node->child1()));
+            break;
+        }
+
+        clobberWorld();
+
+        setTypeForNode(node, SpecString | SpecSymbol);
+        break;
+    }
+
     case ToNumber: {
         JSValue childConst = forNode(node->child1()).value();
         if (childConst && childConst.isNumber()) {
diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
index bc01cfb..d1fa0b3 100644
--- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
@@ -5603,7 +5603,14 @@
             set(bytecode.m_dst, addToGraph(ToPrimitive, value));
             NEXT_OPCODE(op_to_primitive);
         }
-            
+
+        case op_to_property_key: {
+            auto bytecode = currentInstruction->as<OpToPropertyKey>();
+            Node* value = get(bytecode.m_src);
+            set(bytecode.m_dst, addToGraph(ToPropertyKey, value));
+            NEXT_OPCODE(op_to_property_key);
+        }
+
         case op_strcat: {
             auto bytecode = currentInstruction->as<OpStrcat>();
             int startOperand = bytecode.m_src.offset();
diff --git a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
index ce5f6aa..759b778 100644
--- a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
+++ b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp
@@ -282,6 +282,7 @@
     case op_new_regexp:
     case op_get_internal_field:
     case op_put_internal_field:
+    case op_to_property_key:
     case op_unreachable:
     case op_super_sampler_begin:
     case op_super_sampler_end:
@@ -293,7 +294,6 @@
 
     case op_yield:
     case op_create_generator_frame_environment:
-    case op_to_property_key: // FIXME: op_to_property_key will need DFG/FTL support to ship class fields.
     case llint_program_prologue:
     case llint_eval_prologue:
     case llint_module_program_prologue:
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h
index eee9bae..81b0014 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.h
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h
@@ -649,6 +649,7 @@
     case ConstructVarargs:
     case ConstructForwardVarargs:
     case ToPrimitive:
+    case ToPropertyKey:
     case InByVal:
     case InById:
     case HasOwnProperty:
diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index cebb3c8..d8af035 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -717,6 +717,15 @@
                 break;
             }
 
+            case ToPropertyKey: {
+                if (m_state.forNode(node->child1()).m_type & ~(SpecString | SpecSymbol))
+                    break;
+
+                node->convertToIdentity();
+                changed = true;
+                break;
+            }
+
             case ToThis: {
                 ToThisResult result = isToThisAnIdentity(m_graph.m_vm, m_graph.isStrictModeFor(node->origin.semantic), m_state.forNode(node->child1()));
                 if (result == ToThisResult::Identity) {
diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
index 1f30dff..6ee725a 100644
--- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
@@ -336,6 +336,7 @@
     case ToNumeric:
     case ToObject:
     case ToPrimitive:
+    case ToPropertyKey:
     case ToThis:
     case TryGetById:
     case CreateThis:
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 922d35b..c79cd7c 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -1391,6 +1391,38 @@
             break;
         }
 
+        case ToPropertyKey: {
+            if (node->child1()->shouldSpeculateString()) {
+                fixEdge<StringUse>(node->child1());
+                node->convertToIdentity();
+
+                return;
+            }
+
+            if (node->child1()->shouldSpeculateSymbol()) {
+                fixEdge<SymbolUse>(node->child1());
+                node->convertToIdentity();
+                return;
+            }
+
+            if (node->child1()->shouldSpeculateStringObject()
+                && m_graph.canOptimizeStringObjectAccess(node->origin.semantic)) {
+                addCheckStructureForOriginalStringObjectUse(StringObjectUse, node->origin, node->child1().node());
+                fixEdge<StringObjectUse>(node->child1());
+                node->convertToToString();
+                return;
+            }
+
+            if (node->child1()->shouldSpeculateStringOrStringObject()
+                && m_graph.canOptimizeStringObjectAccess(node->origin.semantic)) {
+                addCheckStructureForOriginalStringObjectUse(StringOrStringObjectUse, node->origin, node->child1().node());
+                fixEdge<StringOrStringObjectUse>(node->child1());
+                node->convertToToString();
+                return;
+            }
+            break;
+        }
+
         case ToNumber: {
             fixupToNumber(node);
             break;
diff --git a/Source/JavaScriptCore/dfg/DFGNode.h b/Source/JavaScriptCore/dfg/DFGNode.h
index 517af10..344a2ab 100644
--- a/Source/JavaScriptCore/dfg/DFGNode.h
+++ b/Source/JavaScriptCore/dfg/DFGNode.h
@@ -711,7 +711,7 @@
     
     void convertToToString()
     {
-        ASSERT(m_op == ToPrimitive || m_op == StringValueOf);
+        ASSERT(m_op == ToPrimitive || m_op == StringValueOf || m_op == ToPropertyKey);
         m_op = ToString;
     }
 
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index 7c6d1b2..547fe4d 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -402,6 +402,7 @@
     macro(TypeOf, NodeResultJS) \
     macro(LogicalNot, NodeResultBoolean) \
     macro(ToPrimitive, NodeResultJS | NodeMustGenerate) \
+    macro(ToPropertyKey, NodeResultJS | NodeMustGenerate) \
     macro(ToString, NodeResultJS | NodeMustGenerate) \
     macro(ToNumber, NodeResultJS | NodeMustGenerate) \
     macro(ToNumeric, NodeResultJS | NodeMustGenerate) \
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp
index f7ac7ce..2fdb9c9 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp
@@ -1649,6 +1649,15 @@
     return JSValue::encode(JSValue::decode(value).toPrimitive(globalObject));
 }
 
+EncodedJSValue JIT_OPERATION operationToPropertyKey(JSGlobalObject* globalObject, EncodedJSValue value)
+{
+    VM& vm = globalObject->vm();
+    CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
+    JITOperationPrologueCallFrameTracer tracer(vm, callFrame);
+
+    return JSValue::encode(JSValue::decode(value).toPropertyKeyValue(globalObject));
+}
+
 EncodedJSValue JIT_OPERATION operationToNumber(JSGlobalObject* globalObject, EncodedJSValue value)
 {
     VM& vm = globalObject->vm();
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.h b/Source/JavaScriptCore/dfg/DFGOperations.h
index a33ede9..0f1c875 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.h
+++ b/Source/JavaScriptCore/dfg/DFGOperations.h
@@ -91,6 +91,7 @@
 EncodedJSValue JIT_OPERATION operationGetByValObjectString(JSGlobalObject*, JSCell*, JSCell* string) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationGetByValObjectSymbol(JSGlobalObject*, JSCell*, JSCell* symbol) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationToPrimitive(JSGlobalObject*, EncodedJSValue) WTF_INTERNAL;
+EncodedJSValue JIT_OPERATION operationToPropertyKey(JSGlobalObject*, EncodedJSValue) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationToNumber(JSGlobalObject*, EncodedJSValue) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationToNumeric(JSGlobalObject*, EncodedJSValue) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationGetByValWithThis(JSGlobalObject*, EncodedJSValue, EncodedJSValue, EncodedJSValue) WTF_INTERNAL;
diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
index f92ae9d..9b8d60d 100644
--- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
@@ -575,6 +575,13 @@
             break;
         }
 
+        case ToPropertyKey: {
+            SpeculatedType child = node->child1()->prediction();
+            if (child)
+                changed |= mergePrediction(resultOfToPropertyKey(child));
+            break;
+        }
+
         case NormalizeMapKey: {
             SpeculatedType prediction = node->child1()->prediction();
             if (prediction)
@@ -1222,6 +1229,7 @@
         case GetByVal:
         case ToThis:
         case ToPrimitive: 
+        case ToPropertyKey:
         case NormalizeMapKey:
         case AtomicsAdd:
         case AtomicsAnd:
@@ -1431,6 +1439,18 @@
         return type;
     }
 
+    SpeculatedType resultOfToPropertyKey(SpeculatedType type)
+    {
+        // Propagate the prediction of the source directly if already proven to be a property key.
+        if (type && !(type & ~(SpecString | SpecSymbol)))
+            return type;
+
+        if (type & SpecStringObject && m_graph.canOptimizeStringObjectAccess(m_currentNode->origin.semantic))
+            return mergeSpeculations(type & SpecSymbol, SpecString);
+
+        return SpecString | SpecSymbol;
+    }
+
     Vector<Node*> m_dependentNodes;
     Node* m_currentNode;
     bool m_changed { false };
diff --git a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
index 203387b..ecdd993 100644
--- a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
+++ b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
@@ -361,6 +361,7 @@
     case LogicalNot:
     case CallObjectConstructor:
     case ToPrimitive:
+    case ToPropertyKey:
     case ToString:
     case ToNumber:
     case ToNumeric:
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index 68f523f..13f28ce 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -13053,6 +13053,30 @@
     jsValueResult(resultRegs, node, DataFormatJS, UseChildrenCalledExplicitly);
 }
 
+void SpeculativeJIT::compileToPropertyKey(Node* node)
+{
+    DFG_ASSERT(m_jit.graph(), node, node->child1().useKind() == UntypedUse, node->child1().useKind());
+    JSValueOperand argument(this, node->child1());
+    JSValueRegsTemporary result(this, Reuse, argument);
+
+    JSValueRegs argumentRegs = argument.jsValueRegs();
+    JSValueRegs resultRegs = result.regs();
+
+    argument.use();
+
+    MacroAssembler::JumpList slowCases;
+    slowCases.append(m_jit.branchIfNotCell(argumentRegs));
+    MacroAssembler::Jump alreadyPropertyKey = m_jit.branchIfSymbol(argumentRegs.payloadGPR());
+    slowCases.append(m_jit.branchIfNotString(argumentRegs.payloadGPR()));
+
+    alreadyPropertyKey.link(&m_jit);
+    m_jit.moveValueRegs(argumentRegs, resultRegs);
+
+    addSlowPathGenerator(slowPathCall(slowCases, this, operationToPropertyKey, resultRegs, TrustedImmPtr::weakPointer(m_graph, m_graph.globalObjectFor(node->origin.semantic)), argumentRegs));
+
+    jsValueResult(resultRegs, node, DataFormatJSCell, UseChildrenCalledExplicitly);
+}
+
 void SpeculativeJIT::compileToNumeric(Node* node)
 {
     DFG_ASSERT(m_jit.graph(), node, node->child1().useKind() == UntypedUse, node->child1().useKind());
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
index 8f96182..b1fb0e8 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
@@ -1464,6 +1464,7 @@
     void compileNewAsyncGenerator(Node*);
     void compileNewArrayIterator(Node*);
     void compileToPrimitive(Node*);
+    void compileToPropertyKey(Node*);
     void compileToNumeric(Node*);
     void compileLogShadowChickenPrologue(Node*);
     void compileLogShadowChickenTail(Node*);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
index d45edc1..fa066bf 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
@@ -3095,6 +3095,11 @@
         break;
     }
 
+    case ToPropertyKey: {
+        compileToPropertyKey(node);
+        break;
+    }
+
     case ToNumber: {
         JSValueOperand argument(this, node->child1());
         GPRTemporary resultTag(this, Reuse, argument, TagWord);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
index ec7f337..804369d 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
@@ -3431,6 +3431,11 @@
         break;
     }
 
+    case ToPropertyKey: {
+        compileToPropertyKey(node);
+        break;
+    }
+
     case ToNumber: {
         JSValueOperand argument(this, node->child1());
         GPRTemporary result(this, Reuse, argument);
diff --git a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
index b953cfe..eba197f 100644
--- a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
+++ b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
@@ -233,6 +233,7 @@
     case MultiGetByOffset:
     case MultiPutByOffset:
     case ToPrimitive:
+    case ToPropertyKey:
     case Throw:
     case ThrowStaticError:
     case Unreachable:
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
index 58390d3..0c58572 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
@@ -1139,6 +1139,9 @@
         case ToPrimitive:
             compileToPrimitive();
             break;
+        case ToPropertyKey:
+            compileToPropertyKey();
+            break;
         case MakeRope:
             compileMakeRope();
             break;
@@ -7339,6 +7342,38 @@
         m_out.appendTo(continuation, lastNext);
         setJSValue(m_out.phi(Int64, results));
     }
+
+    void compileToPropertyKey()
+    {
+        ASSERT(m_node->child1().useKind() == UntypedUse);
+        JSGlobalObject* globalObject = m_graph.globalObjectFor(m_node->origin.semantic);
+        LValue value = lowJSValue(m_node->child1());
+
+        LBasicBlock isCellCase = m_out.newBlock();
+        LBasicBlock notStringCase = m_out.newBlock();
+        LBasicBlock slowPathCase = m_out.newBlock();
+        LBasicBlock continuation = m_out.newBlock();
+
+        Vector<ValueFromBlock, 3> results;
+        m_out.branch(
+            isCell(value, provenType(m_node->child1())), unsure(isCellCase), unsure(slowPathCase));
+
+        LBasicBlock lastNext = m_out.appendTo(isCellCase, notStringCase);
+        results.append(m_out.anchor(value));
+        m_out.branch(isString(value, provenType(m_node->child1())), unsure(continuation), unsure(notStringCase));
+
+        m_out.appendTo(notStringCase, slowPathCase);
+        results.append(m_out.anchor(value));
+        m_out.branch(isSymbol(value, provenType(m_node->child1())), unsure(continuation), unsure(slowPathCase));
+
+        m_out.appendTo(slowPathCase, continuation);
+        results.append(m_out.anchor(vmCall(
+            Int64, operationToPropertyKey, weakPointer(globalObject), value)));
+        m_out.jump(continuation);
+
+        m_out.appendTo(continuation, lastNext);
+        setJSValue(m_out.phi(Int64, results));
+    }
     
     void compileMakeRope()
     {