String.prototype.toLowerCase should be a DFG/FTL intrinsic
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Oct 2016 06:16:15 +0000 (06:16 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Oct 2016 06:16:15 +0000 (06:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=162887

Reviewed by Filip Pizlo and Yusuke Suzuki.

JSTests:

* microbenchmarks/to-lower-case.js: Added.
(assert):
(foo):
(bar):
* stress/to-lower-case.js: Added.
(assert):
(foo):

Source/JavaScriptCore:

This patch makes ToLowerCase an intrinsic in the DFG/FTL. On the fast
path, the intrinsic will loop over an 8-bit string ensuring it's already
lower case, and simply return the string. In the slow path, it'll call
into C code to make a new string.

This is a 7-8% speedup on ES6SampleBench/Basic.

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileToLowerCase):
* dfg/DFGSpeculativeJIT.h:
(JSC::DFG::SpeculativeJIT::callOperation):
* 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::compileToLowerCase):
* jit/JITOperations.h:
* runtime/Intrinsic.h:
* runtime/StringPrototype.cpp:
(JSC::StringPrototype::finishCreation):

Source/WTF:

This patch exposes a new StringImpl function called convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit
which extracts slow path for the 8-bit part of convertToLowercaseWithoutLocale
into a helper function. I decided to extract this into its own function because
it may be the case that JSCs JITs will want to continue the operation
after it has already ensured that part of an 8-bit string is lower case.

* wtf/text/StringImpl.cpp:
(WTF::StringImpl::convertToLowercaseWithoutLocale):
(WTF::StringImpl::convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit):
* wtf/text/StringImpl.h:
* wtf/text/WTFString.cpp:
(WTF::String::convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit):
* wtf/text/WTFString.h:

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

29 files changed:
JSTests/ChangeLog
JSTests/microbenchmarks/to-lower-case.js [new file with mode: 0644]
JSTests/stress/to-lower-case.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGOperations.cpp
Source/JavaScriptCore/dfg/DFGOperations.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/JITOperations.h
Source/JavaScriptCore/runtime/Intrinsic.h
Source/JavaScriptCore/runtime/StringPrototype.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/text/StringImpl.cpp
Source/WTF/wtf/text/StringImpl.h
Source/WTF/wtf/text/WTFString.cpp
Source/WTF/wtf/text/WTFString.h

index 54ca647..c2113d0 100644 (file)
@@ -1,3 +1,18 @@
+2016-10-04  Saam Barati  <sbarati@apple.com>
+
+        String.prototype.toLowerCase should be a DFG/FTL intrinsic
+        https://bugs.webkit.org/show_bug.cgi?id=162887
+
+        Reviewed by Filip Pizlo and Yusuke Suzuki.
+
+        * microbenchmarks/to-lower-case.js: Added.
+        (assert):
+        (foo):
+        (bar):
+        * stress/to-lower-case.js: Added.
+        (assert):
+        (foo):
+
 2016-10-04  JF Bastien  <jfbastien@apple.com>
 
         WebAssembly: handle a few corner cases
diff --git a/JSTests/microbenchmarks/to-lower-case.js b/JSTests/microbenchmarks/to-lower-case.js
new file mode 100644 (file)
index 0000000..25e633a
--- /dev/null
@@ -0,0 +1,36 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad assertion");
+}
+noInline(assert);
+
+let tests = [
+    ["foo", "foo"],
+    ["foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", "foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"],
+];
+
+function foo(a) {
+    return a.toLowerCase();
+}
+noInline(foo);
+
+function bar(a) {
+    a.toLowerCase();
+    assert(true); // side effects
+    a.toLowerCase();
+}
+noInline(bar);
+
+let start = Date.now();
+for (let i = 0; i < 500000; i++) {
+    for (let i = 0; i < tests.length; i++) {
+        let test = tests[i][0];
+        let result = tests[i][1];
+        assert(foo(test) === result);
+        bar(test);
+    }
+}
+
+const verbose = false;
+if (verbose)
+    print(Date.now() - start);
diff --git a/JSTests/stress/to-lower-case.js b/JSTests/stress/to-lower-case.js
new file mode 100644 (file)
index 0000000..28c4392
--- /dev/null
@@ -0,0 +1,30 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad assertion");
+}
+
+let tests = [
+    ["FOO", "foo"],
+    ["fff\u00C2", "fff\u00E2"],
+    ["foo", "foo"],
+    ["foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", "foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"],
+    ["BaR", "bar"],
+    ["FOO\u00A9", "foo\u00A9"],
+    ["#$#$", "#$#$"],
+    ["&&&\u00A9", "&&&\u00A9"],
+    ["&&&\u00C2", "&&&\u00E2"],
+    ["ABC\u0100", "abc\u0101"],
+];
+
+function foo(a) {
+    return a.toLowerCase();
+}
+noInline(foo);
+
+for (let i = 0; i < 10000; i++) {
+    for (let i = 0; i < tests.length; i++) {
+        let test = tests[i][0];
+        let result = tests[i][1];
+        assert(foo(test) === result);
+    }
+}
index 1a2d98e..a55e898 100644 (file)
@@ -1,3 +1,52 @@
+2016-10-04  Saam Barati  <sbarati@apple.com>
+
+        String.prototype.toLowerCase should be a DFG/FTL intrinsic
+        https://bugs.webkit.org/show_bug.cgi?id=162887
+
+        Reviewed by Filip Pizlo and Yusuke Suzuki.
+
+        This patch makes ToLowerCase an intrinsic in the DFG/FTL. On the fast
+        path, the intrinsic will loop over an 8-bit string ensuring it's already
+        lower case, and simply return the string. In the slow path, it'll call
+        into C code to make a new string.
+
+        This is a 7-8% speedup on ES6SampleBench/Basic.
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsicCall):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileToLowerCase):
+        * dfg/DFGSpeculativeJIT.h:
+        (JSC::DFG::SpeculativeJIT::callOperation):
+        * 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::compileToLowerCase):
+        * jit/JITOperations.h:
+        * runtime/Intrinsic.h:
+        * runtime/StringPrototype.cpp:
+        (JSC::StringPrototype::finishCreation):
+
 2016-10-04  Brian Burg  <bburg@apple.com>
 
         Web Inspector: don't synchronously send a listing message if we might need to query _WKAutomationDelegate
index ce6bef9..2d50c64 100644 (file)
@@ -1005,6 +1005,11 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         break;
     }
 
+    case ToLowerCase: {
+        forNode(node).setType(m_graph, SpecString);
+        break;
+    }
+
     case LoadFromJSMapBucket:
         forNode(node).makeHeapTop();
         break;
index 0b5fb95..dd70e3d 100644 (file)
@@ -2580,6 +2580,20 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, int resultOperand, Intrin
         return true;
     }
 
+    case ToLowerCaseIntrinsic: {
+        if (argumentCountIncludingThis != 1)
+            return false;
+
+        if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType))
+            return false;
+
+        insertChecks();
+        Node* thisString = get(virtualRegisterForArgument(0, registerOffset));
+        Node* result = addToGraph(ToLowerCase, thisString);
+        set(VirtualRegister(resultOperand), result);
+        return true;
+    }
+
     default:
         return false;
     }
index 1e555fe..ed23afa 100644 (file)
@@ -1291,6 +1291,10 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         read(MiscFields);
         def(HeapLocation(MapHasLoc, MiscFields, node->child1()), LazyNode(node));
         return;
+
+    case ToLowerCase:
+        def(PureValue(node));
+        return;
         
     case LastNodeType:
         RELEASE_ASSERT_NOT_REACHED();
index 3185596..9f13c73 100644 (file)
@@ -292,6 +292,7 @@ bool doesGC(Graph& graph, Node* node)
     case StringReplace:
     case StringReplaceRegExp:
     case CreateRest:
+    case ToLowerCase:
         return true;
         
     case MultiPutByOffset:
index fd6dd54..d058949 100644 (file)
@@ -1673,6 +1673,15 @@ private:
             break;
         }
 
+        case ToLowerCase: {
+            // We currently only support StringUse since that will ensure that
+            // ToLowerCase is a pure operation. If we decide to update this with
+            // more types in the future, we need to ensure that the clobberize rules
+            // are correct.
+            fixEdge<StringUse>(node->child1());
+            break;
+        }
+
         case DefineAccessorProperty: {
             fixEdge<CellUse>(m_graph.varArgChild(node, 0));
             Edge& propertyEdge = m_graph.varArgChild(node, 1);
index d5ada89..8eaf724 100644 (file)
@@ -396,6 +396,8 @@ namespace JSC { namespace DFG {
     macro(GetMapBucket, NodeResultJS) \
     macro(LoadFromJSMapBucket, NodeResultJS) \
     macro(IsNonEmptyMapBucket, NodeResultBoolean) \
+    \
+    macro(ToLowerCase, NodeResultJS) \
 
 // This enum generates a monotonically increasing id for all Node types,
 // and is used by the subsequent enum to fill out the id (as accessed via the NodeIdMask).
index 3aed72c..c079f9a 100644 (file)
@@ -1514,6 +1514,22 @@ StringImpl* JIT_OPERATION operationResolveRope(ExecState* exec, JSString* string
     return string->value(exec).impl();
 }
 
+JSString* JIT_OPERATION operationToLowerCase(ExecState* exec, JSString* string, uint32_t failingIndex)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    const String& inputString = string->value(exec);
+    RETURN_IF_EXCEPTION(scope, nullptr);
+    String lowercasedString = inputString.is8Bit() ? inputString.convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit(failingIndex) : inputString.convertToLowercaseWithoutLocale();
+    if (lowercasedString.impl() == inputString.impl())
+        return string;
+    scope.release();
+    return jsString(exec, lowercasedString);
+}
+
 JSString* JIT_OPERATION operationSingleCharacterString(ExecState* exec, int32_t character)
 {
     VM& vm = exec->vm();
index 11c2b8c..9578cae 100644 (file)
@@ -155,6 +155,8 @@ char* JIT_OPERATION operationEnsureArrayStorage(ExecState*, JSCell*);
 StringImpl* JIT_OPERATION operationResolveRope(ExecState*, JSString*);
 JSString* JIT_OPERATION operationSingleCharacterString(ExecState*, int32_t);
 
+JSString* JIT_OPERATION operationToLowerCase(ExecState*, JSString*, uint32_t);
+
 int32_t JIT_OPERATION operationMapHash(ExecState*, EncodedJSValue input);
 JSCell* JIT_OPERATION operationJSMapFindBucket(ExecState*, JSCell*, EncodedJSValue, int32_t);
 JSCell* JIT_OPERATION operationJSSetFindBucket(ExecState*, JSCell*, EncodedJSValue, int32_t);
index 459418a..56ba571 100644 (file)
@@ -754,6 +754,11 @@ private:
             setPrediction(SpecInt32Only);
             break;
         }
+
+        case ToLowerCase:
+            setPrediction(SpecString);
+            break;
+
         case ArithPow:
         case ArithSqrt:
         case ArithFRound:
index abe672a..da82d7b 100644 (file)
@@ -361,6 +361,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case PutDynamicVar:
     case ResolveScope:
     case MapHash:
+    case ToLowerCase:
     case GetMapBucket:
     case LoadFromJSMapBucket:
     case IsNonEmptyMapBucket:
index 6b64454..3e17dec 100644 (file)
@@ -1490,6 +1490,60 @@ void SpeculativeJIT::compilePeepHoleBooleanBranch(Node* node, Node* branchNode,
     jump(notTaken);
 }
 
+void SpeculativeJIT::compileToLowerCase(Node* node)
+{
+    ASSERT(node->op() == ToLowerCase);
+    SpeculateCellOperand string(this, node->child1());
+    GPRTemporary temp(this);
+    GPRTemporary index(this);
+    GPRTemporary charReg(this);
+    GPRTemporary length(this);
+
+    GPRReg stringGPR = string.gpr();
+    GPRReg tempGPR = temp.gpr();
+    GPRReg indexGPR = index.gpr();
+    GPRReg charGPR = charReg.gpr();
+    GPRReg lengthGPR = length.gpr();
+
+    speculateString(node->child1(), stringGPR);
+
+    CCallHelpers::JumpList slowPath;
+
+    m_jit.move(TrustedImmPtr(0), indexGPR);
+
+    m_jit.loadPtr(MacroAssembler::Address(stringGPR, JSString::offsetOfValue()), tempGPR);
+    slowPath.append(m_jit.branchTestPtr(MacroAssembler::Zero, tempGPR));
+
+    slowPath.append(m_jit.branchTest32(
+        MacroAssembler::Zero, MacroAssembler::Address(tempGPR, StringImpl::flagsOffset()),
+        MacroAssembler::TrustedImm32(StringImpl::flagIs8Bit())));
+    m_jit.load32(MacroAssembler::Address(tempGPR, StringImpl::lengthMemoryOffset()), lengthGPR);
+    m_jit.loadPtr(MacroAssembler::Address(tempGPR, StringImpl::dataOffset()), tempGPR);
+
+    auto loopStart = m_jit.label();
+    auto loopDone = m_jit.branch32(CCallHelpers::AboveOrEqual, indexGPR, lengthGPR);
+    m_jit.load8(MacroAssembler::BaseIndex(tempGPR, indexGPR, MacroAssembler::TimesOne), charGPR);
+    slowPath.append(m_jit.branchTest32(CCallHelpers::NonZero, charGPR, TrustedImm32(~0x7F)));
+    m_jit.sub32(TrustedImm32('A'), charGPR);
+    slowPath.append(m_jit.branch32(CCallHelpers::BelowOrEqual, charGPR, TrustedImm32('Z' - 'A')));
+
+    m_jit.add32(TrustedImm32(1), indexGPR);
+    m_jit.jump().linkTo(loopStart, &m_jit);
+    
+    slowPath.link(&m_jit);
+    silentSpillAllRegisters(lengthGPR);
+    callOperation(operationToLowerCase, lengthGPR, stringGPR, indexGPR);
+    silentFillAllRegisters(lengthGPR);
+    m_jit.exceptionCheck();
+    auto done = m_jit.jump();
+
+    loopDone.link(&m_jit);
+    m_jit.move(stringGPR, lengthGPR);
+
+    done.link(&m_jit);
+    cellResult(lengthGPR, node);
+}
+
 void SpeculativeJIT::compilePeepHoleInt32Branch(Node* node, Node* branchNode, JITCompiler::RelationalCondition condition)
 {
     BasicBlock* taken = branchNode->branchData()->taken.block;
index 1dab921..536ed79 100644 (file)
@@ -938,6 +938,11 @@ public:
         m_jit.setupArgumentsWithExecState(cell);
         return appendCallSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(Jss_JITOperation_EJssUi operation, GPRReg result, GPRReg arg1, GPRReg arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallSetResult(operation, result);
+    }
     JITCompiler::Call callOperation(P_JITOperation_EO operation, GPRReg result, GPRReg object)
     {
         m_jit.setupArgumentsWithExecState(object);
@@ -2651,6 +2656,7 @@ public:
     void compileCompareEqPtr(Node*);
     void compileDefineDataProperty(Node*);
     void compileDefineAccessorProperty(Node*);
+    void compileToLowerCase(Node*);
 
     void moveTrueTo(GPRReg);
     void moveFalseTo(GPRReg);
index 5884638..c95949e 100644 (file)
@@ -2678,6 +2678,11 @@ void SpeculativeJIT::compile(Node* node)
         } }
         break;
     }
+    
+    case ToLowerCase: {
+        compileToLowerCase(node);
+        break;
+    }
 
     case GetByValWithThis: {
         JSValueOperand base(this, node->child1());
index 96a7daf..4dd21dd 100644 (file)
@@ -4888,6 +4888,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case ToLowerCase: {
+        compileToLowerCase(node);
+        break;
+    }
+
     case IsObject: {
         JSValueOperand value(this, node->child1());
         GPRTemporary result(this, Reuse, value);
index 719f52c..fe6b1c6 100644 (file)
@@ -101,6 +101,7 @@ namespace JSC { namespace FTL {
     macro(ScopedArgumentsTable_length, ScopedArgumentsTable::offsetOfLength()) \
     macro(StringImpl_data, StringImpl::dataOffset()) \
     macro(StringImpl_hashAndFlags, StringImpl::flagsOffset()) \
+    macro(StringImpl_length, StringImpl::lengthMemoryOffset()) \
     macro(Structure_classInfo, Structure::classInfoOffset()) \
     macro(Structure_globalObject, Structure::globalObjectOffset()) \
     macro(Structure_prototype, Structure::prototypeOffset()) \
index 3cef998..fbf871c 100644 (file)
@@ -265,6 +265,7 @@ inline CapabilityLevel canCompile(Node* node)
     case CompareStrictEq:
     case DefineDataProperty:
     case DefineAccessorProperty:
+    case ToLowerCase:
         // These are OK.
         break;
 
index 25dd667..96fabaa 100644 (file)
@@ -1043,6 +1043,9 @@ private:
         case Unreachable:
             compileUnreachable();
             break;
+        case ToLowerCase:
+            compileToLowerCase();
+            break;
 
         case PhantomLocal:
         case LoopHint:
@@ -8623,6 +8626,60 @@ private:
         nonSpeculativeCompare(intFunctor, fallbackFunction);
     }
 
+    void compileToLowerCase()
+    {
+        LBasicBlock notRope = m_out.newBlock();
+        LBasicBlock is8Bit = m_out.newBlock();
+        LBasicBlock loopTop = m_out.newBlock();
+        LBasicBlock loopBody = m_out.newBlock();
+        LBasicBlock slowPath = m_out.newBlock();
+        LBasicBlock continuation = m_out.newBlock();
+
+        LValue string = lowString(m_node->child1());
+        ValueFromBlock startIndex = m_out.anchor(m_out.constInt32(0));
+        ValueFromBlock startIndexForCall = m_out.anchor(m_out.constInt32(0));
+        LValue impl = m_out.loadPtr(string, m_heaps.JSString_value);
+        m_out.branch(m_out.isZero64(impl),
+            unsure(slowPath), unsure(notRope));
+
+        LBasicBlock lastNext = m_out.appendTo(notRope, is8Bit);
+
+        m_out.branch(
+            m_out.testIsZero32(
+                m_out.load32(impl, m_heaps.StringImpl_hashAndFlags),
+                m_out.constInt32(StringImpl::flagIs8Bit())),
+            unsure(slowPath), unsure(is8Bit));
+
+        m_out.appendTo(is8Bit, loopTop);
+        LValue length = m_out.load32(impl, m_heaps.StringImpl_length);
+        LValue buffer = m_out.loadPtr(impl, m_heaps.StringImpl_data);
+        ValueFromBlock fastResult = m_out.anchor(string);
+        m_out.jump(loopTop);
+
+        m_out.appendTo(loopTop, loopBody);
+        LValue index = m_out.phi(Int32, startIndex);
+        ValueFromBlock indexFromBlock = m_out.anchor(index);
+        m_out.branch(m_out.below(index, length),
+            unsure(loopBody), unsure(continuation));
+
+        m_out.appendTo(loopBody, slowPath);
+
+        LValue byte = m_out.load8ZeroExt32(m_out.baseIndex(m_heaps.characters8, buffer, m_out.zeroExtPtr(index)));
+        LValue isInvalidAsciiRange = m_out.bitAnd(byte, m_out.constInt32(~0x7F));
+        LValue isUpperCase = m_out.belowOrEqual(m_out.sub(byte, m_out.constInt32('A')), m_out.constInt32('Z' - 'A'));
+        LValue isBadCharacter = m_out.bitOr(isInvalidAsciiRange, isUpperCase);
+        m_out.addIncomingToPhi(index, m_out.anchor(m_out.add(index, m_out.int32One)));
+        m_out.branch(isBadCharacter, unsure(slowPath), unsure(loopTop));
+
+        m_out.appendTo(slowPath, continuation);
+        LValue slowPathIndex = m_out.phi(Int32, startIndexForCall, indexFromBlock);
+        ValueFromBlock slowResult = m_out.anchor(vmCall(pointerType(), m_out.operation(operationToLowerCase), m_callFrame, string, slowPathIndex));
+        m_out.jump(continuation);
+
+        m_out.appendTo(continuation, lastNext);
+        setJSValue(m_out.phi(pointerType(), fastResult, slowResult));
+    }
+
     void compileResolveScope()
     {
         UniquedStringImpl* uid = m_graph.identifiers()[m_node->identifierNumber()];
index 551a78d..8065177 100644 (file)
@@ -309,6 +309,7 @@ typedef SlowPathReturnType (JIT_OPERATION *Sprt_JITOperation_ECli)(ExecState*, C
 typedef StringImpl* (JIT_OPERATION *T_JITOperation_EJss)(ExecState*, JSString*);
 typedef JSString* (JIT_OPERATION *Jss_JITOperation_EZ)(ExecState*, int32_t);
 typedef JSString* (JIT_OPERATION *Jss_JITOperation_EJJJ)(ExecState*, EncodedJSValue, EncodedJSValue, EncodedJSValue);
+typedef JSString* (JIT_OPERATION *Jss_JITOperation_EJssUi)(ExecState*, JSString*, uint32_t);
 
 // This method is used to lookup an exception hander, keyed by faultLocation, which is
 // the return location from one of the calls out to one of the helper operations above.
index 3ee9dbe..62133f8 100644 (file)
@@ -65,6 +65,7 @@ enum JS_EXPORT_PRIVATE Intrinsic {
     JSMapHasIntrinsic,
     JSSetHasIntrinsic,
     HasOwnPropertyIntrinsic,
+    ToLowerCaseIntrinsic,
 
     // Getter intrinsics.
     TypedArrayLengthIntrinsic,
index 04d783e..12c44da 100644 (file)
@@ -142,7 +142,7 @@ void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSStr
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("slice", stringProtoFuncSlice, DontEnum, 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substr", stringProtoFuncSubstr, DontEnum, 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substring", stringProtoFuncSubstring, DontEnum, 2);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLowerCase", stringProtoFuncToLowerCase, DontEnum, 0);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("toLowerCase", stringProtoFuncToLowerCase, DontEnum, 0, ToLowerCaseIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
 #if ENABLE(INTL)
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("localeCompare", stringPrototypeLocaleCompareCodeGenerator, DontEnum);
index 52ac320..c302906 100644 (file)
@@ -1,3 +1,24 @@
+2016-10-04  Saam Barati  <sbarati@apple.com>
+
+        String.prototype.toLowerCase should be a DFG/FTL intrinsic
+        https://bugs.webkit.org/show_bug.cgi?id=162887
+
+        Reviewed by Filip Pizlo and Yusuke Suzuki.
+
+        This patch exposes a new StringImpl function called convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit
+        which extracts slow path for the 8-bit part of convertToLowercaseWithoutLocale
+        into a helper function. I decided to extract this into its own function because
+        it may be the case that JSCs JITs will want to continue the operation
+        after it has already ensured that part of an 8-bit string is lower case.
+
+        * wtf/text/StringImpl.cpp:
+        (WTF::StringImpl::convertToLowercaseWithoutLocale):
+        (WTF::StringImpl::convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit):
+        * wtf/text/StringImpl.h:
+        * wtf/text/WTFString.cpp:
+        (WTF::String::convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit):
+        * wtf/text/WTFString.h:
+
 2016-10-04  Chris Dumez  <cdumez@apple.com>
 
         Implement KeyboardEvent.code from the UI Event spec
index a026303..5cd66c1 100644 (file)
@@ -363,39 +363,20 @@ UChar32 StringImpl::characterStartingAt(unsigned i)
 Ref<StringImpl> StringImpl::convertToLowercaseWithoutLocale()
 {
     // Note: At one time this was a hot function in the Dromaeo benchmark, specifically the
-    // no-op code path up through the first 'return' statement.
+    // no-op code path that may return ourself if we find no upper case letters and no invalid
+    // ASCII letters.
 
     // First scan the string for uppercase and non-ASCII characters:
     if (is8Bit()) {
-        unsigned failingIndex;
         for (unsigned i = 0; i < m_length; ++i) {
             LChar character = m_data8[i];
-            if (UNLIKELY((character & ~0x7F) || isASCIIUpper(character))) {
-                failingIndex = i;
-                goto SlowPath;
-            }
-        }
-        return *this;
-
-SlowPath:
-        LChar* data8;
-        auto newImpl = createUninitializedInternalNonEmpty(m_length, data8);
-
-        for (unsigned i = 0; i < failingIndex; ++i)
-            data8[i] = m_data8[i];
-
-        for (unsigned i = failingIndex; i < m_length; ++i) {
-            LChar character = m_data8[i];
-            if (!(character & ~0x7F))
-                data8[i] = toASCIILower(character);
-            else {
-                ASSERT(u_tolower(character) <= 0xFF);
-                data8[i] = static_cast<LChar>(u_tolower(character));
-            }
+            if (UNLIKELY((character & ~0x7F) || isASCIIUpper(character)))
+                return convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit(i);
         }
 
-        return newImpl;
+        return *this;
     }
+
     bool noUpper = true;
     unsigned ored = 0;
 
@@ -441,6 +422,30 @@ SlowPath:
     return newImpl;
 }
 
+Ref<StringImpl> StringImpl::convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit(unsigned failingIndex)
+{
+    ASSERT(is8Bit());
+    LChar* data8;
+    auto newImpl = createUninitializedInternalNonEmpty(m_length, data8);
+
+    for (unsigned i = 0; i < failingIndex; ++i) {
+        ASSERT(!(m_data8[i] & ~0x7F) && !isASCIIUpper(m_data8[i]));
+        data8[i] = m_data8[i];
+    }
+
+    for (unsigned i = failingIndex; i < m_length; ++i) {
+        LChar character = m_data8[i];
+        if (!(character & ~0x7F))
+            data8[i] = toASCIILower(character);
+        else {
+            ASSERT(u_tolower(character) <= 0xFF);
+            data8[i] = static_cast<LChar>(u_tolower(character));
+        }
+    }
+
+    return newImpl;
+}
+
 Ref<StringImpl> StringImpl::convertToUppercaseWithoutLocale()
 {
     // This function could be optimized for no-op cases the way
index 421a923..176aa59 100644 (file)
@@ -682,6 +682,7 @@ public:
     WTF_EXPORT_STRING_API Ref<StringImpl> convertToASCIILowercase();
     WTF_EXPORT_STRING_API Ref<StringImpl> convertToASCIIUppercase();
     WTF_EXPORT_STRING_API Ref<StringImpl> convertToLowercaseWithoutLocale();
+    WTF_EXPORT_STRING_API Ref<StringImpl> convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit(unsigned);
     WTF_EXPORT_STRING_API Ref<StringImpl> convertToUppercaseWithoutLocale();
     WTF_EXPORT_STRING_API Ref<StringImpl> convertToLowercaseWithLocale(const AtomicString& localeIdentifier);
     WTF_EXPORT_STRING_API Ref<StringImpl> convertToUppercaseWithLocale(const AtomicString& localeIdentifier);
index fb60400..4f49ebc 100644 (file)
@@ -358,6 +358,13 @@ String String::convertToLowercaseWithoutLocale() const
     return m_impl->convertToLowercaseWithoutLocale();
 }
 
+String String::convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit(unsigned failingIndex) const
+{
+    if (!m_impl)
+        return String();
+    return m_impl->convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit(failingIndex);
+}
+
 String String::convertToUppercaseWithoutLocale() const
 {
     if (!m_impl)
index 9c21c45..f53ac11 100644 (file)
@@ -330,6 +330,7 @@ public:
     WTF_EXPORT_STRING_API String convertToASCIILowercase() const;
     WTF_EXPORT_STRING_API String convertToASCIIUppercase() const;
     WTF_EXPORT_STRING_API String convertToLowercaseWithoutLocale() const;
+    WTF_EXPORT_STRING_API String convertToLowercaseWithoutLocaleStartingAtFailingIndex8Bit(unsigned) const;
     WTF_EXPORT_STRING_API String convertToUppercaseWithoutLocale() const;
     WTF_EXPORT_STRING_API String convertToLowercaseWithLocale(const AtomicString& localeIdentifier) const;
     WTF_EXPORT_STRING_API String convertToUppercaseWithLocale(const AtomicString& localeIdentifier) const;