[JSC] Optimize Object.prototype.toString
https://bugs.webkit.org/show_bug.cgi?id=193031

Reviewed by Saam Barati.

JSTests:

* stress/object-tostring-changed-proto.js: Added.
(shouldBe):
(test):
* stress/object-tostring-changed.js: Added.
(shouldBe):
(test):
* stress/object-tostring-misc.js: Added.
(shouldBe):
(test):
(i.switch):
* stress/object-tostring-other.js: Added.
(shouldBe):
(test):
* stress/object-tostring-untyped.js: Added.
(shouldBe):
(test):
(i.switch):

Source/JavaScriptCore:

Object.prototype.toString is frequently used for type checking.
It is called many times in wtb-lebab.js. This patch optimizes
Object.prototype.toString by the following two optimizations.

1. We should emit code looking up cached to string in DFG and FTL.

toString's result is cached in the Structure. We emit a fast path code
in DFG and FTL to lookup this cache.

2. We should not create objects for primitive values in major cases.

When Object.prototype.toString(primitive) is called, this primitive is converted
to an object by calling ToObject. But if the result is appropriately cached in
the Structure, we should get it in the fast path without creating this object.
When converting primitives to objects, Structures used in these newly created objects
are known (Structure for StringObject etc.). So we can first query the cached string
before actually converting primitives to objects.

This patch improves wtb-lebab.js by roughly 2%.

    before:    lebab:  8.90 runs/s
    after :    lebab:  9.09 runs/s

* JavaScriptCore.xcodeproj/project.pbxproj:
* 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):
(JSC::DFG::FixupPhase::fixupObjectToString):
* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileObjectToString):
* 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::compileObjectToString):
* runtime/Intrinsic.cpp:
(JSC::intrinsicName):
* runtime/Intrinsic.h:
* runtime/ObjectPrototype.cpp:
(JSC::ObjectPrototype::finishCreation):
(JSC::objectProtoFuncToString):
* runtime/ObjectPrototype.h:
* runtime/ObjectPrototypeInlines.h: Added.
(JSC::structureForPrimitiveValue):
(JSC::objectToString):
* runtime/StructureRareData.h:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@239612 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog
index 62fee43..a344798 100644
--- a/JSTests/ChangeLog
+++ b/JSTests/ChangeLog
@@ -1,3 +1,28 @@
+2019-01-02  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
+
+        [JSC] Optimize Object.prototype.toString
+        https://bugs.webkit.org/show_bug.cgi?id=193031
+
+        Reviewed by Saam Barati.
+
+        * stress/object-tostring-changed-proto.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-tostring-changed.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-tostring-misc.js: Added.
+        (shouldBe):
+        (test):
+        (i.switch):
+        * stress/object-tostring-other.js: Added.
+        (shouldBe):
+        (test):
+        * stress/object-tostring-untyped.js: Added.
+        (shouldBe):
+        (test):
+        (i.switch):
+
 2019-01-03  Ross Kirsling  <ross.kirsling@sony.com>
 
         test262-runner misbehaves when test file YAML has a trailing space
diff --git a/JSTests/stress/object-tostring-changed-proto.js b/JSTests/stress/object-tostring-changed-proto.js
new file mode 100644
index 0000000..9a1a22b
--- /dev/null
+++ b/JSTests/stress/object-tostring-changed-proto.js
@@ -0,0 +1,18 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+function test(value)
+{
+    return Object.prototype.toString.call(value);
+}
+noInline(test);
+
+var object = {};
+for (var i = 0; i < 1e5; ++i)
+    shouldBe(test(object), `[object Object]`);
+Object.prototype[Symbol.toStringTag] = "Hello";
+shouldBe(test(object), `[object Hello]`);
diff --git a/JSTests/stress/object-tostring-changed.js b/JSTests/stress/object-tostring-changed.js
new file mode 100644
index 0000000..b4f7275
--- /dev/null
+++ b/JSTests/stress/object-tostring-changed.js
@@ -0,0 +1,18 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+function test(value)
+{
+    return Object.prototype.toString.call(value);
+}
+noInline(test);
+
+var object = {};
+for (var i = 0; i < 1e5; ++i)
+    shouldBe(test(object), `[object Object]`);
+object[Symbol.toStringTag] = "Hello";
+shouldBe(test(object), `[object Hello]`);
diff --git a/JSTests/stress/object-tostring-misc.js b/JSTests/stress/object-tostring-misc.js
new file mode 100644
index 0000000..1b00aeb
--- /dev/null
+++ b/JSTests/stress/object-tostring-misc.js
@@ -0,0 +1,26 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+function test(value)
+{
+    return Object.prototype.toString.call(value);
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i) {
+    switch (i % 3) {
+    case 0:
+        shouldBe(test(null), `[object Null]`);
+        break;
+    case 1:
+        shouldBe(test(undefined), `[object Undefined]`);
+        break;
+    case 2:
+        shouldBe(test(true), `[object Boolean]`);
+        break;
+    }
+}
diff --git a/JSTests/stress/object-tostring-other.js b/JSTests/stress/object-tostring-other.js
new file mode 100644
index 0000000..657fbaf
--- /dev/null
+++ b/JSTests/stress/object-tostring-other.js
@@ -0,0 +1,19 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+function test(value)
+{
+    return Object.prototype.toString.call(value);
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i) {
+    if (i & 0x1)
+        shouldBe(test(null), `[object Null]`);
+    else
+        shouldBe(test(undefined), `[object Undefined]`);
+}
diff --git a/JSTests/stress/object-tostring-untyped.js b/JSTests/stress/object-tostring-untyped.js
new file mode 100644
index 0000000..f77cdd0
--- /dev/null
+++ b/JSTests/stress/object-tostring-untyped.js
@@ -0,0 +1,50 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+function test(value)
+{
+    return Object.prototype.toString.call(value);
+}
+noInline(test);
+
+var value0 = {};
+var value1 = { [Symbol.toStringTag]: "Hello" };
+var value2 = new Date();
+var value3 = "Hello";
+var value4 = 42;
+var value5 = Symbol("Cocoa");
+var value6 = 42.195;
+var value7 = false;
+
+for (var i = 0; i < 1e6; ++i) {
+    switch (i % 8) {
+    case 0:
+        shouldBe(test(value0), `[object Object]`);
+        break;
+    case 1:
+        shouldBe(test(value1), `[object Hello]`);
+        break;
+    case 2:
+        shouldBe(test(value2), `[object Date]`);
+        break;
+    case 3:
+        shouldBe(test(value3), `[object String]`);
+        break;
+    case 4:
+        shouldBe(test(value4), `[object Number]`);
+        break;
+    case 5:
+        shouldBe(test(value5), `[object Symbol]`);
+        break;
+    case 6:
+        shouldBe(test(value6), `[object Number]`);
+        break;
+    case 7:
+        shouldBe(test(value7), `[object Boolean]`);
+        break;
+    }
+}
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 5eae418..d2852b8 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,76 @@
+2019-01-02  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
+
+        [JSC] Optimize Object.prototype.toString
+        https://bugs.webkit.org/show_bug.cgi?id=193031
+
+        Reviewed by Saam Barati.
+
+        Object.prototype.toString is frequently used for type checking.
+        It is called many times in wtb-lebab.js. This patch optimizes
+        Object.prototype.toString by the following two optimizations.
+
+        1. We should emit code looking up cached to string in DFG and FTL.
+
+        toString's result is cached in the Structure. We emit a fast path code
+        in DFG and FTL to lookup this cache.
+
+        2. We should not create objects for primitive values in major cases.
+
+        When Object.prototype.toString(primitive) is called, this primitive is converted
+        to an object by calling ToObject. But if the result is appropriately cached in
+        the Structure, we should get it in the fast path without creating this object.
+        When converting primitives to objects, Structures used in these newly created objects
+        are known (Structure for StringObject etc.). So we can first query the cached string
+        before actually converting primitives to objects.
+
+        This patch improves wtb-lebab.js by roughly 2%.
+
+            before:    lebab:  8.90 runs/s
+            after :    lebab:  9.09 runs/s
+
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        * 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):
+        (JSC::DFG::FixupPhase::fixupObjectToString):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileObjectToString):
+        * 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::compileObjectToString):
+        * runtime/Intrinsic.cpp:
+        (JSC::intrinsicName):
+        * runtime/Intrinsic.h:
+        * runtime/ObjectPrototype.cpp:
+        (JSC::ObjectPrototype::finishCreation):
+        (JSC::objectProtoFuncToString):
+        * runtime/ObjectPrototype.h:
+        * runtime/ObjectPrototypeInlines.h: Added.
+        (JSC::structureForPrimitiveValue):
+        (JSC::objectToString):
+        * runtime/StructureRareData.h:
+
 2019-01-03  Michael Saboff  <msaboff@apple.com>
 
         DFG IntegerRangeOptimization phase exceeding loop limit shouldn't ASSERT
diff --git a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
index f3ef1c4..02c352f 100644
--- a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
+++ b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
@@ -1624,6 +1624,7 @@
 		BC18C4440E16F5CD00B34460 /* NumberPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = BC2680C50E16D4E900A06E92 /* NumberPrototype.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		BC18C4450E16F5CD00B34460 /* ObjectConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = BC2680C70E16D4E900A06E92 /* ObjectConstructor.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		BC18C4460E16F5CD00B34460 /* ObjectPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = BC2680C90E16D4E900A06E92 /* ObjectPrototype.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		5E158AC350BC4EC7877DC0F4 /* ObjectPrototypeInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D0CC9E1CBC149AB8F403434 /* ObjectPrototypeInlines.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		BC18C4480E16F5CD00B34460 /* Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = F692A8780255597D01FF60F7 /* Operations.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		BC18C44B0E16F5CD00B34460 /* Parser.h in Headers */ = {isa = PBXBuildFile; fileRef = 93F0B3AA09BB4DC00068FCE3 /* Parser.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		BC18C4540E16F5CD00B34460 /* PropertyNameArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 65400C100A69BAF200509887 /* PropertyNameArray.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -4486,6 +4487,7 @@
 		BC2680C70E16D4E900A06E92 /* ObjectConstructor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectConstructor.h; sourceTree = "<group>"; };
 		BC2680C80E16D4E900A06E92 /* ObjectPrototype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectPrototype.cpp; sourceTree = "<group>"; };
 		BC2680C90E16D4E900A06E92 /* ObjectPrototype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectPrototype.h; sourceTree = "<group>"; };
+		6D0CC9E1CBC149AB8F403434 /* ObjectPrototypeInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectPrototypeInlines.h; sourceTree = "<group>"; };
 		BC2680E60E16D52300A06E92 /* NumberConstructor.lut.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NumberConstructor.lut.h; sourceTree = "<group>"; };
 		BC3046060E1F497F003232CF /* Error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Error.h; sourceTree = "<group>"; };
 		BC337BDE0E1AF0B80076918A /* GetterSetter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = GetterSetter.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
@@ -7000,6 +7002,7 @@
 				E3C295DC1ED2CBAA00D3016F /* ObjectPropertyChangeAdaptiveWatchpoint.h */,
 				BC2680C80E16D4E900A06E92 /* ObjectPrototype.cpp */,
 				BC2680C90E16D4E900A06E92 /* ObjectPrototype.h */,
+				6D0CC9E1CBC149AB8F403434 /* ObjectPrototypeInlines.h */,
 				F692A8770255597D01FF60F7 /* Operations.cpp */,
 				F692A8780255597D01FF60F7 /* Operations.h */,
 				0FE228EA1436AB2300196C48 /* Options.cpp */,
@@ -9447,6 +9450,7 @@
 				0FD3E40A1B618B6600C80E1E /* ObjectPropertyCondition.h in Headers */,
 				0FD3E40C1B618B6600C80E1E /* ObjectPropertyConditionSet.h in Headers */,
 				BC18C4460E16F5CD00B34460 /* ObjectPrototype.h in Headers */,
+				5E158AC350BC4EC7877DC0F4 /* ObjectPrototypeInlines.h in Headers */,
 				E124A8F70E555775003091F1 /* OpaqueJSString.h in Headers */,
 				14F79F70216EAFD200046D39 /* Opcode.h in Headers */,
 				FE64872E2141D04800AB0D3E /* OpcodeInlines.h in Headers */,
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index 5197133..a8113f3 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -2620,6 +2620,24 @@
         break;
     }
 
+    case ObjectToString: {
+        AbstractValue& source = forNode(node->child1());
+        bool clobbering = node->child1().useKind() != OtherUse;
+        if (JSValue sourceValue = source.m_value) {
+            if (sourceValue.isUndefinedOrNull()) {
+                if (clobbering)
+                    didFoldClobberWorld();
+                setConstant(node, *m_graph.freeze(sourceValue.isUndefined() ? m_vm.smallStrings.undefinedObjectString() : m_vm.smallStrings.nullObjectString()));
+                break;
+            }
+        }
+
+        if (clobbering)
+            clobberWorld();
+        setTypeForNode(node, SpecString);
+        break;
+    }
+
     case ToObject:
     case CallObjectConstructor: {
         AbstractValue& source = forNode(node->child1());
diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
index d6e3843..93529db 100644
--- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
@@ -2701,6 +2701,13 @@
         return true;
     }
 
+    case ObjectPrototypeToStringIntrinsic: {
+        insertChecks();
+        Node* value = get(virtualRegisterForArgument(0, registerOffset));
+        set(result, addToGraph(ObjectToString, value));
+        return true;
+    }
+
     case ReflectGetPrototypeOfIntrinsic: {
         if (argumentCountIncludingThis != 2)
             return false;
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h
index 48c846b..fcb3c6b 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.h
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h
@@ -681,6 +681,20 @@
         write(Heap);
         return;
 
+    case ObjectToString:
+        switch (node->child1().useKind()) {
+        case OtherUse:
+            def(PureValue(node));
+            return;
+        case UntypedUse:
+            read(World);
+            write(Heap);
+            return;
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+            return;
+        }
+
     case AtomicsAdd:
     case AtomicsAnd:
     case AtomicsCompareExchange:
diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
index df74930..ddbfa5d 100644
--- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
@@ -372,6 +372,7 @@
     case StringReplaceRegExp:
     case StringSlice:
     case StringValueOf:
+    case ObjectToString:
     case CreateRest:
     case ToLowerCase:
     case CallDOMGetter:
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 307632d..d7860ea 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -2147,6 +2147,11 @@
             break;
         }
 
+        case ObjectToString: {
+            fixupObjectToString(node);
+            break;
+        }
+
         case StringSlice: {
             fixEdge<StringUse>(node->child1());
             fixEdge<Int32Use>(node->child2());
@@ -2931,6 +2936,15 @@
         }
     }
 
+    void fixupObjectToString(Node* node)
+    {
+        if (node->child1()->shouldSpeculateOther()) {
+            fixEdge<OtherUse>(node->child1());
+            node->clearFlags(NodeMustGenerate);
+            return;
+        }
+    }
+
     bool attemptToMakeFastStringAdd(Node* node)
     {
         bool goodToGo = true;
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index dc2f3e1..03fe46b9 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -266,6 +266,7 @@
     macro(GetPrototypeOf, NodeMustGenerate | NodeResultJS) \
     macro(ObjectCreate, NodeMustGenerate | NodeResultJS) \
     macro(ObjectKeys, NodeMustGenerate | NodeResultJS) \
+    macro(ObjectToString, NodeMustGenerate | NodeResultJS) \
     \
     /* Atomics object functions. */\
     macro(AtomicsAdd, NodeResultJS | NodeMustGenerate | NodeHasVarArgs) \
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp
index 1da406b..dfced10 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp
@@ -63,6 +63,7 @@
 #include "JSWeakSet.h"
 #include "NumberConstructor.h"
 #include "ObjectConstructor.h"
+#include "ObjectPrototypeInlines.h"
 #include "Operations.h"
 #include "ParseInt.h"
 #include "RegExpConstructor.h"
@@ -71,6 +72,7 @@
 #include "Repatch.h"
 #include "ScopedArguments.h"
 #include "StringConstructor.h"
+#include "StructureRareDataInlines.h"
 #include "SuperSampler.h"
 #include "Symbol.h"
 #include "TypeProfilerLog.h"
@@ -2154,6 +2156,13 @@
     return nullptr;
 }
 
+JSString* JIT_OPERATION operationObjectToString(ExecState* exec, EncodedJSValue source)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+    return objectToString(exec, JSValue::decode(source));
+}
+
 JSCell* JIT_OPERATION operationStringSubstr(ExecState* exec, JSCell* cell, int32_t from, int32_t span)
 {
     VM& vm = exec->vm();
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.h b/Source/JavaScriptCore/dfg/DFGOperations.h
index 38c116a..8b76f4d 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.h
+++ b/Source/JavaScriptCore/dfg/DFGOperations.h
@@ -205,6 +205,7 @@
 JSCell* JIT_OPERATION operationStringSubstr(ExecState*, JSCell*, int32_t, int32_t);
 JSString* JIT_OPERATION operationStringValueOf(ExecState*, EncodedJSValue);
 JSString* JIT_OPERATION operationToLowerCase(ExecState*, JSString*, uint32_t);
+JSString* JIT_OPERATION operationObjectToString(ExecState*, EncodedJSValue);
 
 char* JIT_OPERATION operationInt32ToString(ExecState*, int32_t, int32_t);
 char* JIT_OPERATION operationInt52ToString(ExecState*, int64_t, int32_t);
diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
index b3c69bc..1ba4eff 100644
--- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
@@ -884,6 +884,7 @@
         case StringValueOf:
         case StringSlice:
         case ToLowerCase:
+        case ObjectToString:
             setPrediction(SpecString);
             break;
 
diff --git a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
index f106982..06da94f 100644
--- a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
+++ b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
@@ -452,6 +452,7 @@
     case StringValueOf:
     case StringSlice:
     case ToLowerCase:
+    case ObjectToString:
     case GetMapBucket:
     case GetMapBucketHead:
     case GetMapBucketNext:
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index 63e09fb..61269bc 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -12453,6 +12453,63 @@
     }
 }
 
+void SpeculativeJIT::compileObjectToString(Node* node)
+{
+    switch (node->child1().useKind()) {
+    case OtherUse: {
+        JSValueOperand source(this, node->child1(), ManualOperandSpeculation);
+        GPRTemporary result(this);
+
+        JSValueRegs sourceRegs = source.jsValueRegs();
+        GPRReg resultGPR = result.gpr();
+
+        speculateOther(node->child1(), sourceRegs);
+
+        auto isUndefined = m_jit.branchIfUndefined(sourceRegs);
+        m_jit.move(TrustedImmPtr::weakPointer(m_jit.graph(), m_jit.vm()->smallStrings.nullObjectString()), resultGPR);
+        auto done = m_jit.jump();
+        isUndefined.link(&m_jit);
+        m_jit.move(TrustedImmPtr::weakPointer(m_jit.graph(), m_jit.vm()->smallStrings.undefinedObjectString()), resultGPR);
+        done.link(&m_jit);
+
+        cellResult(resultGPR, node);
+        return;
+    }
+    case UntypedUse: {
+        JSValueOperand source(this, node->child1());
+
+        JSValueRegs sourceRegs = source.jsValueRegs();
+
+        GPRTemporary structure(this);
+        GPRTemporary scratch(this);
+
+        GPRReg structureGPR = structure.gpr();
+        GPRReg scratchGPR = scratch.gpr();
+
+        CCallHelpers::JumpList slowCases;
+        slowCases.append(m_jit.branchIfNotCell(sourceRegs));
+        slowCases.append(m_jit.branchIfNotObject(sourceRegs.payloadGPR()));
+
+        m_jit.emitLoadStructure(*m_jit.vm(), sourceRegs.payloadGPR(), 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::offsetOfObjectToStringValue()), scratchGPR);
+        slowCases.append(m_jit.branchTestPtr(CCallHelpers::Zero, scratchGPR));
+
+        addSlowPathGenerator(slowPathCall(slowCases, this, operationObjectToString, scratchGPR, sourceRegs));
+
+        cellResult(scratchGPR, node);
+        return;
+    }
+    default:
+        DFG_CRASH(m_graph, node, "Bad use kind");
+        return;
+    }
+}
+
 void SpeculativeJIT::compileObjectCreate(Node* node)
 {
     switch (node->child1().useKind()) {
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
index dcdf966..2836910 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
@@ -1480,6 +1480,7 @@
     void compileNewTypedArray(Node*);
     void compileToThis(Node*);
     void compileObjectKeys(Node*);
+    void compileObjectToString(Node*);
     void compileObjectCreate(Node*);
     void compileCreateThis(Node*);
     void compileNewObject(Node*);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
index fcda6a6..10bddf8 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
@@ -3105,6 +3105,11 @@
         compileToStringOrCallStringConstructorOrStringValueOf(node);
         break;
     }
+
+    case ObjectToString: {
+        compileObjectToString(node);
+        break;
+    }
         
     case NewStringObject: {
         compileNewStringObject(node);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
index 553f86f..5d58fda 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
@@ -3347,6 +3347,11 @@
         compileToStringOrCallStringConstructorOrStringValueOf(node);
         break;
     }
+
+    case ObjectToString: {
+        compileObjectToString(node);
+        break;
+    }
         
     case NewStringObject: {
         compileNewStringObject(node);
diff --git a/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h b/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h
index 9f3aa2f..b7e9984 100644
--- a/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h
+++ b/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h
@@ -120,6 +120,7 @@
     macro(Structure_prototype, Structure::prototypeOffset()) \
     macro(Structure_structureID, Structure::structureIDOffset()) \
     macro(StructureRareData_cachedOwnKeys, StructureRareData::offsetOfCachedOwnKeys()) \
+    macro(StructureRareData_objectToStringValue, StructureRareData::offsetOfObjectToStringValue()) \
     macro(HashMapImpl_capacity, HashMapImpl<HashMapBucket<HashMapBucketDataKey>>::offsetOfCapacity()) \
     macro(HashMapImpl_buffer,  HashMapImpl<HashMapBucket<HashMapBucketDataKey>>::offsetOfBuffer()) \
     macro(HashMapImpl_head,  HashMapImpl<HashMapBucket<HashMapBucketDataKey>>::offsetOfHead()) \
diff --git a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
index 23c9396..6b7d0fc 100644
--- a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
+++ b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
@@ -333,6 +333,7 @@
     case StringValueOf:
     case StringSlice:
     case ToLowerCase:
+    case ObjectToString:
     case NumberToStringWithRadix:
     case NumberToStringWithValidRadixConstant:
     case CheckSubClass:
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
index 4130bd1..9156a48 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
@@ -937,6 +937,9 @@
         case StringFromCharCode:
             compileStringFromCharCode();
             break;
+        case ObjectToString:
+            compileObjectToString();
+            break;
         case GetByOffset:
         case GetGetterSetterByOffset:
             compileGetByOffset();
@@ -6419,6 +6422,61 @@
             break;
         }
     }
+
+    void compileObjectToString()
+    {
+        switch (m_node->child1().useKind()) {
+        case OtherUse: {
+            speculate(m_node->child1());
+            LValue source = lowJSValue(m_node->child1(), ManualOperandSpeculation);
+            LValue result = m_out.select(m_out.equal(source, m_out.constInt64(ValueUndefined)),
+                weakPointer(vm().smallStrings.undefinedObjectString()), weakPointer(vm().smallStrings.nullObjectString()));
+            setJSValue(result);
+            return;
+        }
+        case UntypedUse: {
+            LBasicBlock cellCase = m_out.newBlock();
+            LBasicBlock objectCase = m_out.newBlock();
+            LBasicBlock notNullCase = m_out.newBlock();
+            LBasicBlock rareDataCase = m_out.newBlock();
+            LBasicBlock slowCase = m_out.newBlock();
+            LBasicBlock continuation = m_out.newBlock();
+
+            LValue source = lowJSValue(m_node->child1());
+            m_out.branch(isCell(source, provenType(m_node->child1())), unsure(cellCase), unsure(slowCase));
+
+            LBasicBlock lastNext = m_out.appendTo(cellCase, objectCase);
+            m_out.branch(isObject(source, provenType(m_node->child1()) & SpecCell), unsure(objectCase), unsure(slowCase));
+
+            m_out.appendTo(objectCase, notNullCase);
+            LValue structure = loadStructure(source);
+            LValue previousOrRareData = m_out.loadPtr(structure, m_heaps.Structure_previousOrRareData);
+            m_out.branch(m_out.notNull(previousOrRareData), unsure(notNullCase), unsure(slowCase));
+
+            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, slowCase);
+            LValue objectToStringValue = m_out.loadPtr(previousOrRareData, m_heaps.StructureRareData_objectToStringValue);
+            ValueFromBlock fastResult = m_out.anchor(objectToStringValue);
+            m_out.branch(m_out.isNull(objectToStringValue), unsure(slowCase), unsure(continuation));
+
+            m_out.appendTo(slowCase, continuation);
+            LValue slowResultValue = vmCall(pointerType(), m_out.operation(operationObjectToString), m_callFrame, source);
+            ValueFromBlock slowResult = m_out.anchor(slowResultValue);
+            m_out.jump(continuation);
+
+            m_out.appendTo(continuation, lastNext);
+            setJSValue(m_out.phi(pointerType(), fastResult, slowResult));
+            return;
+        }
+        default:
+            DFG_CRASH(m_graph, m_node, "Bad use kind");
+            return;
+        }
+    }
     
     void compileToPrimitive()
     {
diff --git a/Source/JavaScriptCore/runtime/Intrinsic.cpp b/Source/JavaScriptCore/runtime/Intrinsic.cpp
index fbc4ef3..f3db584 100644
--- a/Source/JavaScriptCore/runtime/Intrinsic.cpp
+++ b/Source/JavaScriptCore/runtime/Intrinsic.cpp
@@ -121,6 +121,8 @@
         return "ObjectIsIntrinsic";
     case ObjectKeysIntrinsic:
         return "ObjectKeysIntrinsic";
+    case ObjectPrototypeToStringIntrinsic:
+        return "ObjectPrototypeToStringIntrinsic";
     case ReflectGetPrototypeOfIntrinsic:
         return "ReflectGetPrototypeOfIntrinsic";
     case StringPrototypeValueOfIntrinsic:
diff --git a/Source/JavaScriptCore/runtime/Intrinsic.h b/Source/JavaScriptCore/runtime/Intrinsic.h
index b47f46d..15eab26 100644
--- a/Source/JavaScriptCore/runtime/Intrinsic.h
+++ b/Source/JavaScriptCore/runtime/Intrinsic.h
@@ -73,6 +73,7 @@
     ObjectGetPrototypeOfIntrinsic,
     ObjectIsIntrinsic,
     ObjectKeysIntrinsic,
+    ObjectPrototypeToStringIntrinsic,
     ReflectGetPrototypeOfIntrinsic,
     StringPrototypeValueOfIntrinsic,
     StringPrototypeReplaceIntrinsic,
diff --git a/Source/JavaScriptCore/runtime/ObjectPrototype.cpp b/Source/JavaScriptCore/runtime/ObjectPrototype.cpp
index 1bd8930..052eb21 100644
--- a/Source/JavaScriptCore/runtime/ObjectPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/ObjectPrototype.cpp
@@ -27,6 +27,7 @@
 #include "JSFunction.h"
 #include "JSString.h"
 #include "JSCInlines.h"
+#include "ObjectPrototypeInlines.h"
 #include "PropertySlot.h"
 #include "StructureInlines.h"
 #include "StructureRareDataInlines.h"
@@ -42,6 +43,7 @@
 static EncodedJSValue JSC_HOST_CALL objectProtoFuncLookupSetter(ExecState*);
 static EncodedJSValue JSC_HOST_CALL objectProtoFuncPropertyIsEnumerable(ExecState*);
 static EncodedJSValue JSC_HOST_CALL objectProtoFuncToLocaleString(ExecState*);
+static EncodedJSValue JSC_HOST_CALL objectProtoFuncToString(ExecState*);
 
 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(ObjectPrototype);
 
@@ -58,7 +60,7 @@
     ASSERT(inherits(vm, info()));
     didBecomePrototype();
     
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, objectProtoFuncToString, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, objectProtoFuncToString, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, ObjectPrototypeToStringIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toLocaleString, objectProtoFuncToLocaleString, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->valueOf, objectProtoFuncValueOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->hasOwnProperty, objectProtoFuncHasOwnProperty, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, HasOwnPropertyIntrinsic);
@@ -311,50 +313,8 @@
 
 EncodedJSValue JSC_HOST_CALL objectProtoFuncToString(ExecState* exec)
 {
-    VM& vm = exec->vm();
-    auto scope = DECLARE_THROW_SCOPE(vm);
-
     JSValue thisValue = exec->thisValue().toThis(exec, StrictMode);
-    if (thisValue.isUndefinedOrNull())
-        return JSValue::encode(thisValue.isUndefined() ? vm.smallStrings.undefinedObjectString() : vm.smallStrings.nullObjectString());
-    JSObject* thisObject = thisValue.toObject(exec);
-    EXCEPTION_ASSERT(!!scope.exception() == !thisObject);
-    if (!thisObject)
-        return JSValue::encode(jsUndefined());
-
-    auto result = thisObject->structure(vm)->objectToStringValue();
-    if (result)
-        return JSValue::encode(result);
-
-    PropertyName toStringTagSymbol = vm.propertyNames->toStringTagSymbol;
-    RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->getPropertySlot(exec, toStringTagSymbol, [&] (bool found, PropertySlot& toStringTagSlot) -> JSValue {
-        if (found) {
-            JSValue stringTag = toStringTagSlot.getValue(exec, toStringTagSymbol);
-            RETURN_IF_EXCEPTION(scope, { });
-            if (stringTag.isString()) {
-                JSRopeString::RopeBuilder<RecordOverflow> ropeBuilder(vm);
-                ropeBuilder.append(vm.smallStrings.objectStringStart());
-                ropeBuilder.append(asString(stringTag));
-                ropeBuilder.append(vm.smallStrings.singleCharacterString(']'));
-                if (ropeBuilder.hasOverflowed())
-                    return throwOutOfMemoryError(exec, scope);
-
-                JSString* result = ropeBuilder.release();
-                thisObject->structure(vm)->setObjectToStringValue(exec, vm, result, toStringTagSlot);
-                return result;
-            }
-        }
-
-        String tag = thisObject->methodTable(vm)->toStringName(thisObject, exec);
-        RETURN_IF_EXCEPTION(scope, { });
-        String newString = tryMakeString("[object ", WTFMove(tag), "]");
-        if (!newString)
-            return throwOutOfMemoryError(exec, scope);
-
-        auto result = jsNontrivialString(&vm, newString);
-        thisObject->structure(vm)->setObjectToStringValue(exec, vm, result, toStringTagSlot);
-        return result;
-    })));
+    return JSValue::encode(objectToString(exec, thisValue));
 }
 
 } // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/ObjectPrototype.h b/Source/JavaScriptCore/runtime/ObjectPrototype.h
index b70a8658..602b13d 100644
--- a/Source/JavaScriptCore/runtime/ObjectPrototype.h
+++ b/Source/JavaScriptCore/runtime/ObjectPrototype.h
@@ -45,6 +45,4 @@
     ObjectPrototype(VM&, Structure*);
 };
 
-JS_EXPORT_PRIVATE EncodedJSValue JSC_HOST_CALL objectProtoFuncToString(ExecState*);
-
 } // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/ObjectPrototypeInlines.h b/Source/JavaScriptCore/runtime/ObjectPrototypeInlines.h
new file mode 100644
index 0000000..331991f
--- /dev/null
+++ b/Source/JavaScriptCore/runtime/ObjectPrototypeInlines.h
@@ -0,0 +1,112 @@
+/*
+ *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
+ *  Copyright (C) 2008-2017 Apple Inc. All rights reserved.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#pragma once
+
+#include "JSCInlines.h"
+#include "JSObject.h"
+#include "JSString.h"
+#include "ObjectPrototype.h"
+#include "PropertySlot.h"
+#include "StructureInlines.h"
+#include "StructureRareDataInlines.h"
+
+namespace JSC {
+
+inline Structure* structureForPrimitiveValue(JSGlobalObject* globalObject, JSValue value)
+{
+    if (value.isCell()) {
+        if (value.isString())
+            return globalObject->stringObjectStructure();
+        if (value.isBigInt())
+            return globalObject->bigIntObjectStructure();
+        ASSERT(value.isSymbol());
+        return globalObject->symbolObjectStructure();
+    }
+
+    if (value.isNumber())
+        return globalObject->numberObjectStructure();
+    if (value.isBoolean())
+        return globalObject->booleanObjectStructure();
+
+    ASSERT(value.isUndefinedOrNull());
+    return nullptr;
+}
+
+ALWAYS_INLINE JSString* objectToString(ExecState* exec, JSValue thisValue)
+{
+    VM& vm = exec->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSObject* thisObject = nullptr;
+    if (thisValue.isObject()) {
+        thisObject = jsCast<JSObject*>(thisValue);
+        if (auto* result = thisObject->structure(vm)->objectToStringValue())
+            return result;
+    } else {
+        if (thisValue.isUndefinedOrNull())
+            return thisValue.isUndefined() ? vm.smallStrings.undefinedObjectString() : vm.smallStrings.nullObjectString();
+
+        auto* structure = structureForPrimitiveValue(exec->lexicalGlobalObject(), thisValue);
+        ASSERT(structure);
+        if (auto* result = structure->objectToStringValue())
+            return result;
+        thisObject = thisValue.toObject(exec);
+        EXCEPTION_ASSERT(!!scope.exception() == !thisObject);
+        if (!thisObject)
+            return nullptr;
+    }
+
+    RELEASE_AND_RETURN(scope, thisObject->getPropertySlot(exec, vm.propertyNames->toStringTagSymbol, [&] (bool found, PropertySlot& toStringTagSlot) -> JSString* {
+        auto scope = DECLARE_THROW_SCOPE(vm);
+        if (found) {
+            JSValue stringTag = toStringTagSlot.getValue(exec, vm.propertyNames->toStringTagSymbol);
+            RETURN_IF_EXCEPTION(scope, { });
+            if (stringTag.isString()) {
+                JSRopeString::RopeBuilder<RecordOverflow> ropeBuilder(vm);
+                ropeBuilder.append(vm.smallStrings.objectStringStart());
+                ropeBuilder.append(asString(stringTag));
+                ropeBuilder.append(vm.smallStrings.singleCharacterString(']'));
+                if (ropeBuilder.hasOverflowed()) {
+                    throwOutOfMemoryError(exec, scope);
+                    return nullptr;
+                }
+
+                JSString* result = ropeBuilder.release();
+                thisObject->structure(vm)->setObjectToStringValue(exec, vm, result, toStringTagSlot);
+                return result;
+            }
+        }
+
+        String tag = thisObject->methodTable(vm)->toStringName(thisObject, exec);
+        RETURN_IF_EXCEPTION(scope, { });
+        String newString = tryMakeString("[object ", WTFMove(tag), "]");
+        if (!newString) {
+            throwOutOfMemoryError(exec, scope);
+            return nullptr;
+        }
+
+        auto result = jsNontrivialString(&vm, WTFMove(newString));
+        thisObject->structure(vm)->setObjectToStringValue(exec, vm, result, toStringTagSlot);
+        return result;
+    }));
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/StructureRareData.h b/Source/JavaScriptCore/runtime/StructureRareData.h
index 336732d..179aa82 100644
--- a/Source/JavaScriptCore/runtime/StructureRareData.h
+++ b/Source/JavaScriptCore/runtime/StructureRareData.h
@@ -81,6 +81,11 @@
     void setSharedPolyProtoWatchpoint(Box<InlineWatchpointSet>&& sharedPolyProtoWatchpoint) { m_polyProtoWatchpoint = WTFMove(sharedPolyProtoWatchpoint); }
     bool hasSharedPolyProtoWatchpoint() const { return static_cast<bool>(m_polyProtoWatchpoint); }
 
+    static ptrdiff_t offsetOfObjectToStringValue()
+    {
+        return OBJECT_OFFSETOF(StructureRareData, m_objectToStringValue);
+    }
+
     static JSImmutableButterfly* cachedOwnKeysSentinel() { return bitwise_cast<JSImmutableButterfly*>(static_cast<uintptr_t>(1)); }
 
     static ptrdiff_t offsetOfCachedOwnKeys()