CSS JIT: add support for :last-child and :only-child
authorbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Apr 2014 02:25:19 +0000 (02:25 +0000)
committerbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Apr 2014 02:25:19 +0000 (02:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=131283

Reviewed by Andreas Kling.

This is the straightforward implementation of :last-child and :only-child.

Both are extremely similar to :first-child. There are only minor differences:
-:last-child has an additional check for the flag IsParsingChildrenFinished.
-:only-child is like :first-child + :last-child but with combined marking.

* cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::addPseudoType):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateWalkToNextAdjacentElement):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementMatching):
(WebCore::SelectorCompiler::SelectorCodeGenerator::jumpIfNoPreviousAdjacentElement):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsFirstChild):
(WebCore::SelectorCompiler::SelectorCodeGenerator::jumpIfNoNextAdjacentElement):
(WebCore::SelectorCompiler::markElementWithSetChildrenAffectedByLastChildRules):
(WebCore::SelectorCompiler::setLastChildState):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsLastChild):
(WebCore::SelectorCompiler::markElementWithSetChildrenAffectedByFirstChildAndLastChildRules):
(WebCore::SelectorCompiler::setOnlyChildState):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsOnlyChild):
* dom/Node.h:
(WebCore::Node::nextSiblingMemoryOffset):
(WebCore::Node::flagIsParsingChildrenFinished):
* rendering/style/RenderStyle.h:
Removed the unused flags, I will add them back later as needed.

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

Source/WebCore/ChangeLog
Source/WebCore/cssjit/SelectorCompiler.cpp
Source/WebCore/dom/Node.h
Source/WebCore/rendering/style/RenderStyle.h

index 5202e97..daec4e4 100644 (file)
@@ -1,3 +1,35 @@
+2014-04-06  Benjamin Poulain  <benjamin@webkit.org>
+
+        CSS JIT: add support for :last-child and :only-child
+        https://bugs.webkit.org/show_bug.cgi?id=131283
+
+        Reviewed by Andreas Kling.
+
+        This is the straightforward implementation of :last-child and :only-child.
+
+        Both are extremely similar to :first-child. There are only minor differences:
+        -:last-child has an additional check for the flag IsParsingChildrenFinished.
+        -:only-child is like :first-child + :last-child but with combined marking.
+
+        * cssjit/SelectorCompiler.cpp:
+        (WebCore::SelectorCompiler::addPseudoType):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateWalkToNextAdjacentElement):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementMatching):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::jumpIfNoPreviousAdjacentElement):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsFirstChild):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::jumpIfNoNextAdjacentElement):
+        (WebCore::SelectorCompiler::markElementWithSetChildrenAffectedByLastChildRules):
+        (WebCore::SelectorCompiler::setLastChildState):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsLastChild):
+        (WebCore::SelectorCompiler::markElementWithSetChildrenAffectedByFirstChildAndLastChildRules):
+        (WebCore::SelectorCompiler::setOnlyChildState):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsOnlyChild):
+        * dom/Node.h:
+        (WebCore::Node::nextSiblingMemoryOffset):
+        (WebCore::Node::flagIsParsingChildrenFinished):
+        * rendering/style/RenderStyle.h:
+        Removed the unused flags, I will add them back later as needed.
+
 2014-04-06  Darin Adler  <darin@apple.com>
 
         Rework CSS calc logic, fixing some reference count mistakes in Length
index 57bdf26..1683590 100644 (file)
@@ -156,6 +156,7 @@ private:
     void generateParentElementTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateAncestorTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment&);
 
+    void generateWalkToNextAdjacentElement(Assembler::JumpList& failureCases, Assembler::RegisterID);
     void generateWalkToPreviousAdjacentElement(Assembler::JumpList& failureCases, Assembler::RegisterID);
     void generateWalkToPreviousAdjacent(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateDirectAdjacentTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment&);
@@ -171,7 +172,11 @@ private:
     void generateElementMatching(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateElementDataMatching(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateElementFunctionCallTest(Assembler::JumpList& failureCases, JSC::FunctionPtr);
+    Assembler::JumpList jumpIfNoPreviousAdjacentElement();
     void generateElementIsFirstChild(Assembler::JumpList& failureCases, const SelectorFragment&);
+    Assembler::JumpList jumpIfNoNextAdjacentElement();
+    void generateElementIsLastChild(Assembler::JumpList& failureCases, const SelectorFragment&);
+    void generateElementIsOnlyChild(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateSynchronizeStyleAttribute(Assembler::RegisterID elementDataArraySizeAndFlags);
     void generateSynchronizeAllAnimatedSVGAttribute(Assembler::RegisterID elementDataArraySizeAndFlags);
     void generateElementAttributesMatching(Assembler::JumpList& failureCases, const LocalRegister& elementDataAddress, const SelectorFragment&);
@@ -312,6 +317,8 @@ static inline FunctionType addPseudoType(CSSSelector::PseudoType type, SelectorF
         return FunctionType::SimpleSelectorChecker;
 
     case CSSSelector::PseudoFirstChild:
+    case CSSSelector::PseudoLastChild:
+    case CSSSelector::PseudoOnlyChild:
         pseudoClasses.pseudoClasses.add(type);
         if (selectorContext == SelectorContext::QuerySelector)
             return FunctionType::SimpleSelectorChecker;
@@ -794,6 +801,14 @@ void SelectorCodeGenerator::generateAncestorTreeWalker(Assembler::JumpList& fail
     tagMatchingLocalFailureCases.linkTo(loopStart, &m_assembler);
 }
 
+inline void SelectorCodeGenerator::generateWalkToNextAdjacentElement(Assembler::JumpList& failureCases, Assembler::RegisterID workRegister)
+{
+    Assembler::Label loopStart = m_assembler.label();
+    m_assembler.loadPtr(Assembler::Address(workRegister, Node::nextSiblingMemoryOffset()), workRegister);
+    failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, workRegister));
+    testIsElementFlagOnNode(Assembler::Zero, m_assembler, workRegister).linkTo(loopStart, &m_assembler);
+}
+
 inline void SelectorCodeGenerator::generateWalkToPreviousAdjacentElement(Assembler::JumpList& failureCases, Assembler::RegisterID workRegister)
 {
     Assembler::Label loopStart = m_assembler.label();
@@ -993,8 +1008,12 @@ void SelectorCodeGenerator::generateElementMatching(Assembler::JumpList& failure
 
     generateElementDataMatching(failureCases, fragment);
 
+    if (fragment.pseudoClasses.contains(CSSSelector::PseudoOnlyChild))
+        generateElementIsOnlyChild(failureCases, fragment);
     if (fragment.pseudoClasses.contains(CSSSelector::PseudoFirstChild))
         generateElementIsFirstChild(failureCases, fragment);
+    if (fragment.pseudoClasses.contains(CSSSelector::PseudoLastChild))
+        generateElementIsLastChild(failureCases, fragment);
 }
 
 void SelectorCodeGenerator::generateElementDataMatching(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
@@ -1419,13 +1438,19 @@ static void setFirstChildState(Element* element)
         style->setFirstChildState();
 }
 
+inline Assembler::JumpList SelectorCodeGenerator::jumpIfNoPreviousAdjacentElement()
+{
+    Assembler::JumpList successCase;
+    LocalRegister previousSibling(m_registerAllocator);
+    m_assembler.move(elementAddressRegister, previousSibling);
+    generateWalkToPreviousAdjacentElement(successCase, previousSibling);
+    return successCase;
+}
+
 void SelectorCodeGenerator::generateElementIsFirstChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
 {
     if (m_selectorContext == SelectorContext::QuerySelector) {
-        Assembler::JumpList successCase;
-        LocalRegister previousSibling(m_registerAllocator);
-        m_assembler.move(elementAddressRegister, previousSibling);
-        generateWalkToPreviousAdjacentElement(successCase, previousSibling);
+        Assembler::JumpList successCase = jumpIfNoPreviousAdjacentElement();
         failureCases.append(m_assembler.jump());
         successCase.link(&m_assembler);
         LocalRegister parent(m_registerAllocator);
@@ -1441,10 +1466,7 @@ void SelectorCodeGenerator::generateElementIsFirstChild(Assembler::JumpList& fai
     m_assembler.move(Assembler::TrustedImm32(0), isFirstChildRegister);
 
     {
-        Assembler::JumpList successCase;
-        LocalRegister previousSibling(m_registerAllocator);
-        m_assembler.move(elementAddressRegister, previousSibling);
-        generateWalkToPreviousAdjacentElement(successCase, previousSibling);
+        Assembler::JumpList successCase = jumpIfNoPreviousAdjacentElement();
 
         // If there was a sibling element, the element was not the first child -> failure case.
         m_assembler.move(Assembler::TrustedImm32(1), isFirstChildRegister);
@@ -1489,6 +1511,190 @@ void SelectorCodeGenerator::generateElementIsFirstChild(Assembler::JumpList& fai
     failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isFirstChildRegister));
 }
 
+Assembler::JumpList SelectorCodeGenerator::jumpIfNoNextAdjacentElement()
+{
+    Assembler::JumpList successCase;
+    LocalRegister nextSibling(m_registerAllocator);
+    m_assembler.move(elementAddressRegister, nextSibling);
+    generateWalkToNextAdjacentElement(successCase, nextSibling);
+    return successCase;
+}
+
+static void markElementWithSetChildrenAffectedByLastChildRules(Element *element)
+{
+    element->setChildrenAffectedByLastChildRules();
+}
+
+static void setLastChildState(Element* element)
+{
+    if (RenderStyle* style = element->renderStyle())
+        style->setLastChildState();
+}
+
+void SelectorCodeGenerator::generateElementIsLastChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
+{
+    if (m_selectorContext == SelectorContext::QuerySelector) {
+        Assembler::JumpList successCase = jumpIfNoNextAdjacentElement();
+        failureCases.append(m_assembler.jump());
+
+        successCase.link(&m_assembler);
+        LocalRegister parent(m_registerAllocator);
+        generateWalkToParentElement(failureCases, parent);
+
+        failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parent, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
+
+        return;
+    }
+
+    Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister();
+    generateWalkToParentElement(failureCases, parentElement);
+
+    // Zero in isLastChildRegister is the success case. The register is set to non-zero if a sibling if found.
+    LocalRegister isLastChildRegister(m_registerAllocator);
+    m_assembler.move(Assembler::TrustedImm32(0), isLastChildRegister);
+
+    {
+        Assembler::Jump notFinishedParsingChildren = m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentElement, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished()));
+
+        Assembler::JumpList successCase = jumpIfNoNextAdjacentElement();
+
+        notFinishedParsingChildren.link(&m_assembler);
+        m_assembler.move(Assembler::TrustedImm32(1), isLastChildRegister);
+
+        successCase.link(&m_assembler);
+    }
+
+    LocalRegister checkingContext(m_registerAllocator);
+    Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext);
+
+    m_registerAllocator.deallocateRegister(parentElement);
+    FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls);
+    functionCall.setFunctionAddress(markElementWithSetChildrenAffectedByLastChildRules);
+    functionCall.setOneArgument(parentElement);
+    functionCall.call();
+
+    // The parent marking is unconditional. If the matching is not a success, we can now fail.
+    // Otherwise we need to apply setLastChildState() on the RenderStyle.
+    failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isLastChildRegister));
+
+    if (fragment.relationToRightFragment == FragmentRelation::Rightmost) {
+        LocalRegister childStyle(m_registerAllocator);
+        m_assembler.loadPtr(Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, elementStyle)), childStyle);
+
+        // FIXME: We should look into doing something smart in MacroAssembler instead.
+        LocalRegister flags(m_registerAllocator);
+        Assembler::Address flagAddress(childStyle, RenderStyle::noninheritedFlagsMemoryOffset() + RenderStyle::NonInheritedFlags::flagsMemoryOffset());
+        m_assembler.load64(flagAddress, flags);
+        LocalRegister isLastChildStateFlagImmediate(m_registerAllocator);
+        m_assembler.move(Assembler::TrustedImm64(RenderStyle::NonInheritedFlags::setLastChildStateFlags()), isLastChildStateFlagImmediate);
+        m_assembler.or64(isLastChildStateFlagImmediate, flags);
+        m_assembler.store64(flags, flagAddress);
+    } else {
+        FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls);
+        functionCall.setFunctionAddress(setLastChildState);
+        Assembler::RegisterID elementAddress = elementAddressRegister;
+        functionCall.setOneArgument(elementAddress);
+        functionCall.call();
+    }
+
+    notResolvingStyle.link(&m_assembler);
+    failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isLastChildRegister));
+}
+
+static void markElementWithSetChildrenAffectedByFirstChildAndLastChildRules(Element *element)
+{
+    element->setChildrenAffectedByFirstChildRules();
+    element->setChildrenAffectedByLastChildRules();
+}
+
+static void setOnlyChildState(Element* element)
+{
+    if (RenderStyle* style = element->renderStyle()) {
+        style->setFirstChildState();
+        style->setLastChildState();
+    }
+}
+
+void SelectorCodeGenerator::generateElementIsOnlyChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
+{
+    // Is Only child is pretty much a combination of isFirstChild + isLastChild. The main difference is that tree marking is combined.
+    if (m_selectorContext == SelectorContext::QuerySelector) {
+        Assembler::JumpList previousSuccessCase = jumpIfNoPreviousAdjacentElement();
+        failureCases.append(m_assembler.jump());
+        previousSuccessCase.link(&m_assembler);
+
+        Assembler::JumpList nextSuccessCase = jumpIfNoNextAdjacentElement();
+        failureCases.append(m_assembler.jump());
+        nextSuccessCase.link(&m_assembler);
+
+        LocalRegister parent(m_registerAllocator);
+        generateWalkToParentElement(failureCases, parent);
+
+        failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parent, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
+
+        return;
+    }
+
+    Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister();
+    generateWalkToParentElement(failureCases, parentElement);
+
+    // Zero in isOnlyChildRegister is the success case. The register is set to non-zero if a sibling if found.
+    LocalRegister isOnlyChildRegister(m_registerAllocator);
+    m_assembler.move(Assembler::TrustedImm32(0), isOnlyChildRegister);
+
+    {
+        Assembler::JumpList localFailureCases;
+        {
+            Assembler::JumpList successCase = jumpIfNoPreviousAdjacentElement();
+            localFailureCases.append(m_assembler.jump());
+            successCase.link(&m_assembler);
+        }
+        localFailureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentElement, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
+        Assembler::JumpList successCase = jumpIfNoNextAdjacentElement();
+
+        localFailureCases.link(&m_assembler);
+        m_assembler.move(Assembler::TrustedImm32(1), isOnlyChildRegister);
+
+        successCase.link(&m_assembler);
+    }
+
+    LocalRegister checkingContext(m_registerAllocator);
+    Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext);
+
+    m_registerAllocator.deallocateRegister(parentElement);
+    FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls);
+    functionCall.setFunctionAddress(markElementWithSetChildrenAffectedByFirstChildAndLastChildRules);
+    functionCall.setOneArgument(parentElement);
+    functionCall.call();
+
+    // The parent marking is unconditional. If the matching is not a success, we can now fail.
+    // Otherwise we need to apply setLastChildState() on the RenderStyle.
+    failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isOnlyChildRegister));
+
+    if (fragment.relationToRightFragment == FragmentRelation::Rightmost) {
+        LocalRegister childStyle(m_registerAllocator);
+        m_assembler.loadPtr(Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, elementStyle)), childStyle);
+
+        // FIXME: We should look into doing something smart in MacroAssembler instead.
+        LocalRegister flags(m_registerAllocator);
+        Assembler::Address flagAddress(childStyle, RenderStyle::noninheritedFlagsMemoryOffset() + RenderStyle::NonInheritedFlags::flagsMemoryOffset());
+        m_assembler.load64(flagAddress, flags);
+        LocalRegister isLastChildStateFlagImmediate(m_registerAllocator);
+        m_assembler.move(Assembler::TrustedImm64(RenderStyle::NonInheritedFlags::setFirstChildStateFlags() | RenderStyle::NonInheritedFlags::setLastChildStateFlags()), isLastChildStateFlagImmediate);
+        m_assembler.or64(isLastChildStateFlagImmediate, flags);
+        m_assembler.store64(flags, flagAddress);
+    } else {
+        FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls);
+        functionCall.setFunctionAddress(setOnlyChildState);
+        Assembler::RegisterID elementAddress = elementAddressRegister;
+        functionCall.setOneArgument(elementAddress);
+        functionCall.call();
+    }
+
+    notResolvingStyle.link(&m_assembler);
+    failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isOnlyChildRegister));
+}
+
 inline void SelectorCodeGenerator::generateElementHasTagName(Assembler::JumpList& failureCases, const QualifiedName& nameToMatch)
 {
     if (nameToMatch == anyQName())
index 85ab487..8593e6a 100644 (file)
@@ -175,6 +175,7 @@ public:
     Node* previousSibling() const { return m_previous; }
     static ptrdiff_t previousSiblingMemoryOffset() { return OBJECT_OFFSETOF(Node, m_previous); }
     Node* nextSibling() const { return m_next; }
+    static ptrdiff_t nextSiblingMemoryOffset() { return OBJECT_OFFSETOF(Node, m_next); }
     PassRefPtr<NodeList> childNodes();
     Node* firstChild() const;
     Node* lastChild() const;
@@ -573,6 +574,7 @@ public:
     static int32_t flagIsElement() { return IsElementFlag; }
     static int32_t flagIsHTML() { return IsHTMLFlag; }
     static int32_t flagIsLink() { return IsLinkFlag; }
+    static int32_t flagIsParsingChildrenFinished() { return IsParsingChildrenFinishedFlag; }
 #endif // ENABLE(CSS_SELECTOR_JIT)
 
 protected:
index 84ea942..8b3bcae 100644 (file)
@@ -278,12 +278,8 @@ public:
         static ETableLayout initialTableLayout() { return TAUTO; }
 
         static ptrdiff_t flagsMemoryOffset() { return OBJECT_OFFSETOF(NonInheritedFlags, m_flags); }
-        static uint64_t flagEmptyState() { return oneBitMask << emptyStateOffset; }
         static uint64_t setFirstChildStateFlags() { return flagFirstChildState() | flagIsUnique(); }
-        static uint64_t flagLastChildState() { return oneBitMask << lastChildStateOffset; }
-        static uint64_t flagAffectedByHover() { return oneBitMask << affectedByHoverOffset; }
-        static uint64_t flagAffectedByActive() { return oneBitMask << affectedByActiveOffset; }
-        static uint64_t flagAffectedByDrag() { return oneBitMask << affectedByDragOffset; }
+        static uint64_t setLastChildStateFlags() { return flagLastChildState() | flagIsUnique(); }
     private:
         void updateBoolean(bool isSet, uint64_t offset)
         {
@@ -312,6 +308,7 @@ public:
 
         static uint64_t flagIsUnique() { return oneBitMask << isUniqueOffset; }
         static uint64_t flagFirstChildState() { return oneBitMask << firstChildStateOffset; }
+        static uint64_t flagLastChildState() { return oneBitMask << lastChildStateOffset; }
 
         // To type the bit mask properly on 64bits.
         static const uint64_t oneBitMask = 0x1;