[JSC] Store bits for JSRopeString in 3 stores
authorysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Mar 2019 23:27:43 +0000 (23:27 +0000)
committerysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Mar 2019 23:27:43 +0000 (23:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195234

Reviewed by Saam Barati.

JSTests:

* stress/null-rope-and-collectors.js: Added.

Source/JavaScriptCore:

This patch cleans up the initialization of JSRopeString fields in DFG and FTL.
Previously, we store some part of data separately. Instead, this patch calculates
the data first by bit operations and store calculated data with fewer stores.

This patch also cleans up is8Bit and isSubstring flags. We put them in lower bits
of the first fiber instead of the upper 16 bits. Since we only have 3 bit flags, (isRope, is8Bit, isSubstring),
we can put them into the lower 3 bits, they are always empty due to alignment.

* bytecode/AccessCase.cpp:
(JSC::AccessCase::generateImpl): A bit clean up of StringLength IC to give a chance of unnecessary mov removal.
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::canBeRope):
(JSC::DFG::SpeculativeJIT::compileGetArrayLength):
(JSC::DFG::SpeculativeJIT::compileMakeRope):
* dfg/DFGSpeculativeJIT.h:
* ftl/FTLAbstractHeapRepository.cpp:
(JSC::FTL::AbstractHeapRepository::AbstractHeapRepository):
* ftl/FTLAbstractHeapRepository.h:
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileMakeRope):
(JSC::FTL::DFG::LowerDFGToB3::isRopeString):
(JSC::FTL::DFG::LowerDFGToB3::isNotRopeString):
* runtime/JSString.cpp:
(JSC::JSString::visitChildren):
* runtime/JSString.h:
(JSC::JSString::is8Bit const):
(JSC::JSString::isSubstring const):
* tools/JSDollarVM.cpp:
(JSC::functionCreateNullRopeString):
(JSC::JSDollarVM::finishCreation):

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

12 files changed:
JSTests/ChangeLog
JSTests/stress/null-rope-and-collectors.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/AccessCase.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.cpp
Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/runtime/JSString.cpp
Source/JavaScriptCore/runtime/JSString.h
Source/JavaScriptCore/tools/JSDollarVM.cpp

index 2cc0559..1a49554 100644 (file)
@@ -1,3 +1,12 @@
+2019-03-04  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Store bits for JSRopeString in 3 stores
+        https://bugs.webkit.org/show_bug.cgi?id=195234
+
+        Reviewed by Saam Barati.
+
+        * stress/null-rope-and-collectors.js: Added.
+
 2019-03-01  Dominik Infuehr  <dinfuehr@igalia.com>
 
         Unskip test read-dead-bytecode-locals-in-must-have-handle-values2.js on ARM/MIPS
diff --git a/JSTests/stress/null-rope-and-collectors.js b/JSTests/stress/null-rope-and-collectors.js
new file mode 100644 (file)
index 0000000..2ff2494
--- /dev/null
@@ -0,0 +1,4 @@
+var array = [];
+for (var i = 0; i < 1e6; ++i) {
+    array.push($vm.createNullRopeString());
+}
index 4ede916..8b19787 100644 (file)
@@ -1,3 +1,41 @@
+2019-03-04  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Store bits for JSRopeString in 3 stores
+        https://bugs.webkit.org/show_bug.cgi?id=195234
+
+        Reviewed by Saam Barati.
+
+        This patch cleans up the initialization of JSRopeString fields in DFG and FTL.
+        Previously, we store some part of data separately. Instead, this patch calculates
+        the data first by bit operations and store calculated data with fewer stores.
+
+        This patch also cleans up is8Bit and isSubstring flags. We put them in lower bits
+        of the first fiber instead of the upper 16 bits. Since we only have 3 bit flags, (isRope, is8Bit, isSubstring),
+        we can put them into the lower 3 bits, they are always empty due to alignment.
+
+        * bytecode/AccessCase.cpp:
+        (JSC::AccessCase::generateImpl): A bit clean up of StringLength IC to give a chance of unnecessary mov removal.
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::canBeRope):
+        (JSC::DFG::SpeculativeJIT::compileGetArrayLength):
+        (JSC::DFG::SpeculativeJIT::compileMakeRope):
+        * dfg/DFGSpeculativeJIT.h:
+        * ftl/FTLAbstractHeapRepository.cpp:
+        (JSC::FTL::AbstractHeapRepository::AbstractHeapRepository):
+        * ftl/FTLAbstractHeapRepository.h:
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileMakeRope):
+        (JSC::FTL::DFG::LowerDFGToB3::isRopeString):
+        (JSC::FTL::DFG::LowerDFGToB3::isNotRopeString):
+        * runtime/JSString.cpp:
+        (JSC::JSString::visitChildren):
+        * runtime/JSString.h:
+        (JSC::JSString::is8Bit const):
+        (JSC::JSString::isSubstring const):
+        * tools/JSDollarVM.cpp:
+        (JSC::functionCreateNullRopeString):
+        (JSC::JSDollarVM::finishCreation):
+
 2019-03-04  Joseph Pecoraro  <pecoraro@apple.com>
 
         ITMLKit Inspector: Data Bindings / Associated Data for nodes
index efc939b..1877231 100644 (file)
@@ -1215,14 +1215,14 @@ void AccessCase::generateImpl(AccessGenerationState& state)
     case StringLength: {
         jit.loadPtr(CCallHelpers::Address(baseGPR, JSString::offsetOfValue()), scratchGPR);
         auto isRope = jit.branchIfRopeStringImpl(scratchGPR);
-        jit.load32(CCallHelpers::Address(scratchGPR, StringImpl::lengthMemoryOffset()), scratchGPR);
+        jit.load32(CCallHelpers::Address(scratchGPR, StringImpl::lengthMemoryOffset()), valueRegs.payloadGPR());
         auto done = jit.jump();
 
         isRope.link(&jit);
-        jit.load32(CCallHelpers::Address(baseGPR, JSRopeString::offsetOfLength()), scratchGPR);
+        jit.load32(CCallHelpers::Address(baseGPR, JSRopeString::offsetOfLength()), valueRegs.payloadGPR());
 
         done.link(&jit);
-        jit.boxInt32(scratchGPR, valueRegs);
+        jit.boxInt32(valueRegs.payloadGPR(), valueRegs);
         state.succeed();
         return;
     }
index 3bbe4b5..95041b4 100644 (file)
@@ -6897,6 +6897,17 @@ void SpeculativeJIT::compileGetGlobalThis(Node* node)
     cellResult(resultGPR, node);
 }
 
+bool SpeculativeJIT::canBeRope(Edge& edge)
+{
+    if (m_state.forNode(edge).isType(SpecStringIdent))
+        return false;
+    // If this value is LazyValue, it will be converted to JSString, and the result must be non-rope string.
+    String string = edge->tryGetString(m_graph);
+    if (!string.isNull())
+        return false;
+    return true;
+}
+
 void SpeculativeJIT::compileGetArrayLength(Node* node)
 {
     switch (node->arrayMode().type()) {
@@ -6934,15 +6945,21 @@ void SpeculativeJIT::compileGetArrayLength(Node* node)
         GPRReg resultGPR = result.gpr();
         GPRReg tempGPR = temp.gpr();
 
+        bool needsRopeCase = canBeRope(node->child1());
+
         m_jit.loadPtr(MacroAssembler::Address(baseGPR, JSString::offsetOfValue()), tempGPR);
-        auto isRope = m_jit.branchIfRopeStringImpl(tempGPR);
+        CCallHelpers::Jump isRope;
+        if (needsRopeCase)
+            isRope = m_jit.branchIfRopeStringImpl(tempGPR);
         m_jit.load32(MacroAssembler::Address(tempGPR, StringImpl::lengthMemoryOffset()), resultGPR);
-        auto done = m_jit.jump();
+        if (needsRopeCase) {
+            auto done = m_jit.jump();
 
-        isRope.link(&m_jit);
-        m_jit.load32(CCallHelpers::Address(baseGPR, JSRopeString::offsetOfLength()), resultGPR);
+            isRope.link(&m_jit);
+            m_jit.load32(CCallHelpers::Address(baseGPR, JSRopeString::offsetOfLength()), resultGPR);
 
-        done.link(&m_jit);
+            done.link(&m_jit);
+        }
         int32Result(resultGPR, node);
         break;
     }
@@ -13425,43 +13442,28 @@ void SpeculativeJIT::compileMakeRope(Node* node)
     Allocator allocatorValue = allocatorForNonVirtualConcurrently<JSRopeString>(*m_jit.vm(), sizeof(JSRopeString), AllocatorForMode::AllocatorIfExists);
     emitAllocateJSCell(resultGPR, JITAllocator::constant(allocatorValue), allocatorGPR, TrustedImmPtr(m_jit.graph().registerStructure(m_jit.vm()->stringStructure.get())), scratchGPR, slowPath);
 
-    m_jit.orPtr(TrustedImm32(JSString::isRopeInPointer), opGPRs[0], allocatorGPR);
-    m_jit.storePtr(allocatorGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber0()));
-
-    m_jit.move(opGPRs[1], scratchGPR);
-    m_jit.store32(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber1Lower()));
-    m_jit.rshiftPtr(TrustedImm32(32), scratchGPR);
-    m_jit.store16(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber1Upper()));
-
-    if (numOpGPRs == 3) {
-        m_jit.move(opGPRs[2], scratchGPR);
-        m_jit.store32(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber2Lower()));
-        m_jit.rshiftPtr(TrustedImm32(32), scratchGPR);
-        m_jit.store16(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber2Upper()));
-    } else {
-        m_jit.storeZero32(CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber2Lower()));
-        m_jit.storeZero16(CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber2Upper()));
-    }
+    // This puts nullptr for the first fiber. It makes visitChildren safe even if this JSRopeString is discarded due to the speculation failure in the following path.
+    m_jit.storePtr(TrustedImmPtr(JSString::isRopeInPointer), CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber0()));
 
     {
         if (JSString* string = edges[0]->dynamicCastConstant<JSString*>(*m_jit.vm())) {
             m_jit.move(TrustedImm32(string->is8Bit() ? StringImpl::flagIs8Bit() : 0), scratchGPR);
             m_jit.move(TrustedImm32(string->length()), allocatorGPR);
         } else {
-            bool canBeRope = !m_state.forNode(edges[0]).isType(SpecStringIdent);
+            bool needsRopeCase = canBeRope(edges[0]);
             m_jit.loadPtr(CCallHelpers::Address(opGPRs[0], JSString::offsetOfValue()), scratch2GPR);
             CCallHelpers::Jump isRope;
-            if (canBeRope)
+            if (needsRopeCase)
                 isRope = m_jit.branchIfRopeStringImpl(scratch2GPR);
 
             m_jit.load32(CCallHelpers::Address(scratch2GPR, StringImpl::flagsOffset()), scratchGPR);
             m_jit.load32(CCallHelpers::Address(scratch2GPR, StringImpl::lengthMemoryOffset()), allocatorGPR);
 
-            if (canBeRope) {
+            if (needsRopeCase) {
                 auto done = m_jit.jump();
 
                 isRope.link(&m_jit);
-                m_jit.load16(CCallHelpers::Address(opGPRs[0], JSRopeString::offsetOfFlags()), scratchGPR);
+                m_jit.load32(CCallHelpers::Address(opGPRs[0], JSRopeString::offsetOfFlags()), scratchGPR);
                 m_jit.load32(CCallHelpers::Address(opGPRs[0], JSRopeString::offsetOfLength()), allocatorGPR);
                 done.link(&m_jit);
             }
@@ -13484,23 +13486,23 @@ void SpeculativeJIT::compileMakeRope(Node* node)
                     CCallHelpers::Overflow,
                     TrustedImm32(string->length()), allocatorGPR));
         } else {
-            bool canBeRope = !m_state.forNode(edges[i]).isType(SpecStringIdent);
+            bool needsRopeCase = canBeRope(edges[i]);
             m_jit.loadPtr(CCallHelpers::Address(opGPRs[i], JSString::offsetOfValue()), scratch2GPR);
             CCallHelpers::Jump isRope;
-            if (canBeRope)
+            if (needsRopeCase)
                 isRope = m_jit.branchIfRopeStringImpl(scratch2GPR);
 
-            m_jit.and16(CCallHelpers::Address(scratch2GPR, StringImpl::flagsOffset()), scratchGPR);
+            m_jit.and32(CCallHelpers::Address(scratch2GPR, StringImpl::flagsOffset()), scratchGPR);
             speculationCheck(
                 Uncountable, JSValueSource(), nullptr,
                 m_jit.branchAdd32(
                     CCallHelpers::Overflow,
                     CCallHelpers::Address(scratch2GPR, StringImpl::lengthMemoryOffset()), allocatorGPR));
-            if (canBeRope) {
+            if (needsRopeCase) {
                 auto done = m_jit.jump();
 
                 isRope.link(&m_jit);
-                m_jit.and16(CCallHelpers::Address(opGPRs[i], JSRopeString::offsetOfFlags()), scratchGPR);
+                m_jit.and32(CCallHelpers::Address(opGPRs[i], JSRopeString::offsetOfFlags()), scratchGPR);
                 m_jit.load32(CCallHelpers::Address(opGPRs[i], JSRopeString::offsetOfLength()), scratch2GPR);
                 speculationCheck(
                     Uncountable, JSValueSource(), nullptr,
@@ -13510,14 +13512,38 @@ void SpeculativeJIT::compileMakeRope(Node* node)
             }
         }
     }
-    m_jit.store16(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFlags()));
+
     if (!ASSERT_DISABLED) {
         CCallHelpers::Jump ok = m_jit.branch32(
             CCallHelpers::GreaterThanOrEqual, allocatorGPR, TrustedImm32(0));
         m_jit.abortWithReason(DFGNegativeStringLength);
         ok.link(&m_jit);
     }
-    m_jit.store32(allocatorGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfLength()));
+
+    static_assert(StringImpl::flagIs8Bit() == JSRopeString::is8BitInPointer, "");
+    m_jit.and32(TrustedImm32(StringImpl::flagIs8Bit()), scratchGPR);
+    m_jit.orPtr(opGPRs[0], scratchGPR);
+    m_jit.orPtr(TrustedImmPtr(JSString::isRopeInPointer), scratchGPR);
+    m_jit.storePtr(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber0()));
+
+    m_jit.move(opGPRs[1], scratchGPR);
+    m_jit.lshiftPtr(TrustedImm32(32), scratchGPR);
+    m_jit.orPtr(allocatorGPR, scratchGPR);
+    m_jit.storePtr(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber1()));
+
+    if (numOpGPRs == 2) {
+        m_jit.move(opGPRs[1], scratchGPR);
+        m_jit.rshiftPtr(TrustedImm32(32), scratchGPR);
+        m_jit.storePtr(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber2()));
+    } else {
+        m_jit.move(opGPRs[1], scratchGPR);
+        m_jit.rshiftPtr(TrustedImm32(32), scratchGPR);
+        m_jit.move(opGPRs[2], scratch2GPR);
+        m_jit.lshiftPtr(TrustedImm32(16), scratch2GPR);
+        m_jit.orPtr(scratch2GPR, scratchGPR);
+        m_jit.storePtr(scratchGPR, CCallHelpers::Address(resultGPR, JSRopeString::offsetOfFiber2()));
+    }
+
     auto isNonEmptyString = m_jit.branchTest32(CCallHelpers::NonZero, allocatorGPR);
 
     m_jit.move(TrustedImmPtr::weakPointer(m_jit.graph(), jsEmptyString(&m_jit.graph().m_vm)), resultGPR);
index f2b4303..7e07854 100644 (file)
@@ -577,6 +577,8 @@ public:
     bool isKnownNotNumber(Node* node) { return !(m_state.forNode(node).m_type & SpecFullNumber); }
     bool isKnownNotCell(Node* node) { return !(m_state.forNode(node).m_type & SpecCell); }
     bool isKnownNotOther(Node* node) { return !(m_state.forNode(node).m_type & SpecOther); }
+
+    bool canBeRope(Edge&);
     
     UniquedStringImpl* identifierUID(unsigned index)
     {
index a06ac4e..9c3c1c7 100644 (file)
@@ -86,6 +86,7 @@ AbstractHeapRepository::AbstractHeapRepository()
     JSCell_typeInfoFlags.changeParent(&JSCell_usefulBytes);
     JSCell_cellState.changeParent(&JSCell_usefulBytes);
     JSRopeString_flags.changeParent(&JSRopeString_fiber0);
+    JSRopeString_length.changeParent(&JSRopeString_fiber1);
 
     RELEASE_ASSERT(!JSCell_freeListNext.offset());
 }
index 1bdcf7e..4bf88b5 100644 (file)
@@ -89,12 +89,10 @@ namespace JSC { namespace FTL {
     macro(JSPropertyNameEnumerator_endStructurePropertyIndex, JSPropertyNameEnumerator::endStructurePropertyIndexOffset()) \
     macro(JSPropertyNameEnumerator_indexLength, JSPropertyNameEnumerator::indexedLengthOffset()) \
     macro(JSRopeString_flags, JSRopeString::offsetOfFlags()) \
-    macro(JSRopeString_fiber0, JSRopeString::offsetOfFiber0()) \
     macro(JSRopeString_length, JSRopeString::offsetOfLength()) \
-    macro(JSRopeString_fiber1Lower, JSRopeString::offsetOfFiber1Lower()) \
-    macro(JSRopeString_fiber1Upper, JSRopeString::offsetOfFiber1Upper()) \
-    macro(JSRopeString_fiber2Lower, JSRopeString::offsetOfFiber2Lower()) \
-    macro(JSRopeString_fiber2Upper, JSRopeString::offsetOfFiber2Upper()) \
+    macro(JSRopeString_fiber0, JSRopeString::offsetOfFiber0()) \
+    macro(JSRopeString_fiber1, JSRopeString::offsetOfFiber1()) \
+    macro(JSRopeString_fiber2, JSRopeString::offsetOfFiber2()) \
     macro(JSScope_next, JSScope::offsetOfNext()) \
     macro(JSSymbolTableObject_symbolTable, JSSymbolTableObject::offsetOfSymbolTable()) \
     macro(JSWrapperObject_internalValue, JSWrapperObject::internalValueOffset()) \
index 9b38f3a..9587df8 100644 (file)
@@ -6524,6 +6524,7 @@ private:
             numKids = 2;
         }
         
+        LBasicBlock emptyCase = m_out.newBlock();
         LBasicBlock slowPath = m_out.newBlock();
         LBasicBlock continuation = m_out.newBlock();
         
@@ -6532,18 +6533,8 @@ private:
         LValue result = allocateCell(
             m_out.constIntPtr(allocator.localAllocator()), vm().stringStructure.get(), slowPath);
         
-        m_out.storePtr(m_out.bitOr(kids[0], m_out.constIntPtr(JSString::isRopeInPointer)), result, m_heaps.JSRopeString_fiber0);
-
-        m_out.store32(m_out.castToInt32(kids[1]), result, m_heaps.JSRopeString_fiber1Lower);
-        m_out.store32As16(m_out.castToInt32(m_out.lShr(kids[1], m_out.constInt32(32))), result, m_heaps.JSRopeString_fiber1Upper);
-
-        if (numKids == 3) {
-            m_out.store32(m_out.castToInt32(kids[2]), result, m_heaps.JSRopeString_fiber2Lower);
-            m_out.store32As16(m_out.castToInt32(m_out.lShr(kids[2], m_out.constInt32(32))), result, m_heaps.JSRopeString_fiber2Upper);
-        } else {
-            m_out.store32(m_out.int32Zero, result, m_heaps.JSRopeString_fiber2Lower);
-            m_out.store32As16(m_out.int32Zero, result, m_heaps.JSRopeString_fiber2Upper);
-        }
+        // This puts nullptr for the first fiber. It makes visitChildren safe even if this JSRopeString is discarded due to the speculation failure in the following path.
+        m_out.storePtr(m_out.constIntPtr(JSString::isRopeInPointer), result, m_heaps.JSRopeString_fiber0);
 
         auto getFlagsAndLength = [&] (Edge& edge, LValue child) {
             if (JSString* string = edge->dynamicCastConstant<JSString*>(vm())) {
@@ -6560,7 +6551,7 @@ private:
             m_out.branch(isRopeString(child, edge), unsure(ropeCase), unsure(notRopeCase));
 
             LBasicBlock lastNext = m_out.appendTo(ropeCase, notRopeCase);
-            ValueFromBlock flagsForRope = m_out.anchor(m_out.load16ZeroExt32(child, m_heaps.JSRopeString_flags));
+            ValueFromBlock flagsForRope = m_out.anchor(m_out.load32NonNegative(child, m_heaps.JSRopeString_flags));
             ValueFromBlock lengthForRope = m_out.anchor(m_out.load32NonNegative(child, m_heaps.JSRopeString_length));
             m_out.jump(continuation);
 
@@ -6591,14 +6582,29 @@ private:
             };
             flagsAndLength = mergeFlagsAndLength(edges[i], kids[i], flagsAndLength);
         }
-        m_out.store32As16(flagsAndLength.flags, result, m_heaps.JSRopeString_flags);
-        m_out.store32(flagsAndLength.length, result, m_heaps.JSRopeString_length);
+
+        m_out.storePtr(
+            m_out.bitOr(
+                m_out.bitOr(kids[0], m_out.constIntPtr(JSString::isRopeInPointer)),
+                m_out.bitAnd(m_out.constIntPtr(JSRopeString::is8BitInPointer), m_out.zeroExtPtr(flagsAndLength.flags))),
+            result, m_heaps.JSRopeString_fiber0);
+        m_out.storePtr(
+            m_out.bitOr(m_out.zeroExtPtr(flagsAndLength.length), m_out.shl(kids[1], m_out.constInt32(32))),
+            result, m_heaps.JSRopeString_fiber1);
+        if (numKids == 2)
+            m_out.storePtr(m_out.lShr(kids[1], m_out.constInt32(32)), result, m_heaps.JSRopeString_fiber2);
+        else
+            m_out.storePtr(m_out.bitOr(m_out.lShr(kids[1], m_out.constInt32(32)), m_out.shl(kids[2], m_out.constInt32(16))), result, m_heaps.JSRopeString_fiber2);
         
         mutatorFence();
-        ValueFromBlock fastResult = m_out.anchor(m_out.select(m_out.isZero32(flagsAndLength.length), weakPointer(jsEmptyString(&m_graph.m_vm)), result));
+        ValueFromBlock fastResult = m_out.anchor(result);
+        m_out.branch(m_out.isZero32(flagsAndLength.length), rarely(emptyCase), usually(continuation));
+
+        LBasicBlock lastNext = m_out.appendTo(emptyCase, slowPath);
+        ValueFromBlock emptyResult = m_out.anchor(weakPointer(jsEmptyString(&m_graph.m_vm)));
         m_out.jump(continuation);
         
-        LBasicBlock lastNext = m_out.appendTo(slowPath, continuation);
+        m_out.appendTo(slowPath, continuation);
         LValue slowResultValue;
         VM& vm = this->vm();
         switch (numKids) {
@@ -6626,7 +6632,7 @@ private:
         m_out.jump(continuation);
         
         m_out.appendTo(continuation, lastNext);
-        setJSValue(m_out.phi(Int64, fastResult, slowResult));
+        setJSValue(m_out.phi(Int64, fastResult, emptyResult, slowResult));
     }
     
     void compileStringCharAt()
@@ -15680,6 +15686,11 @@ private:
                 if (value.isCell() && value.asCell()->type() == StringType && !asString(value)->isRope())
                     return m_out.booleanFalse;
             }
+            String value = edge->tryGetString(m_graph);
+            if (!value.isNull()) {
+                // If this value is LazyValue, it will be converted to JSString, and the result must be non-rope string.
+                return m_out.booleanFalse;
+            }
         }
 
         return m_out.testNonZeroPtr(m_out.loadPtr(string, m_heaps.JSString_value), m_out.constIntPtr(JSString::isRopeInPointer));
@@ -15694,6 +15705,11 @@ private:
                 if (value.isCell() && value.asCell()->type() == StringType && !asString(value)->isRope())
                     return m_out.booleanTrue;
             }
+            String value = edge->tryGetString(m_graph);
+            if (!value.isNull()) {
+                // If this value is LazyValue, it will be converted to JSString, and the result must be non-rope string.
+                return m_out.booleanTrue;
+            }
         }
 
         return m_out.testIsZeroPtr(m_out.loadPtr(string, m_heaps.JSString_value), m_out.constIntPtr(JSString::isRopeInPointer));
index 4c44f4b..c836fa2 100644 (file)
@@ -114,7 +114,7 @@ void JSString::visitChildren(JSCell* cell, SlotVisitor& visitor)
     
     uintptr_t pointer = thisObject->m_fiber;
     if (pointer & isRopeInPointer) {
-        if ((pointer & JSRopeString::stringMask) == JSRopeString::substringSentinel()) {
+        if (pointer & JSRopeString::isSubstringInPointer) {
             visitor.appendUnbarriered(static_cast<JSRopeString*>(thisObject)->fiber1());
             return;
         }
index 6fc5467..c8032f9 100644 (file)
@@ -86,9 +86,9 @@ struct StringViewWithUnderlyingString {
 //
 //              0                        8        10               16                       32                                     48
 // JSString     [   ID      ][  header  ][   String pointer      0]
-// JSRopeString [   ID      ][  header  ][ flags ][ 1st fiber    1][  length  ][2nd lower32][2nd upper16][3rd lower16][3rd upper32]
+// JSRopeString [   ID      ][  header  ][   1st fiber         xyz][  length  ][2nd lower32][2nd upper16][3rd lower16][3rd upper32]
 //                                                               ^
-//                                                            isRope bit
+//                                            x:(is8Bit),y:(isSubstring),z:(isRope) bit flags
 class JSString : public JSCell {
 public:
     friend class JIT;
@@ -266,13 +266,15 @@ private:
 class JSRopeString final : public JSString {
     friend class JSString;
 public:
+    // We use lower 3bits of fiber0 for flags. These bits are usable due to alignment, and it is OK even in 32bit architecture.
+    static constexpr uintptr_t is8BitInPointer = static_cast<uintptr_t>(StringImpl::flagIs8Bit());
+    static constexpr uintptr_t isSubstringInPointer = 0x2;
+    static_assert(is8BitInPointer == 0b100, "");
+    static_assert(isSubstringInPointer == 0b010, "");
+    static_assert(isRopeInPointer == 0b001, "");
+    static constexpr uintptr_t stringMask = ~(isRopeInPointer | is8BitInPointer | isSubstringInPointer);
 #if CPU(ADDRESS64)
     static_assert(sizeof(uintptr_t) == sizeof(uint64_t), "");
-    static constexpr uintptr_t flagMask = 0xffff000000000000ULL;
-    static constexpr uintptr_t stringMask = ~(flagMask | isRopeInPointer);
-    static_assert(StringImpl::flagIs8Bit() == 0b100, "");
-    static constexpr uintptr_t is8BitInPointer = static_cast<uintptr_t>(StringImpl::flagIs8Bit()) << 48;
-
     class CompactFibers {
     public:
         JSString* fiber1() const
@@ -289,13 +291,13 @@ public:
 
         JSString* fiber2() const
         {
-            return bitwise_cast<JSString*>(static_cast<uintptr_t>(m_fiber2Lower) | (static_cast<uintptr_t>(m_fiber2Upper) << 32));
+            return bitwise_cast<JSString*>(static_cast<uintptr_t>(m_fiber2Lower) | (static_cast<uintptr_t>(m_fiber2Upper) << 16));
         }
         void initializeFiber2(JSString* fiber)
         {
             uintptr_t pointer = bitwise_cast<uintptr_t>(fiber);
-            m_fiber2Lower = static_cast<uint32_t>(pointer);
-            m_fiber2Upper = static_cast<uint16_t>(pointer >> 32);
+            m_fiber2Lower = static_cast<uint16_t>(pointer);
+            m_fiber2Upper = static_cast<uint32_t>(pointer >> 16);
         }
 
         unsigned length() const { return m_length; }
@@ -305,22 +307,18 @@ public:
         }
 
         static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(CompactFibers, m_length); }
-        static ptrdiff_t offsetOfFiber1Lower() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1Lower); }
-        static ptrdiff_t offsetOfFiber1Upper() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1Upper); }
-        static ptrdiff_t offsetOfFiber2Lower() { return OBJECT_OFFSETOF(CompactFibers, m_fiber2Lower); }
-        static ptrdiff_t offsetOfFiber2Upper() { return OBJECT_OFFSETOF(CompactFibers, m_fiber2Upper); }
+        static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(CompactFibers, m_length); }
+        static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1Upper); }
 
     private:
         uint32_t m_length { 0 };
         uint32_t m_fiber1Lower { 0 };
         uint16_t m_fiber1Upper { 0 };
-        uint16_t m_fiber2Upper { 0 };
-        uint32_t m_fiber2Lower { 0 };
+        uint16_t m_fiber2Lower { 0 };
+        uint32_t m_fiber2Upper { 0 };
     };
     static_assert(sizeof(CompactFibers) == sizeof(void*) * 2, "");
 #else
-    static constexpr uintptr_t stringMask = ~(isRopeInPointer);
-
     class CompactFibers {
     public:
         JSString* fiber1() const
@@ -347,24 +345,12 @@ public:
             m_length = length;
         }
 
-        void initializeIs8Bit(bool flag)
-        {
-            if (flag)
-                m_flags |= static_cast<uintptr_t>(StringImpl::flagIs8Bit());
-            else
-                m_flags &= ~static_cast<uintptr_t>(StringImpl::flagIs8Bit());
-        }
-
-        bool is8Bit()
-        {
-            return m_flags & static_cast<uintptr_t>(StringImpl::flagIs8Bit());
-        }
-
         static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(CompactFibers, m_length); }
+        static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1); }
+        static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(CompactFibers, m_fiber2); }
 
     private:
         uint32_t m_length { 0 };
-        uint32_t m_flags { 0 };
         JSString* m_fiber1 { nullptr };
         JSString* m_fiber2 { nullptr };
     };
@@ -456,14 +442,18 @@ private:
 
     void initializeIs8Bit(bool flag) const
     {
-#if CPU(ADDRESS64)
         if (flag)
             m_fiber |= is8BitInPointer;
         else
             m_fiber &= ~is8BitInPointer;
-#else
-        m_compactFibers.initializeIs8Bit(flag);
-#endif
+    }
+
+    void initializeIsSubstring(bool flag) const
+    {
+        if (flag)
+            m_fiber |= isSubstringInPointer;
+        else
+            m_fiber &= ~isSubstringInPointer;
     }
 
     ALWAYS_INLINE void initializeLength(unsigned length)
@@ -472,6 +462,17 @@ private:
         m_compactFibers.initializeLength(length);
     }
 
+    JSRopeString(VM& vm)
+        : JSString(vm)
+    {
+        initializeIsSubstring(false);
+        initializeLength(0);
+        initializeIs8Bit(true);
+        initializeFiber0(nullptr);
+        initializeFiber1(nullptr);
+        initializeFiber2(nullptr);
+    }
+
     JSRopeString(VM& vm, JSString* s1, JSString* s2)
         : JSString(vm)
     {
@@ -549,32 +550,36 @@ private:
 
 public:
     static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfLength(); } // 32byte width.
+    static ptrdiff_t offsetOfFlags() { return offsetOfValue(); }
     static ptrdiff_t offsetOfFiber0() { return offsetOfValue(); }
-#if CPU(ADDRESS64)
-    static ptrdiff_t offsetOfFlags() { return offsetOfValue() + sizeof(uint16_t) * 3; } // 16byte width.
-    static ptrdiff_t offsetOfFiber1Lower() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber1Lower(); } // 32byte width.
-    static ptrdiff_t offsetOfFiber1Upper() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber1Upper(); } // 16byte width.
-    static ptrdiff_t offsetOfFiber2Lower() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber2Lower(); } // 32byte width.
-    static ptrdiff_t offsetOfFiber2Upper() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber2Upper(); } // 16byte width.
-#elif USE(JSVALUE64)
-    // FIXME: This is an temporary workaround to make JSC built on ARM64_32. Once we start calculating bits before storing them to JSRopeString,
-    // we do not need to have such a detailed information as an offset. After that, what we only need is offsetOfFiber0, offsetOfFiber1, and offsetOfFiber2.
-    // https://bugs.webkit.org/show_bug.cgi?id=195234
-    static ptrdiff_t offsetOfFlags() { ASSERT_NOT_REACHED(); return 0; }
-    static ptrdiff_t offsetOfFiber1Lower() { ASSERT_NOT_REACHED(); return 0; }
-    static ptrdiff_t offsetOfFiber1Upper() { ASSERT_NOT_REACHED(); return 0; }
-    static ptrdiff_t offsetOfFiber2Lower() { ASSERT_NOT_REACHED(); return 0; }
-    static ptrdiff_t offsetOfFiber2Upper() { ASSERT_NOT_REACHED(); return 0; }
-#endif
+    static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber1(); }
+    static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber2(); }
 
     static constexpr unsigned s_maxInternalRopeLength = 3;
 
+    // This JSRopeString is only used to simulate half-baked JSRopeString in DFG and FTL MakeRope. If OSR exit happens in
+    // the middle of MakeRope due to string length overflow, we have half-baked JSRopeString which is the same to the result
+    // of this function. This half-baked JSRopeString will not be exposed to users, but still collectors can see it due to
+    // the conservative stack scan. This JSRopeString is used to test the collector with such a half-baked JSRopeString.
+    // Because this JSRopeString breaks the JSString's invariant (only one singleton JSString can be zero length), almost all the
+    // operations in JS fail to handle this string correctly.
+    static JSRopeString* createNullForTesting(VM& vm)
+    {
+        JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap)) JSRopeString(vm);
+        newString->finishCreation(vm);
+        ASSERT(!newString->length());
+        ASSERT(newString->isRope());
+        ASSERT(fiber0() == nullptr);
+        return newString;
+    }
+
 private:
     static JSRopeString* create(VM& vm, JSString* s1, JSString* s2)
     {
         JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap)) JSRopeString(vm, s1, s2);
         newString->finishCreation(vm);
         ASSERT(newString->length());
+        ASSERT(newString->isRope());
         return newString;
     }
     static JSRopeString* create(VM& vm, JSString* s1, JSString* s2, JSString* s3)
@@ -582,6 +587,7 @@ private:
         JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap)) JSRopeString(vm, s1, s2, s3);
         newString->finishCreation(vm);
         ASSERT(newString->length());
+        ASSERT(newString->isRope());
         return newString;
     }
 
@@ -590,6 +596,7 @@ private:
         JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap)) JSRopeString(vm, base, offset, length);
         newString->finishCreationSubstring(vm, exec);
         ASSERT(newString->length());
+        ASSERT(newString->isRope());
         return newString;
     }
 
@@ -598,6 +605,7 @@ private:
         JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap, deferralContext)) JSRopeString(SubstringOfResolved, vm, base, offset, length);
         newString->finishCreationSubstringOfResolved(vm);
         ASSERT(newString->length());
+        ASSERT(newString->isRope());
         return newString;
     }
 
@@ -685,26 +693,6 @@ private:
         return static_cast<unsigned>(bitwise_cast<uintptr_t>(fiber2()));
     }
 
-    static constexpr uintptr_t notSubstringSentinel()
-    {
-        return 0;
-    }
-
-    static constexpr uintptr_t substringSentinel()
-    {
-        return 2;
-    }
-
-    bool isSubstring() const
-    {
-        return (m_fiber & stringMask) == substringSentinel();
-    }
-
-    void initializeIsSubstring(bool isSubstring)
-    {
-        m_fiber |= (isSubstring ? substringSentinel() : notSubstringSentinel());
-    }
-
     static_assert(s_maxInternalRopeLength >= 2, "");
     mutable CompactFibers m_compactFibers;
 
@@ -726,14 +714,9 @@ ALWAYS_INLINE bool JSString::is8Bit() const
 {
     uintptr_t pointer = m_fiber;
     if (pointer & isRopeInPointer) {
-#if CPU(ADDRESS64)
         // Do not load m_fiber twice. We should use the information in pointer.
         // Otherwise, JSRopeString may be converted to JSString between the first and second accesses.
         return pointer & JSRopeString::is8BitInPointer;
-#else
-        // It is OK to load flag since even if JSRopeString is converted to JSString, this flag still exists.
-        return jsCast<const JSRopeString*>(this)->m_compactFibers.is8Bit();
-#endif
     }
     return bitwise_cast<StringImpl*>(pointer)->is8Bit();
 }
@@ -1046,7 +1029,7 @@ ALWAYS_INLINE StringViewWithUnderlyingString JSString::viewWithUnderlyingString(
 
 inline bool JSString::isSubstring() const
 {
-    return isRope() && static_cast<const JSRopeString*>(this)->isSubstring();
+    return m_fiber & JSRopeString::isSubstringInPointer;
 }
 
 // --- JSValue inlines ----------------------------
index 12e153c..5560e5a 100644 (file)
@@ -1721,6 +1721,13 @@ static EncodedJSValue JSC_HOST_CALL functionCreateRuntimeArray(ExecState* exec)
     return JSValue::encode(array);
 }
 
+static EncodedJSValue JSC_HOST_CALL functionCreateNullRopeString(ExecState* exec)
+{
+    VM& vm = exec->vm();
+    JSLockHolder lock(vm);
+    return JSValue::encode(JSRopeString::createNullForTesting(vm));
+}
+
 static EncodedJSValue JSC_HOST_CALL functionCreateImpureGetter(ExecState* exec)
 {
     VM& vm = exec->vm();
@@ -2220,6 +2227,7 @@ void JSDollarVM::finishCreation(VM& vm)
     addFunction(vm, "createGlobalObject", functionCreateGlobalObject, 0);
     addFunction(vm, "createProxy", functionCreateProxy, 1);
     addFunction(vm, "createRuntimeArray", functionCreateRuntimeArray, 0);
+    addFunction(vm, "createNullRopeString", functionCreateNullRopeString, 0);
 
     addFunction(vm, "createImpureGetter", functionCreateImpureGetter, 1);
     addFunction(vm, "createCustomGetterObject", functionCreateCustomGetterObject, 0);