/* * Copyright (C) 2013, 2014 Apple Inc. All rights reserved. * Copyright (C) 2014 Yusuke Suzuki * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "SelectorCompiler.h" #if ENABLE(CSS_SELECTOR_JIT) #include "CSSSelector.h" #include "CSSSelectorList.h" #include "Element.h" #include "ElementData.h" #include "ElementRareData.h" #include "FunctionCall.h" #include "HTMLDocument.h" #include "HTMLNames.h" #include "InspectorInstrumentation.h" #include "NodeRenderStyle.h" #include "QualifiedName.h" #include "RegisterAllocator.h" #include "RenderElement.h" #include "RenderStyle.h" #include "SVGElement.h" #include "SelectorCheckerTestFunctions.h" #include "StackAllocator.h" #include "StyledElement.h" #include #include #include #include #include #include #include #include #include namespace WebCore { namespace SelectorCompiler { #define CSS_SELECTOR_JIT_DEBUGGING 0 enum class BacktrackingAction { NoBacktracking, JumpToDescendantEntryPoint, JumpToIndirectAdjacentEntryPoint, JumpToDescendantTreeWalkerEntryPoint, JumpToIndirectAdjacentTreeWalkerEntryPoint, JumpToDescendantTail, JumpToDirectAdjacentTail }; struct BacktrackingFlag { enum { DescendantEntryPoint = 1, IndirectAdjacentEntryPoint = 1 << 1, SaveDescendantBacktrackingStart = 1 << 2, SaveAdjacentBacktrackingStart = 1 << 3, DirectAdjacentTail = 1 << 4, DescendantTail = 1 << 5, InChainWithDescendantTail = 1 << 6 }; }; enum class FragmentRelation { Rightmost, Descendant, Child, DirectAdjacent, IndirectAdjacent }; enum class FunctionType { SimpleSelectorChecker, SelectorCheckerWithCheckingContext, CannotMatchAnything, CannotCompile }; enum class FragmentPositionInRootFragments { Rightmost, NotRightmost }; class AttributeMatchingInfo { public: AttributeMatchingInfo(const CSSSelector* selector, bool canDefaultToCaseSensitiveValueMatch) : m_selector(selector) , m_canDefaultToCaseSensitiveValueMatch(canDefaultToCaseSensitiveValueMatch) { } bool canDefaultToCaseSensitiveValueMatch() const { return m_canDefaultToCaseSensitiveValueMatch; } const CSSSelector& selector() const { return *m_selector; } private: const CSSSelector* m_selector; bool m_canDefaultToCaseSensitiveValueMatch; }; static const unsigned invalidHeight = std::numeric_limits::max(); static const unsigned invalidWidth = std::numeric_limits::max(); struct SelectorFragment { SelectorFragment() : traversalBacktrackingAction(BacktrackingAction::NoBacktracking) , matchingTagNameBacktrackingAction(BacktrackingAction::NoBacktracking) , matchingPostTagNameBacktrackingAction(BacktrackingAction::NoBacktracking) , backtrackingFlags(0) , tagNameMatchedBacktrackingStartHeightFromDescendant(invalidHeight) , tagNameNotMatchedBacktrackingStartHeightFromDescendant(invalidHeight) , heightFromDescendant(0) , tagNameMatchedBacktrackingStartWidthFromIndirectAdjacent(invalidWidth) , tagNameNotMatchedBacktrackingStartWidthFromIndirectAdjacent(invalidWidth) , widthFromIndirectAdjacent(0) , tagName(nullptr) , id(nullptr) , langFilter(nullptr) , pseudoElementSelector(nullptr) , onlyMatchesLinksInQuirksMode(true) { } FragmentRelation relationToLeftFragment; FragmentRelation relationToRightFragment; FragmentPositionInRootFragments positionInRootFragments; BacktrackingAction traversalBacktrackingAction; BacktrackingAction matchingTagNameBacktrackingAction; BacktrackingAction matchingPostTagNameBacktrackingAction; unsigned char backtrackingFlags; unsigned tagNameMatchedBacktrackingStartHeightFromDescendant; unsigned tagNameNotMatchedBacktrackingStartHeightFromDescendant; unsigned heightFromDescendant; unsigned tagNameMatchedBacktrackingStartWidthFromIndirectAdjacent; unsigned tagNameNotMatchedBacktrackingStartWidthFromIndirectAdjacent; unsigned widthFromIndirectAdjacent; const QualifiedName* tagName; const AtomicString* id; const AtomicString* langFilter; Vector classNames; HashSet pseudoClasses; Vector unoptimizedPseudoClasses; Vector attributes; Vector, 32> nthChildFilters; Vector notFilters; Vector> anyFilters; const CSSSelector* pseudoElementSelector; // For quirks mode, follow this: http://quirks.spec.whatwg.org/#the-:active-and-:hover-quirk // In quirks mode, a compound selector 'selector' that matches the following conditions must not match elements that would not also match the ':any-link' selector. // // selector uses the ':active' or ':hover' pseudo-classes. // selector does not use a type selector. // selector does not use an attribute selector. // selector does not use an ID selector. // selector does not use a class selector. // selector does not use a pseudo-class selector other than ':active' and ':hover'. // selector does not use a pseudo-element selector. // selector is not part of an argument to a functional pseudo-class or pseudo-element. bool onlyMatchesLinksInQuirksMode; }; struct TagNamePattern { TagNamePattern() : tagName(nullptr) , inverted(false) { } const QualifiedName* tagName; bool inverted; }; typedef JSC::MacroAssembler Assembler; typedef Vector SelectorFragmentList; typedef Vector TagNameList; class SelectorCodeGenerator { public: SelectorCodeGenerator(const CSSSelector*, SelectorContext); SelectorCompilationStatus compile(JSC::VM*, JSC::MacroAssemblerCodeRef&); private: static const Assembler::RegisterID returnRegister; static const Assembler::RegisterID elementAddressRegister; static const Assembler::RegisterID checkingContextRegister; static const Assembler::RegisterID callFrameRegister; void generateSelectorChecker(); // Element relations tree walker. void generateWalkToParentNode(Assembler::RegisterID targetRegister); void generateWalkToParentElement(Assembler::JumpList& failureCases, Assembler::RegisterID targetRegister); 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&); void generateIndirectAdjacentTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment&); void markParentElementIfResolvingStyle(int32_t); void markParentElementIfResolvingStyle(JSC::FunctionPtr); void linkFailures(Assembler::JumpList& globalFailureCases, BacktrackingAction, Assembler::JumpList& localFailureCases); void generateAdjacentBacktrackingTail(); void generateDescendantBacktrackingTail(); void generateBacktrackingTailsIfNeeded(Assembler::JumpList& failureCases, const SelectorFragment&); // Element properties matchers. void generateElementMatching(Assembler::JumpList& matchingTagNameFailureCases, Assembler::JumpList& matchingPostTagNameFailureCases, const SelectorFragment&); void generateElementDataMatching(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementFunctionCallTest(Assembler::JumpList& failureCases, JSC::FunctionPtr); void generateElementIsActive(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementIsFirstChild(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementIsHovered(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementIsInLanguage(Assembler::JumpList& failureCases, const AtomicString&); 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&); void generateElementAttributeMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, Assembler::RegisterID decIndexRegister, const AttributeMatchingInfo& attributeInfo); void generateElementAttributeValueMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, const AttributeMatchingInfo& attributeInfo); void generateElementAttributeValueExactMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, const AtomicString& expectedValue, bool caseSensitive); void generateElementAttributeFunctionCallValueMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, const AtomicString& expectedValue, bool caseSensitive, JSC::FunctionPtr caseSensitiveTest, JSC::FunctionPtr caseInsensitiveTest); void generateElementHasTagName(Assembler::JumpList& failureCases, const QualifiedName& nameToMatch); void generateElementHasId(Assembler::JumpList& failureCases, const LocalRegister& elementDataAddress, const AtomicString& idToMatch); void generateElementHasClasses(Assembler::JumpList& failureCases, const LocalRegister& elementDataAddress, const Vector& classNames); void generateElementIsLink(Assembler::JumpList& failureCases); void generateElementIsNthChild(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementMatchesNotPseudoClass(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementMatchesAnyPseudoClass(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementHasPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment&); void generateElementIsRoot(Assembler::JumpList& failureCases); void generateElementIsTarget(Assembler::JumpList& failureCases); // Helpers. void addFlagsToElementStyleFromContext(Assembler::RegisterID checkingContext, int64_t); Assembler::Jump branchOnResolvingModeWithCheckingContext(Assembler::RelationalCondition, SelectorChecker::Mode, Assembler::RegisterID checkingContext); Assembler::Jump branchOnResolvingMode(Assembler::RelationalCondition, SelectorChecker::Mode, Assembler::RegisterID checkingContext); void generateMarkPseudoStyleForPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment&); void generateRequestedPseudoElementEqualsToSelectorPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment&, Assembler::RegisterID checkingContext); void generateSpecialFailureInQuirksModeForActiveAndHoverIfNeeded(Assembler::JumpList& failureCases, const SelectorFragment&); Assembler::JumpList jumpIfNoPreviousAdjacentElement(); Assembler::JumpList jumpIfNoNextAdjacentElement(); Assembler::Jump jumpIfNotResolvingStyle(Assembler::RegisterID checkingContextRegister); void loadCheckingContext(Assembler::RegisterID checkingContext); Assembler::Jump modulo(JSC::MacroAssembler::ResultCondition, Assembler::RegisterID inputDividend, int divisor); void moduloIsZero(Assembler::JumpList& failureCases, Assembler::RegisterID inputDividend, int divisor); bool generatePrologue(); void generateEpilogue(); StackAllocator::StackReferenceVector m_prologueStackReferences; Assembler m_assembler; RegisterAllocator m_registerAllocator; StackAllocator m_stackAllocator; Vector, 32> m_functionCalls; SelectorContext m_selectorContext; FunctionType m_functionType; SelectorFragmentList m_selectorFragments; bool m_needsAdjacentBacktrackingStart; StackAllocator::StackReference m_checkingContextStackReference; Assembler::Label m_descendantEntryPoint; Assembler::Label m_indirectAdjacentEntryPoint; Assembler::Label m_descendantTreeWalkerBacktrackingPoint; Assembler::Label m_indirectAdjacentTreeWalkerBacktrackingPoint; Assembler::RegisterID m_descendantBacktrackingStart; Assembler::JumpList m_descendantBacktrackingFailureCases; StackAllocator::StackReference m_adjacentBacktrackingStart; Assembler::JumpList m_adjacentBacktrackingFailureCases; #if CSS_SELECTOR_JIT_DEBUGGING const CSSSelector* m_originalSelector; #endif }; const Assembler::RegisterID SelectorCodeGenerator::returnRegister = JSC::GPRInfo::returnValueGPR; const Assembler::RegisterID SelectorCodeGenerator::elementAddressRegister = JSC::GPRInfo::argumentGPR0; const Assembler::RegisterID SelectorCodeGenerator::checkingContextRegister = JSC::GPRInfo::argumentGPR1; const Assembler::RegisterID SelectorCodeGenerator::callFrameRegister = JSC::GPRInfo::callFrameRegister; enum class FragmentsLevel { Root = 0, InFunctionalPseudoType = 1 }; static FunctionType constructFragments(const CSSSelector* rootSelector, SelectorContext, SelectorFragmentList& selectorFragments, FragmentsLevel, FragmentPositionInRootFragments); static void computeBacktrackingInformation(SelectorFragmentList& selectorFragments, bool& needsAdjacentBacktrackingStart); SelectorCompilationStatus compileSelector(const CSSSelector* lastSelector, JSC::VM* vm, SelectorContext selectorContext, JSC::MacroAssemblerCodeRef& codeRef) { if (!vm->canUseJIT()) return SelectorCompilationStatus::CannotCompile; SelectorCodeGenerator codeGenerator(lastSelector, selectorContext); return codeGenerator.compile(vm, codeRef); } static inline FragmentRelation fragmentRelationForSelectorRelation(CSSSelector::Relation relation) { switch (relation) { case CSSSelector::Descendant: return FragmentRelation::Descendant; case CSSSelector::Child: return FragmentRelation::Child; case CSSSelector::DirectAdjacent: return FragmentRelation::DirectAdjacent; case CSSSelector::IndirectAdjacent: return FragmentRelation::IndirectAdjacent; case CSSSelector::SubSelector: case CSSSelector::ShadowDescendant: ASSERT_NOT_REACHED(); } ASSERT_NOT_REACHED(); return FragmentRelation::Descendant; } static inline FunctionType mostRestrictiveFunctionType(FunctionType a, FunctionType b) { return std::max(a, b); } static inline bool shouldUseRenderStyleFromCheckingContext(const SelectorFragment& fragment) { // Return true if the position of this fragment is Rightmost in the root fragments. // In this case, we should use the RenderStyle stored in the CheckingContext. return fragment.relationToRightFragment == FragmentRelation::Rightmost && fragment.positionInRootFragments == FragmentPositionInRootFragments::Rightmost; } static inline FunctionType addPseudoClassType(const CSSSelector& selector, SelectorFragment& fragment, SelectorContext selectorContext, FragmentPositionInRootFragments positionInRootFragments) { CSSSelector::PseudoClassType type = selector.pseudoClassType(); switch (type) { // Unoptimized pseudo selector. They are just function call to a simple testing function. case CSSSelector::PseudoClassAutofill: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isAutofilled)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassChecked: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isChecked)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassDefault: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isDefaultButtonForForm)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassDisabled: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isDisabled)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassEnabled: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isEnabled)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassFocus: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(SelectorChecker::matchesFocusPseudoClass)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassInRange: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isInRange)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassIndeterminate: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(shouldAppearIndeterminate)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassInvalid: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isInvalid)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassOptional: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isOptionalFormControl)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassOutOfRange: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isOutOfRange)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassReadOnly: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesReadOnlyPseudoClass)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassReadWrite: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesReadWritePseudoClass)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassRequired: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isRequiredFormControl)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassValid: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isValid)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassWindowInactive: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isWindowInactive)); return FunctionType::SimpleSelectorChecker; #if ENABLE(FULLSCREEN_API) case CSSSelector::PseudoClassFullScreen: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesFullScreenPseudoClass)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassFullScreenDocument: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesFullScreenDocumentPseudoClass)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassFullScreenAncestor: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesFullScreenAncestorPseudoClass)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassAnimatingFullScreenTransition: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesFullScreenAnimatingFullScreenTransitionPseudoClass)); return FunctionType::SimpleSelectorChecker; #endif #if ENABLE(VIDEO_TRACK) case CSSSelector::PseudoClassFuture: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesFutureCuePseudoClass)); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassPast: fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(matchesPastCuePseudoClass)); return FunctionType::SimpleSelectorChecker; #endif // FIXME: Compile these pseudoclasses, too! case CSSSelector::PseudoClassEmpty: case CSSSelector::PseudoClassFirstOfType: case CSSSelector::PseudoClassLastOfType: case CSSSelector::PseudoClassOnlyOfType: case CSSSelector::PseudoClassNthOfType: case CSSSelector::PseudoClassNthLastChild: case CSSSelector::PseudoClassNthLastOfType: case CSSSelector::PseudoClassVisited: case CSSSelector::PseudoClassDrag: case CSSSelector::PseudoClassFullPageMedia: case CSSSelector::PseudoClassScope: case CSSSelector::PseudoClassCornerPresent: case CSSSelector::PseudoClassDecrement: case CSSSelector::PseudoClassIncrement: case CSSSelector::PseudoClassHorizontal: case CSSSelector::PseudoClassVertical: case CSSSelector::PseudoClassStart: case CSSSelector::PseudoClassEnd: case CSSSelector::PseudoClassDoubleButton: case CSSSelector::PseudoClassSingleButton: case CSSSelector::PseudoClassNoButton: return FunctionType::CannotCompile; // Optimized pseudo selectors. case CSSSelector::PseudoClassAnyLink: fragment.pseudoClasses.add(CSSSelector::PseudoClassLink); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassLink: case CSSSelector::PseudoClassRoot: case CSSSelector::PseudoClassTarget: fragment.pseudoClasses.add(type); return FunctionType::SimpleSelectorChecker; case CSSSelector::PseudoClassActive: case CSSSelector::PseudoClassFirstChild: case CSSSelector::PseudoClassHover: case CSSSelector::PseudoClassLastChild: case CSSSelector::PseudoClassOnlyChild: fragment.pseudoClasses.add(type); if (selectorContext == SelectorContext::QuerySelector) return FunctionType::SimpleSelectorChecker; return FunctionType::SelectorCheckerWithCheckingContext; case CSSSelector::PseudoClassNthChild: { if (!selector.parseNth()) return FunctionType::CannotMatchAnything; int a = selector.nthA(); int b = selector.nthB(); // The element count is always positive. if (a <= 0 && b < 1) return FunctionType::CannotMatchAnything; fragment.nthChildFilters.append(std::pair(a, b)); if (selectorContext == SelectorContext::QuerySelector) return FunctionType::SimpleSelectorChecker; return FunctionType::SelectorCheckerWithCheckingContext; } case CSSSelector::PseudoClassNot: { const CSSSelectorList* selectorList = selector.selectorList(); if (!selectorList) return FunctionType::CannotMatchAnything; SelectorFragmentList notFragments; FunctionType functionType = constructFragments(selectorList->first(), selectorContext, notFragments, FragmentsLevel::InFunctionalPseudoType, positionInRootFragments); // Since this is not pseudo class filter, CannotMatchAnything implies this filter always passes. if (functionType == FunctionType::CannotMatchAnything) return FunctionType::SimpleSelectorChecker; if (functionType == FunctionType::CannotCompile) return functionType; ASSERT(notFragments.size() == 1); if (notFragments.size() != 1) return FunctionType::CannotCompile; const SelectorFragment& subFragment = notFragments.first(); // FIXME: Currently we don't support visitedMatchType. if (subFragment.pseudoClasses.contains(CSSSelector::PseudoClassLink)) return FunctionType::CannotCompile; fragment.notFilters.append(subFragment); return functionType; } case CSSSelector::PseudoClassAny: { Vector anyFragments; FunctionType functionType = FunctionType::SimpleSelectorChecker; for (const CSSSelector* rootSelector = selector.selectorList()->first(); rootSelector; rootSelector = CSSSelectorList::next(rootSelector)) { SelectorFragmentList fragmentList; FunctionType subFunctionType = constructFragments(rootSelector, selectorContext, fragmentList, FragmentsLevel::InFunctionalPseudoType, positionInRootFragments); // Since this fragment always unmatch against the element, don't insert it to anyFragments. if (subFunctionType == FunctionType::CannotMatchAnything) continue; if (subFunctionType == FunctionType::CannotCompile) return FunctionType::CannotCompile; // :any() may not contain complex selectors which have combinators. ASSERT(fragmentList.size() == 1); if (fragmentList.size() != 1) return FunctionType::CannotCompile; const SelectorFragment& subFragment = fragmentList.first(); anyFragments.append(subFragment); functionType = mostRestrictiveFunctionType(functionType, subFunctionType); } // Since all fragments in :any() cannot match anything, this :any() filter cannot match anything. if (anyFragments.isEmpty()) return FunctionType::CannotMatchAnything; ASSERT(!anyFragments.isEmpty()); fragment.anyFilters.append(anyFragments); return functionType; } case CSSSelector::PseudoClassLang: { const AtomicString& argument = selector.argument(); if (argument.isEmpty()) return FunctionType::CannotMatchAnything; if (!fragment.langFilter) fragment.langFilter = &argument; else if (*fragment.langFilter != argument) { // If there are multiple definition, we only care about the most restrictive one. if (argument.startsWith(*fragment.langFilter, false)) fragment.langFilter = &argument; else if (fragment.langFilter->startsWith(argument, false)) { } // The existing filter is more restrictive. else return FunctionType::CannotMatchAnything; } return FunctionType::SimpleSelectorChecker; } case CSSSelector::PseudoClassUnknown: ASSERT_NOT_REACHED(); return FunctionType::CannotMatchAnything; } ASSERT_NOT_REACHED(); return FunctionType::CannotCompile; } inline SelectorCodeGenerator::SelectorCodeGenerator(const CSSSelector* rootSelector, SelectorContext selectorContext) : m_stackAllocator(m_assembler) , m_selectorContext(selectorContext) , m_functionType(FunctionType::SimpleSelectorChecker) , m_needsAdjacentBacktrackingStart(false) #if CSS_SELECTOR_JIT_DEBUGGING , m_originalSelector(rootSelector) #endif { #if CSS_SELECTOR_JIT_DEBUGGING dataLogF("Compiling \"%s\"\n", m_originalSelector->selectorText().utf8().data()); #endif m_functionType = constructFragments(rootSelector, m_selectorContext, m_selectorFragments, FragmentsLevel::Root, FragmentPositionInRootFragments::Rightmost); if (m_functionType != FunctionType::CannotCompile && m_functionType != FunctionType::CannotMatchAnything) computeBacktrackingInformation(m_selectorFragments, m_needsAdjacentBacktrackingStart); } static bool pseudoClassOnlyMatchesLinksInQuirksMode(const CSSSelector& selector) { CSSSelector::PseudoClassType pseudoClassType = selector.pseudoClassType(); return pseudoClassType == CSSSelector::PseudoClassHover || pseudoClassType == CSSSelector::PseudoClassActive; } static FunctionType constructFragments(const CSSSelector* rootSelector, SelectorContext selectorContext, SelectorFragmentList& selectorFragments, FragmentsLevel fragmentLevel, FragmentPositionInRootFragments positionInRootFragments) { SelectorFragment fragment; FragmentRelation relationToPreviousFragment = FragmentRelation::Rightmost; FunctionType functionType = FunctionType::SimpleSelectorChecker; for (const CSSSelector* selector = rootSelector; selector; selector = selector->tagHistory()) { CSSSelector::Relation relation = selector->relation(); // A selector is invalid if something follows a pseudo-element. // We make an exception for scrollbar pseudo elements and allow a set of pseudo classes (but nothing else) // to follow the pseudo elements. // FIXME: Currently, CSS JIT doesn't support scrollbar and selection's exceptional cases. // So all selectors following a pseudo-element is treated as invalid. if (relation == CSSSelector::SubSelector && fragment.pseudoElementSelector) return FunctionType::CannotCompile; switch (selector->m_match) { case CSSSelector::Tag: ASSERT(!fragment.tagName); fragment.tagName = &(selector->tagQName()); if (*fragment.tagName != anyQName()) fragment.onlyMatchesLinksInQuirksMode = false; break; case CSSSelector::Id: { const AtomicString& id = selector->value(); if (fragment.id) { if (id != *fragment.id) return FunctionType::CannotMatchAnything; } else fragment.id = &(selector->value()); fragment.onlyMatchesLinksInQuirksMode = false; break; } case CSSSelector::Class: fragment.classNames.append(selector->value().impl()); fragment.onlyMatchesLinksInQuirksMode = false; break; case CSSSelector::PseudoClass: { FragmentPositionInRootFragments subPosition = positionInRootFragments; if (relationToPreviousFragment != FragmentRelation::Rightmost) subPosition = FragmentPositionInRootFragments::NotRightmost; functionType = mostRestrictiveFunctionType(functionType, addPseudoClassType(*selector, fragment, selectorContext, subPosition)); if (!pseudoClassOnlyMatchesLinksInQuirksMode(*selector)) fragment.onlyMatchesLinksInQuirksMode = false; if (functionType == FunctionType::CannotCompile || functionType == FunctionType::CannotMatchAnything) return functionType; break; } case CSSSelector::PseudoElement: { fragment.onlyMatchesLinksInQuirksMode = false; // In the QuerySelector context, PseudoElement selectors always fail. if (selectorContext == SelectorContext::QuerySelector) return FunctionType::CannotMatchAnything; // In Selectors Level 4, a pseudo element inside a functional pseudo class is undefined (issue 7). // Make it as matching failure until the spec clarifies this case. if (fragmentLevel == FragmentsLevel::InFunctionalPseudoType) return FunctionType::CannotMatchAnything; switch (selector->pseudoElementType()) { case CSSSelector::PseudoElementAfter: case CSSSelector::PseudoElementBefore: case CSSSelector::PseudoElementFirstLetter: case CSSSelector::PseudoElementFirstLine: fragment.pseudoElementSelector = selector; break; // FIXME: Support SCROLLBAR, RESIZER, SELECTION etc. default: return FunctionType::CannotCompile; } functionType = FunctionType::SelectorCheckerWithCheckingContext; break; } case CSSSelector::List: if (selector->value().contains(' ')) return FunctionType::CannotMatchAnything; FALLTHROUGH; case CSSSelector::Begin: case CSSSelector::End: case CSSSelector::Contain: if (selector->value().isEmpty()) return FunctionType::CannotMatchAnything; FALLTHROUGH; case CSSSelector::Exact: case CSSSelector::Hyphen: fragment.onlyMatchesLinksInQuirksMode = false; fragment.attributes.append(AttributeMatchingInfo(selector, HTMLDocument::isCaseSensitiveAttribute(selector->attribute()))); break; case CSSSelector::Set: fragment.onlyMatchesLinksInQuirksMode = false; fragment.attributes.append(AttributeMatchingInfo(selector, true)); break; case CSSSelector::PagePseudoClass: fragment.onlyMatchesLinksInQuirksMode = false; // Pseudo page class are only relevant for style resolution, they are ignored for matching. break; case CSSSelector::Unknown: ASSERT_NOT_REACHED(); return FunctionType::CannotMatchAnything; } if (relation == CSSSelector::SubSelector) continue; if (relation == CSSSelector::ShadowDescendant && !selector->isLastInTagHistory()) return FunctionType::CannotCompile; if (relation == CSSSelector::DirectAdjacent || relation == CSSSelector::IndirectAdjacent) { FunctionType relationFunctionType = FunctionType::SelectorCheckerWithCheckingContext; if (selectorContext == SelectorContext::QuerySelector) relationFunctionType = FunctionType::SimpleSelectorChecker; functionType = mostRestrictiveFunctionType(functionType, relationFunctionType); } fragment.relationToLeftFragment = fragmentRelationForSelectorRelation(relation); fragment.relationToRightFragment = relationToPreviousFragment; fragment.positionInRootFragments = positionInRootFragments; relationToPreviousFragment = fragment.relationToLeftFragment; if (fragmentLevel == FragmentsLevel::InFunctionalPseudoType) fragment.onlyMatchesLinksInQuirksMode = false; selectorFragments.append(fragment); fragment = SelectorFragment(); } return functionType; } static inline bool attributeNameTestingRequiresNamespaceRegister(const CSSSelector& attributeSelector) { return attributeSelector.attribute().prefix() != starAtom && !attributeSelector.attribute().namespaceURI().isNull(); } static inline bool attributeValueTestingRequiresCaseFoldingRegister(const AttributeMatchingInfo& attributeInfo) { return !attributeInfo.canDefaultToCaseSensitiveValueMatch(); } // Strict minimum to match anything interesting: // Element + BacktrackingRegister + ElementData + a pointer to values + an index on that pointer + the value we expect; static const unsigned minimumRequiredRegisterCount = 6; // Element + ElementData + scratchRegister + attributeArrayPointer + expectedLocalName + (qualifiedNameImpl && expectedValue). static const unsigned minimumRequiredRegisterCountForAttributeFilter = 6; static inline unsigned minimumRegisterRequirements(const SelectorFragment& selectorFragment) { unsigned minimum = minimumRequiredRegisterCount; const Vector& attributes = selectorFragment.attributes; unsigned backtrackingRegisterRequirements = 0; if (selectorFragment.backtrackingFlags & BacktrackingFlag::InChainWithDescendantTail) backtrackingRegisterRequirements = 1; // If there is a DescendantTail, there is a backtracking register. // Attributes cause some register pressure. unsigned attributeCount = attributes.size(); for (unsigned attributeIndex = 0; attributeIndex < attributeCount; ++attributeIndex) { unsigned attributeMinimum = minimumRequiredRegisterCountForAttributeFilter + backtrackingRegisterRequirements; if (attributeIndex + 1 < attributeCount) attributeMinimum += 2; // For the local copy of the counter and attributeArrayPointer. const AttributeMatchingInfo& attributeInfo = attributes[attributeIndex]; const CSSSelector& attributeSelector = attributeInfo.selector(); if (attributeNameTestingRequiresNamespaceRegister(attributeSelector) || attributeValueTestingRequiresCaseFoldingRegister(attributeInfo)) attributeMinimum += 1; minimum = std::max(minimum, attributeMinimum); } // :not pseudo class filters cause some register pressure. for (const SelectorFragment& subFragment : selectorFragment.notFilters) { unsigned notFilterMinimum = minimumRegisterRequirements(subFragment) + backtrackingRegisterRequirements; minimum = std::max(minimum, notFilterMinimum); } // :any pseudo class filters cause some register pressure. for (const auto& subFragments : selectorFragment.anyFilters) { for (const SelectorFragment& subFragment : subFragments) { unsigned anyFilterMinimum = minimumRegisterRequirements(subFragment) + backtrackingRegisterRequirements; minimum = std::max(minimum, anyFilterMinimum); } } return minimum; } static inline unsigned minimumRegisterRequirements(const SelectorFragmentList& selectorFragments) { unsigned minimum = minimumRequiredRegisterCount; for (unsigned selectorFragmentIndex = 0; selectorFragmentIndex < selectorFragments.size(); ++selectorFragmentIndex) { const SelectorFragment& selectorFragment = selectorFragments[selectorFragmentIndex]; minimum = std::max(minimum, minimumRegisterRequirements(selectorFragment)); } return minimum; } inline SelectorCompilationStatus SelectorCodeGenerator::compile(JSC::VM* vm, JSC::MacroAssemblerCodeRef& codeRef) { switch (m_functionType) { case FunctionType::SimpleSelectorChecker: case FunctionType::SelectorCheckerWithCheckingContext: generateSelectorChecker(); break; case FunctionType::CannotMatchAnything: m_assembler.move(Assembler::TrustedImm32(0), returnRegister); m_assembler.ret(); break; case FunctionType::CannotCompile: return SelectorCompilationStatus::CannotCompile; } JSC::LinkBuffer linkBuffer(*vm, m_assembler, CSS_CODE_ID); for (unsigned i = 0; i < m_functionCalls.size(); i++) linkBuffer.link(m_functionCalls[i].first, m_functionCalls[i].second); #if CSS_SELECTOR_JIT_DEBUGGING codeRef = linkBuffer.finalizeCodeWithDisassembly("CSS Selector JIT for \"%s\"", m_originalSelector->selectorText().utf8().data()); #else codeRef = FINALIZE_CODE(linkBuffer, ("CSS Selector JIT")); #endif if (m_functionType == FunctionType::SimpleSelectorChecker || m_functionType == FunctionType::CannotMatchAnything) return SelectorCompilationStatus::SimpleSelectorChecker; return SelectorCompilationStatus::SelectorCheckerWithCheckingContext; } static inline void updateChainStates(const SelectorFragment& fragment, bool& hasDescendantRelationOnTheRight, unsigned& ancestorPositionSinceDescendantRelation, bool& hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain, unsigned& adjacentPositionSinceIndirectAdjacentTreeWalk) { switch (fragment.relationToRightFragment) { case FragmentRelation::Rightmost: break; case FragmentRelation::Descendant: hasDescendantRelationOnTheRight = true; ancestorPositionSinceDescendantRelation = 0; hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain = false; break; case FragmentRelation::Child: if (hasDescendantRelationOnTheRight) ++ancestorPositionSinceDescendantRelation; hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain = false; break; case FragmentRelation::DirectAdjacent: if (hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain) ++adjacentPositionSinceIndirectAdjacentTreeWalk; break; case FragmentRelation::IndirectAdjacent: hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain = true; adjacentPositionSinceIndirectAdjacentTreeWalk = 0; break; } } static inline bool isFirstAncestor(unsigned ancestorPositionSinceDescendantRelation) { return ancestorPositionSinceDescendantRelation == 1; } static inline bool isFirstAdjacent(unsigned adjacentPositionSinceIndirectAdjacentTreeWalk) { return adjacentPositionSinceIndirectAdjacentTreeWalk == 1; } static inline BacktrackingAction solveDescendantBacktrackingActionForChild(const SelectorFragment& fragment, unsigned backtrackingStartHeightFromDescendant) { // If height is invalid (e.g. There's no tag name). if (backtrackingStartHeightFromDescendant == invalidHeight) return BacktrackingAction::NoBacktracking; // Start backtracking from the current element. if (backtrackingStartHeightFromDescendant == fragment.heightFromDescendant) return BacktrackingAction::JumpToDescendantEntryPoint; // Start backtracking from the parent of current element. if (backtrackingStartHeightFromDescendant == (fragment.heightFromDescendant + 1)) return BacktrackingAction::JumpToDescendantTreeWalkerEntryPoint; return BacktrackingAction::JumpToDescendantTail; } static inline BacktrackingAction solveAdjacentBacktrackingActionForDirectAdjacent(const SelectorFragment& fragment, unsigned backtrackingStartWidthFromIndirectAdjacent) { // If width is invalid (e.g. There's no tag name). if (backtrackingStartWidthFromIndirectAdjacent == invalidWidth) return BacktrackingAction::NoBacktracking; // Start backtracking from the current element. if (backtrackingStartWidthFromIndirectAdjacent == fragment.widthFromIndirectAdjacent) return BacktrackingAction::JumpToIndirectAdjacentEntryPoint; // Start backtracking from the previous adjacent of current element. if (backtrackingStartWidthFromIndirectAdjacent == (fragment.widthFromIndirectAdjacent + 1)) return BacktrackingAction::JumpToIndirectAdjacentTreeWalkerEntryPoint; return BacktrackingAction::JumpToDirectAdjacentTail; } static inline BacktrackingAction solveAdjacentTraversalBacktrackingAction(const SelectorFragment& fragment, bool hasDescendantRelationOnTheRight) { if (!hasDescendantRelationOnTheRight) return BacktrackingAction::NoBacktracking; if (fragment.tagNameMatchedBacktrackingStartHeightFromDescendant == (fragment.heightFromDescendant + 1)) return BacktrackingAction::JumpToDescendantTreeWalkerEntryPoint; return BacktrackingAction::JumpToDescendantTail; } static inline void solveBacktrackingAction(SelectorFragment& fragment, bool hasDescendantRelationOnTheRight, bool hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain) { switch (fragment.relationToRightFragment) { case FragmentRelation::Rightmost: case FragmentRelation::Descendant: break; case FragmentRelation::Child: // Failure to match the element should resume matching at the nearest ancestor/descendant entry point. if (hasDescendantRelationOnTheRight) { fragment.matchingTagNameBacktrackingAction = solveDescendantBacktrackingActionForChild(fragment, fragment.tagNameNotMatchedBacktrackingStartHeightFromDescendant); fragment.matchingPostTagNameBacktrackingAction = solveDescendantBacktrackingActionForChild(fragment, fragment.tagNameMatchedBacktrackingStartHeightFromDescendant); } break; case FragmentRelation::DirectAdjacent: // Failure on traversal implies no other sibling traversal can match. Matching should resume at the // nearest ancestor/descendant traversal. fragment.traversalBacktrackingAction = solveAdjacentTraversalBacktrackingAction(fragment, hasDescendantRelationOnTheRight); // If the rightmost relation is a indirect adjacent, matching sould resume from there. // Otherwise, we resume from the latest ancestor/descendant if any. if (hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain) { fragment.matchingTagNameBacktrackingAction = solveAdjacentBacktrackingActionForDirectAdjacent(fragment, fragment.tagNameNotMatchedBacktrackingStartWidthFromIndirectAdjacent); fragment.matchingPostTagNameBacktrackingAction = solveAdjacentBacktrackingActionForDirectAdjacent(fragment, fragment.tagNameMatchedBacktrackingStartWidthFromIndirectAdjacent); } else if (hasDescendantRelationOnTheRight) { // Since we resume from the latest ancestor/descendant, the action is the same as the traversal action. fragment.matchingTagNameBacktrackingAction = fragment.traversalBacktrackingAction; fragment.matchingPostTagNameBacktrackingAction = fragment.traversalBacktrackingAction; } break; case FragmentRelation::IndirectAdjacent: // Failure on traversal implies no other sibling matching will succeed. Matching can resume // from the latest ancestor/descendant. fragment.traversalBacktrackingAction = solveAdjacentTraversalBacktrackingAction(fragment, hasDescendantRelationOnTheRight); break; } } enum class TagNameEquality { StrictlyNotEqual, MaybeEqual, StrictlyEqual }; static inline TagNameEquality equalTagNames(const QualifiedName* lhs, const QualifiedName* rhs) { if (!lhs || *lhs == anyQName()) return TagNameEquality::MaybeEqual; if (!rhs || *rhs == anyQName()) return TagNameEquality::MaybeEqual; ASSERT(lhs && rhs); const AtomicString& lhsLocalName = lhs->localName(); const AtomicString& rhsLocalName = rhs->localName(); if (lhsLocalName != starAtom && rhsLocalName != starAtom) { if (lhsLocalName != rhsLocalName) return TagNameEquality::StrictlyNotEqual; return TagNameEquality::StrictlyEqual; } const AtomicString& lhsNamespaceURI = lhs->namespaceURI(); const AtomicString& rhsNamespaceURI = rhs->namespaceURI(); if (lhsNamespaceURI != starAtom && rhsNamespaceURI != starAtom) { if (lhsNamespaceURI != rhsNamespaceURI) return TagNameEquality::StrictlyNotEqual; return TagNameEquality::StrictlyEqual; } return TagNameEquality::MaybeEqual; } static inline bool equalTagNamePatterns(const TagNamePattern& lhs, const QualifiedName* rhs) { TagNameEquality result = equalTagNames(lhs.tagName, rhs); if (result == TagNameEquality::MaybeEqual) return true; // If both rhs & lhs have actual localName (or NamespaceURI), // TagNameEquality result becomes StrictlyEqual or StrictlyNotEqual Since inverted lhs never matches on rhs. bool equal = result == TagNameEquality::StrictlyEqual; if (lhs.inverted) return !equal; return equal; } // Find the largest matching prefix from already known tagNames. // And by using this, compute an appropriate height of backtracking start element from the closest base element in the chain. static inline unsigned computeBacktrackingStartOffsetInChain(const TagNameList& tagNames, unsigned maxPrefixSize) { RELEASE_ASSERT(!tagNames.isEmpty()); RELEASE_ASSERT(maxPrefixSize < tagNames.size()); for (unsigned largestPrefixSize = maxPrefixSize; largestPrefixSize > 0; --largestPrefixSize) { unsigned offsetToLargestPrefix = tagNames.size() - largestPrefixSize; bool matched = true; // Since TagNamePatterns are pushed to a tagNames, check tagNames with reverse order. for (unsigned i = 0; i < largestPrefixSize; ++i) { unsigned lastIndex = tagNames.size() - 1; unsigned currentIndex = lastIndex - i; if (!equalTagNamePatterns(tagNames[currentIndex], tagNames[currentIndex - offsetToLargestPrefix].tagName)) { matched = false; break; } } if (matched) return offsetToLargestPrefix; } return tagNames.size(); } static inline void computeBacktrackingHeightFromDescendant(SelectorFragment& fragment, TagNameList& tagNamesForChildChain, bool hasDescendantRelationOnTheRight, const SelectorFragment*& previousChildFragmentInChildChain) { if (!hasDescendantRelationOnTheRight) return; if (fragment.relationToRightFragment == FragmentRelation::Descendant) { tagNamesForChildChain.clear(); TagNamePattern pattern; pattern.tagName = fragment.tagName; tagNamesForChildChain.append(pattern); fragment.heightFromDescendant = 0; previousChildFragmentInChildChain = nullptr; } else if (fragment.relationToRightFragment == FragmentRelation::Child) { TagNamePattern pattern; pattern.tagName = fragment.tagName; tagNamesForChildChain.append(pattern); unsigned maxPrefixSize = tagNamesForChildChain.size() - 1; if (previousChildFragmentInChildChain) { RELEASE_ASSERT(tagNamesForChildChain.size() >= previousChildFragmentInChildChain->tagNameMatchedBacktrackingStartHeightFromDescendant); maxPrefixSize = tagNamesForChildChain.size() - previousChildFragmentInChildChain->tagNameMatchedBacktrackingStartHeightFromDescendant; } if (pattern.tagName) { // Compute height from descendant in the case that tagName is not matched. tagNamesForChildChain.last().inverted = true; fragment.tagNameNotMatchedBacktrackingStartHeightFromDescendant = computeBacktrackingStartOffsetInChain(tagNamesForChildChain, maxPrefixSize); } // Compute height from descendant in the case that tagName is matched. tagNamesForChildChain.last().inverted = false; fragment.tagNameMatchedBacktrackingStartHeightFromDescendant = computeBacktrackingStartOffsetInChain(tagNamesForChildChain, maxPrefixSize); fragment.heightFromDescendant = tagNamesForChildChain.size() - 1; previousChildFragmentInChildChain = &fragment; } else { if (previousChildFragmentInChildChain) { fragment.tagNameNotMatchedBacktrackingStartHeightFromDescendant = previousChildFragmentInChildChain->tagNameNotMatchedBacktrackingStartHeightFromDescendant; fragment.tagNameMatchedBacktrackingStartHeightFromDescendant = previousChildFragmentInChildChain->tagNameMatchedBacktrackingStartHeightFromDescendant; fragment.heightFromDescendant = previousChildFragmentInChildChain->heightFromDescendant; } else { fragment.tagNameNotMatchedBacktrackingStartHeightFromDescendant = tagNamesForChildChain.size(); fragment.tagNameMatchedBacktrackingStartHeightFromDescendant = tagNamesForChildChain.size(); fragment.heightFromDescendant = 0; } } } static inline void computeBacktrackingWidthFromIndirectAdjacent(SelectorFragment& fragment, TagNameList& tagNamesForDirectAdjacentChain, bool hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain, const SelectorFragment*& previousDirectAdjacentFragmentInDirectAdjacentChain) { if (!hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain) return; if (fragment.relationToRightFragment == FragmentRelation::IndirectAdjacent) { tagNamesForDirectAdjacentChain.clear(); TagNamePattern pattern; pattern.tagName = fragment.tagName; tagNamesForDirectAdjacentChain.append(pattern); fragment.widthFromIndirectAdjacent = 0; previousDirectAdjacentFragmentInDirectAdjacentChain = nullptr; } else if (fragment.relationToRightFragment == FragmentRelation::DirectAdjacent) { TagNamePattern pattern; pattern.tagName = fragment.tagName; tagNamesForDirectAdjacentChain.append(pattern); unsigned maxPrefixSize = tagNamesForDirectAdjacentChain.size() - 1; if (previousDirectAdjacentFragmentInDirectAdjacentChain) { RELEASE_ASSERT(tagNamesForDirectAdjacentChain.size() >= previousDirectAdjacentFragmentInDirectAdjacentChain->tagNameMatchedBacktrackingStartWidthFromIndirectAdjacent); maxPrefixSize = tagNamesForDirectAdjacentChain.size() - previousDirectAdjacentFragmentInDirectAdjacentChain->tagNameMatchedBacktrackingStartWidthFromIndirectAdjacent; } if (pattern.tagName) { // Compute height from descendant in the case that tagName is not matched. tagNamesForDirectAdjacentChain.last().inverted = true; fragment.tagNameNotMatchedBacktrackingStartWidthFromIndirectAdjacent = computeBacktrackingStartOffsetInChain(tagNamesForDirectAdjacentChain, maxPrefixSize); } // Compute height from descendant in the case that tagName is matched. tagNamesForDirectAdjacentChain.last().inverted = false; fragment.tagNameMatchedBacktrackingStartWidthFromIndirectAdjacent = computeBacktrackingStartOffsetInChain(tagNamesForDirectAdjacentChain, maxPrefixSize); fragment.widthFromIndirectAdjacent = tagNamesForDirectAdjacentChain.size() - 1; previousDirectAdjacentFragmentInDirectAdjacentChain = &fragment; } } static bool requiresAdjacentTail(const SelectorFragment& fragment) { ASSERT(fragment.traversalBacktrackingAction != BacktrackingAction::JumpToDirectAdjacentTail); return fragment.matchingTagNameBacktrackingAction == BacktrackingAction::JumpToDirectAdjacentTail || fragment.matchingPostTagNameBacktrackingAction == BacktrackingAction::JumpToDirectAdjacentTail; } static bool requiresDescendantTail(const SelectorFragment& fragment) { return fragment.matchingTagNameBacktrackingAction == BacktrackingAction::JumpToDescendantTail || fragment.matchingPostTagNameBacktrackingAction == BacktrackingAction::JumpToDescendantTail || fragment.traversalBacktrackingAction == BacktrackingAction::JumpToDescendantTail; } void computeBacktrackingInformation(SelectorFragmentList& selectorFragments, bool& needsAdjacentBacktrackingStart) { bool hasDescendantRelationOnTheRight = false; unsigned ancestorPositionSinceDescendantRelation = 0; bool hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain = false; unsigned adjacentPositionSinceIndirectAdjacentTreeWalk = 0; bool needsAdjacentTail = false; bool needsDescendantTail = false; unsigned saveDescendantBacktrackingStartFragmentIndex = std::numeric_limits::max(); unsigned saveIndirectAdjacentBacktrackingStartFragmentIndex = std::numeric_limits::max(); TagNameList tagNamesForChildChain; TagNameList tagNamesForDirectAdjacentChain; const SelectorFragment* previousChildFragmentInChildChain = nullptr; const SelectorFragment* previousDirectAdjacentFragmentInDirectAdjacentChain = nullptr; for (unsigned i = 0; i < selectorFragments.size(); ++i) { SelectorFragment& fragment = selectorFragments[i]; updateChainStates(fragment, hasDescendantRelationOnTheRight, ancestorPositionSinceDescendantRelation, hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain, adjacentPositionSinceIndirectAdjacentTreeWalk); computeBacktrackingHeightFromDescendant(fragment, tagNamesForChildChain, hasDescendantRelationOnTheRight, previousChildFragmentInChildChain); computeBacktrackingWidthFromIndirectAdjacent(fragment, tagNamesForDirectAdjacentChain, hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain, previousDirectAdjacentFragmentInDirectAdjacentChain); #if CSS_SELECTOR_JIT_DEBUGGING dataLogF("Computing fragment[%d] backtracking height %u. NotMatched %u / Matched %u | width %u. NotMatched %u / Matched %u\n", i, fragment.heightFromDescendant, fragment.tagNameNotMatchedBacktrackingStartHeightFromDescendant, fragment.tagNameMatchedBacktrackingStartHeightFromDescendant, fragment.widthFromIndirectAdjacent, fragment.tagNameNotMatchedBacktrackingStartWidthFromIndirectAdjacent, fragment.tagNameMatchedBacktrackingStartWidthFromIndirectAdjacent); #endif solveBacktrackingAction(fragment, hasDescendantRelationOnTheRight, hasIndirectAdjacentRelationOnTheRightOfDirectAdjacentChain); needsAdjacentTail |= requiresAdjacentTail(fragment); needsDescendantTail |= requiresDescendantTail(fragment); // Add code generation flags. if (fragment.relationToLeftFragment != FragmentRelation::Descendant && fragment.relationToRightFragment == FragmentRelation::Descendant) fragment.backtrackingFlags |= BacktrackingFlag::DescendantEntryPoint; if (fragment.relationToLeftFragment == FragmentRelation::DirectAdjacent && fragment.relationToRightFragment == FragmentRelation::IndirectAdjacent) fragment.backtrackingFlags |= BacktrackingFlag::IndirectAdjacentEntryPoint; if (fragment.relationToLeftFragment != FragmentRelation::Descendant && fragment.relationToRightFragment == FragmentRelation::Child && isFirstAncestor(ancestorPositionSinceDescendantRelation)) { ASSERT(saveDescendantBacktrackingStartFragmentIndex == std::numeric_limits::max()); saveDescendantBacktrackingStartFragmentIndex = i; } if (fragment.relationToLeftFragment == FragmentRelation::DirectAdjacent && fragment.relationToRightFragment == FragmentRelation::DirectAdjacent && isFirstAdjacent(adjacentPositionSinceIndirectAdjacentTreeWalk)) { ASSERT(saveIndirectAdjacentBacktrackingStartFragmentIndex == std::numeric_limits::max()); saveIndirectAdjacentBacktrackingStartFragmentIndex = i; } if (fragment.relationToLeftFragment != FragmentRelation::DirectAdjacent) { if (needsAdjacentTail) { ASSERT(fragment.relationToRightFragment == FragmentRelation::DirectAdjacent); ASSERT(saveIndirectAdjacentBacktrackingStartFragmentIndex != std::numeric_limits::max()); fragment.backtrackingFlags |= BacktrackingFlag::DirectAdjacentTail; selectorFragments[saveIndirectAdjacentBacktrackingStartFragmentIndex].backtrackingFlags |= BacktrackingFlag::SaveAdjacentBacktrackingStart; needsAdjacentBacktrackingStart = true; needsAdjacentTail = false; } saveIndirectAdjacentBacktrackingStartFragmentIndex = std::numeric_limits::max(); } if (fragment.relationToLeftFragment == FragmentRelation::Descendant) { if (needsDescendantTail) { ASSERT(saveDescendantBacktrackingStartFragmentIndex != std::numeric_limits::max()); fragment.backtrackingFlags |= BacktrackingFlag::DescendantTail; selectorFragments[saveDescendantBacktrackingStartFragmentIndex].backtrackingFlags |= BacktrackingFlag::SaveDescendantBacktrackingStart; needsDescendantTail = false; for (unsigned j = saveDescendantBacktrackingStartFragmentIndex; j <= i; ++j) selectorFragments[j].backtrackingFlags |= BacktrackingFlag::InChainWithDescendantTail; } saveDescendantBacktrackingStartFragmentIndex = std::numeric_limits::max(); } } } inline bool SelectorCodeGenerator::generatePrologue() { #if CPU(ARM64) Vector prologueRegisters; prologueRegisters.append(JSC::ARM64Registers::lr); prologueRegisters.append(JSC::ARM64Registers::fp); m_prologueStackReferences = m_stackAllocator.push(prologueRegisters); return true; #elif CPU(ARM_THUMB2) Vector prologueRegisters; prologueRegisters.append(JSC::ARMRegisters::lr); // r6 is tempRegister in RegisterAllocator.h and addressTempRegister in MacroAssemblerARMv7.h and must be preserved by the callee. prologueRegisters.append(JSC::ARMRegisters::r6); m_prologueStackReferences = m_stackAllocator.push(prologueRegisters); return true; #elif CPU(X86_64) && CSS_SELECTOR_JIT_DEBUGGING Vector prologueRegister; prologueRegister.append(callFrameRegister); m_prologueStackReferences = m_stackAllocator.push(prologueRegister); return true; #endif return false; } inline void SelectorCodeGenerator::generateEpilogue() { #if CPU(ARM64) Vector prologueRegisters; prologueRegisters.append(JSC::ARM64Registers::lr); prologueRegisters.append(JSC::ARM64Registers::fp); m_stackAllocator.pop(m_prologueStackReferences, prologueRegisters); #elif CPU(ARM_THUMB2) Vector prologueRegisters; prologueRegisters.append(JSC::ARMRegisters::lr); prologueRegisters.append(JSC::ARMRegisters::r6); m_stackAllocator.pop(m_prologueStackReferences, prologueRegisters); #elif CPU(X86_64) && CSS_SELECTOR_JIT_DEBUGGING Vector prologueRegister; prologueRegister.append(callFrameRegister); m_stackAllocator.pop(m_prologueStackReferences, prologueRegister); #endif } void SelectorCodeGenerator::generateSelectorChecker() { StackAllocator::StackReferenceVector calleeSavedRegisterStackReferences; bool reservedCalleeSavedRegisters = false; unsigned availableRegisterCount = m_registerAllocator.availableRegisterCount(); unsigned minimumRegisterCountForAttributes = minimumRegisterRequirements(m_selectorFragments); #if CSS_SELECTOR_JIT_DEBUGGING dataLogF("Compiling with minimum required register count %u\n", minimumRegisterCountForAttributes); #endif Assembler::JumpList failureOnFunctionEntry; // Test selector's pseudo element equals to requested PseudoId. if (m_functionType == FunctionType::SelectorCheckerWithCheckingContext) { ASSERT_WITH_MESSAGE(shouldUseRenderStyleFromCheckingContext(m_selectorFragments.first()), "Matching pseudo elements only make sense for the rightmost fragment."); generateRequestedPseudoElementEqualsToSelectorPseudoElement(failureOnFunctionEntry, m_selectorFragments.first(), checkingContextRegister); } bool needsEpilogue = generatePrologue(); ASSERT(minimumRegisterCountForAttributes <= maximumRegisterCount); if (availableRegisterCount < minimumRegisterCountForAttributes) { reservedCalleeSavedRegisters = true; calleeSavedRegisterStackReferences = m_stackAllocator.push(m_registerAllocator.reserveCalleeSavedRegisters(minimumRegisterCountForAttributes - availableRegisterCount)); } m_registerAllocator.allocateRegister(elementAddressRegister); StackAllocator::StackReference temporaryStackBase; if (m_functionType == FunctionType::SelectorCheckerWithCheckingContext) m_checkingContextStackReference = m_stackAllocator.push(checkingContextRegister); if (m_needsAdjacentBacktrackingStart) m_adjacentBacktrackingStart = m_stackAllocator.allocateUninitialized(); // Remember the stack base of the temporary variables. if (m_checkingContextStackReference.isValid()) temporaryStackBase = m_checkingContextStackReference; else if (m_needsAdjacentBacktrackingStart) temporaryStackBase = m_adjacentBacktrackingStart; Assembler::JumpList failureCases; for (unsigned i = 0; i < m_selectorFragments.size(); ++i) { const SelectorFragment& fragment = m_selectorFragments[i]; switch (fragment.relationToRightFragment) { case FragmentRelation::Rightmost: generateElementMatching(failureCases, failureCases, fragment); break; case FragmentRelation::Descendant: generateAncestorTreeWalker(failureCases, fragment); break; case FragmentRelation::Child: generateParentElementTreeWalker(failureCases, fragment); break; case FragmentRelation::DirectAdjacent: generateDirectAdjacentTreeWalker(failureCases, fragment); break; case FragmentRelation::IndirectAdjacent: generateIndirectAdjacentTreeWalker(failureCases, fragment); break; } generateBacktrackingTailsIfNeeded(failureCases, fragment); } if (m_functionType == FunctionType::SelectorCheckerWithCheckingContext) { ASSERT(!m_selectorFragments.isEmpty()); generateMarkPseudoStyleForPseudoElement(failureCases, m_selectorFragments.first()); } m_registerAllocator.deallocateRegister(elementAddressRegister); if (m_functionType == FunctionType::SimpleSelectorChecker) { if (!temporaryStackBase.isValid() && !reservedCalleeSavedRegisters && !needsEpilogue) { ASSERT(!m_needsAdjacentBacktrackingStart); // Success. m_assembler.move(Assembler::TrustedImm32(1), returnRegister); m_assembler.ret(); // Failure. ASSERT_WITH_MESSAGE(failureOnFunctionEntry.empty(), "Early failure on function entry is used for pseudo element. When early failure is used, function type is SelectorCheckerWithCheckingContext."); if (!failureCases.empty()) { failureCases.link(&m_assembler); m_assembler.move(Assembler::TrustedImm32(0), returnRegister); m_assembler.ret(); } return; } } // Success. m_assembler.move(Assembler::TrustedImm32(1), returnRegister); // Failure. if (!failureCases.empty()) { Assembler::Jump skipFailureCase = m_assembler.jump(); failureCases.link(&m_assembler); m_assembler.move(Assembler::TrustedImm32(0), returnRegister); skipFailureCase.link(&m_assembler); } if (temporaryStackBase.isValid()) m_stackAllocator.popAndDiscardUpTo(temporaryStackBase); if (reservedCalleeSavedRegisters) m_stackAllocator.pop(calleeSavedRegisterStackReferences, m_registerAllocator.restoreCalleeSavedRegisters()); if (needsEpilogue) generateEpilogue(); m_assembler.ret(); // Early failure on function entry case. if (!failureOnFunctionEntry.empty()) { failureOnFunctionEntry.link(&m_assembler); m_assembler.move(Assembler::TrustedImm32(0), returnRegister); m_assembler.ret(); } } static inline Assembler::Jump testIsElementFlagOnNode(Assembler::ResultCondition condition, Assembler& assembler, Assembler::RegisterID nodeAddress) { return assembler.branchTest32(condition, Assembler::Address(nodeAddress, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsElement())); } void SelectorCodeGenerator::generateWalkToParentNode(Assembler::RegisterID targetRegister) { m_assembler.loadPtr(Assembler::Address(elementAddressRegister, Node::parentNodeMemoryOffset()), targetRegister); } void SelectorCodeGenerator::generateWalkToParentElement(Assembler::JumpList& failureCases, Assembler::RegisterID targetRegister) { // ContainerNode* parent = parentNode() // if (!parent || !parent->isElementNode()) // failure generateWalkToParentNode(targetRegister); failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, targetRegister)); failureCases.append(testIsElementFlagOnNode(Assembler::Zero, m_assembler, targetRegister)); } void SelectorCodeGenerator::generateParentElementTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { Assembler::JumpList traversalFailureCases; generateWalkToParentElement(traversalFailureCases, elementAddressRegister); linkFailures(failureCases, fragment.traversalBacktrackingAction, traversalFailureCases); Assembler::JumpList matchingTagNameFailureCases; Assembler::JumpList matchingPostTagNameFailureCases; generateElementMatching(matchingTagNameFailureCases, matchingPostTagNameFailureCases, fragment); linkFailures(failureCases, fragment.matchingTagNameBacktrackingAction, matchingTagNameFailureCases); linkFailures(failureCases, fragment.matchingPostTagNameBacktrackingAction, matchingPostTagNameFailureCases); if (fragment.backtrackingFlags & BacktrackingFlag::SaveDescendantBacktrackingStart) { m_descendantBacktrackingStart = m_registerAllocator.allocateRegister(); m_assembler.move(elementAddressRegister, m_descendantBacktrackingStart); } } void SelectorCodeGenerator::generateAncestorTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { // Loop over the ancestors until one of them matches the fragment. Assembler::Label loopStart(m_assembler.label()); if (fragment.backtrackingFlags & BacktrackingFlag::DescendantEntryPoint) m_descendantTreeWalkerBacktrackingPoint = m_assembler.label(); generateWalkToParentElement(failureCases, elementAddressRegister); if (fragment.backtrackingFlags & BacktrackingFlag::DescendantEntryPoint) m_descendantEntryPoint = m_assembler.label(); Assembler::JumpList matchingFailureCases; generateElementMatching(matchingFailureCases, matchingFailureCases, fragment); matchingFailureCases.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(); m_assembler.loadPtr(Assembler::Address(workRegister, Node::previousSiblingMemoryOffset()), workRegister); failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, workRegister)); testIsElementFlagOnNode(Assembler::Zero, m_assembler, workRegister).linkTo(loopStart, &m_assembler); } void SelectorCodeGenerator::generateWalkToPreviousAdjacent(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { // do { // previousSibling = previousSibling->previousSibling(); // if (!previousSibling) // failure! // while (!previousSibling->isElement()); Assembler::RegisterID previousSibling; bool useTailOnTraversalFailure = fragment.traversalBacktrackingAction >= BacktrackingAction::JumpToDescendantTail; if (!useTailOnTraversalFailure) { // If the current fragment is not dependant on a previously saved elementAddressRegister, a fast recover // from a failure would resume with elementAddressRegister. // When walking to the previous sibling, the failure can be that previousSibling is null. We cannot backtrack // with a null elementAddressRegister so we do the traversal on a copy. previousSibling = m_registerAllocator.allocateRegister(); m_assembler.move(elementAddressRegister, previousSibling); } else previousSibling = elementAddressRegister; Assembler::JumpList traversalFailureCases; generateWalkToPreviousAdjacentElement(traversalFailureCases, previousSibling); linkFailures(failureCases, fragment.traversalBacktrackingAction, traversalFailureCases); // On success, move previousSibling over to elementAddressRegister if we could not work on elementAddressRegister directly. if (!useTailOnTraversalFailure) { m_assembler.move(previousSibling, elementAddressRegister); m_registerAllocator.deallocateRegister(previousSibling); } } void SelectorCodeGenerator::generateDirectAdjacentTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { markParentElementIfResolvingStyle(Node::flagChildrenAffectedByDirectAdjacentRulesFlag()); generateWalkToPreviousAdjacent(failureCases, fragment); Assembler::JumpList matchingTagNameFailureCases; Assembler::JumpList matchingPostTagNameFailureCases; generateElementMatching(matchingTagNameFailureCases, matchingPostTagNameFailureCases, fragment); linkFailures(failureCases, fragment.matchingTagNameBacktrackingAction, matchingTagNameFailureCases); linkFailures(failureCases, fragment.matchingPostTagNameBacktrackingAction, matchingPostTagNameFailureCases); if (fragment.backtrackingFlags & BacktrackingFlag::SaveAdjacentBacktrackingStart) { unsigned offsetToAdjacentBacktrackingStart = m_stackAllocator.offsetToStackReference(m_adjacentBacktrackingStart); m_assembler.storePtr(elementAddressRegister, Assembler::Address(Assembler::stackPointerRegister, offsetToAdjacentBacktrackingStart)); } } void SelectorCodeGenerator::generateIndirectAdjacentTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { markParentElementIfResolvingStyle(Element::setChildrenAffectedByForwardPositionalRules); Assembler::Label loopStart(m_assembler.label()); if (fragment.backtrackingFlags & BacktrackingFlag::IndirectAdjacentEntryPoint) m_indirectAdjacentTreeWalkerBacktrackingPoint = m_assembler.label(); generateWalkToPreviousAdjacent(failureCases, fragment); if (fragment.backtrackingFlags & BacktrackingFlag::IndirectAdjacentEntryPoint) m_indirectAdjacentEntryPoint = m_assembler.label(); Assembler::JumpList localFailureCases; generateElementMatching(localFailureCases, localFailureCases, fragment); localFailureCases.linkTo(loopStart, &m_assembler); } void SelectorCodeGenerator::addFlagsToElementStyleFromContext(Assembler::RegisterID checkingContext, int64_t newFlag) { 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. Assembler::Address flagAddress(childStyle, RenderStyle::noninheritedFlagsMemoryOffset() + RenderStyle::NonInheritedFlags::flagsMemoryOffset()); #if CPU(ARM_THUMB2) int32_t flagLowBits = newFlag & 0xffffffff; int32_t flagHighBits = newFlag >> 32; if (flagLowBits) m_assembler.or32(Assembler::TrustedImm32(flagLowBits), flagAddress); if (flagHighBits) { Assembler::Address flagHighAddress = flagAddress.withOffset(4); m_assembler.or32(Assembler::TrustedImm32(flagHighBits), flagHighAddress); } #elif CPU(X86_64) || CPU(ARM64) LocalRegister flags(m_registerAllocator); m_assembler.load64(flagAddress, flags); LocalRegister isFirstChildStateFlagImmediate(m_registerAllocator); m_assembler.move(Assembler::TrustedImm64(newFlag), isFirstChildStateFlagImmediate); m_assembler.or64(isFirstChildStateFlagImmediate, flags); m_assembler.store64(flags, flagAddress); #else #error SelectorCodeGenerator::addFlagsToElementStyleFromContext not implemented for this architecture. #endif } Assembler::JumpList SelectorCodeGenerator::jumpIfNoPreviousAdjacentElement() { Assembler::JumpList successCase; LocalRegister previousSibling(m_registerAllocator); m_assembler.move(elementAddressRegister, previousSibling); generateWalkToPreviousAdjacentElement(successCase, previousSibling); return successCase; } Assembler::JumpList SelectorCodeGenerator::jumpIfNoNextAdjacentElement() { Assembler::JumpList successCase; LocalRegister nextSibling(m_registerAllocator); m_assembler.move(elementAddressRegister, nextSibling); generateWalkToNextAdjacentElement(successCase, nextSibling); return successCase; } void SelectorCodeGenerator::loadCheckingContext(Assembler::RegisterID checkingContext) { RELEASE_ASSERT(m_selectorContext == SelectorContext::RuleCollector); // Get the checking context. unsigned offsetToCheckingContext = m_stackAllocator.offsetToStackReference(m_checkingContextStackReference); m_assembler.loadPtr(Assembler::Address(Assembler::stackPointerRegister, offsetToCheckingContext), checkingContext); } Assembler::Jump SelectorCodeGenerator::branchOnResolvingModeWithCheckingContext(Assembler::RelationalCondition condition, SelectorChecker::Mode mode, Assembler::RegisterID checkingContext) { // Depend on the specified resolving mode and our current mode, branch. static_assert(sizeof(SelectorChecker::Mode) == 1, "We generate a byte load/test for the SelectorChecker::Mode."); return m_assembler.branch8(condition, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, resolvingMode)), Assembler::TrustedImm32(static_cast::type>(mode))); } Assembler::Jump SelectorCodeGenerator::branchOnResolvingMode(Assembler::RelationalCondition condition, SelectorChecker::Mode mode, Assembler::RegisterID checkingContext) { loadCheckingContext(checkingContext); return branchOnResolvingModeWithCheckingContext(condition, mode, checkingContext); } Assembler::Jump SelectorCodeGenerator::jumpIfNotResolvingStyle(Assembler::RegisterID checkingContext) { return branchOnResolvingMode(Assembler::NotEqual, SelectorChecker::Mode::ResolvingStyle, checkingContext); } static void getDocument(Assembler& assembler, Assembler::RegisterID element, Assembler::RegisterID output) { assembler.loadPtr(Assembler::Address(element, Node::treeScopeMemoryOffset()), output); assembler.loadPtr(Assembler::Address(output, TreeScope::documentScopeMemoryOffset()), output); } void SelectorCodeGenerator::generateSpecialFailureInQuirksModeForActiveAndHoverIfNeeded(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { if (fragment.onlyMatchesLinksInQuirksMode) { // If the element is a link, it can always match :hover or :active. Assembler::Jump isLink = m_assembler.branchTest32(Assembler::NonZero, Assembler::Address(elementAddressRegister, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsLink())); // Only quirks mode restrict :hover and :active. static_assert(sizeof(DocumentCompatibilityMode) == 1, "We generate a byte load/test for the compatibility mode."); LocalRegister documentAddress(m_registerAllocator); getDocument(m_assembler, elementAddressRegister, documentAddress); failureCases.append(m_assembler.branchTest8(Assembler::NonZero, Assembler::Address(documentAddress, Document::compatibilityModeMemoryOffset()), Assembler::TrustedImm32(static_cast::type>(DocumentCompatibilityMode::QuirksMode)))); isLink.link(&m_assembler); } } #if CPU(ARM_THUMB2) && !CPU(APPLE_ARMV7S) // FIXME: This could be implemented in assembly to avoid a function call, and we know the divisor at jit-compile time. static int moduloHelper(int dividend, int divisor) { return dividend % divisor; } #endif // The value in inputDividend is destroyed by the modulo operation. Assembler::Jump SelectorCodeGenerator::modulo(Assembler::ResultCondition condition, Assembler::RegisterID inputDividend, int divisor) { RELEASE_ASSERT(divisor); #if CPU(ARM64) || CPU(APPLE_ARMV7S) LocalRegister divisorRegister(m_registerAllocator); m_assembler.move(Assembler::TrustedImm32(divisor), divisorRegister); LocalRegister resultRegister(m_registerAllocator); m_assembler.m_assembler.sdiv<32>(resultRegister, inputDividend, divisorRegister); m_assembler.mul32(divisorRegister, resultRegister); return m_assembler.branchSub32(condition, inputDividend, resultRegister, resultRegister); #elif CPU(ARM_THUMB2) && !CPU(APPLE_ARMV7S) LocalRegisterWithPreference divisorRegister(m_registerAllocator, JSC::GPRInfo::argumentGPR1); m_assembler.move(Assembler::TrustedImm32(divisor), divisorRegister); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(moduloHelper); functionCall.setTwoArguments(inputDividend, divisorRegister); return functionCall.callAndBranchOnBooleanReturnValue(condition); #elif CPU(X86_64) // idiv takes RAX + an arbitrary register, and return RAX + RDX. Most of this code is about doing // an efficient allocation of those registers. If a register is already in use and is not the inputDividend, // we first try to copy it to a temporary register, it that is not possible we fall back to the stack. enum class RegisterAllocationType { External, AllocatedLocally, CopiedToTemporary, PushedToStack }; // 1) Get RAX and RDX. // If they are already used, push them to the stack. Assembler::RegisterID dividend = JSC::X86Registers::eax; RegisterAllocationType dividendAllocation = RegisterAllocationType::External; StackAllocator::StackReference temporaryDividendStackReference; Assembler::RegisterID temporaryDividendCopy = InvalidGPRReg; if (inputDividend != dividend) { bool registerIsInUse = m_registerAllocator.allocatedRegisters().contains(dividend); if (registerIsInUse) { if (m_registerAllocator.availableRegisterCount()) { temporaryDividendCopy = m_registerAllocator.allocateRegister(); m_assembler.move(dividend, temporaryDividendCopy); dividendAllocation = RegisterAllocationType::CopiedToTemporary; } else { temporaryDividendStackReference = m_stackAllocator.push(dividend); dividendAllocation = RegisterAllocationType::PushedToStack; } } else { m_registerAllocator.allocateRegister(dividend); dividendAllocation = RegisterAllocationType::AllocatedLocally; } m_assembler.move(inputDividend, dividend); } Assembler::RegisterID remainder = JSC::X86Registers::edx; RegisterAllocationType remainderAllocation = RegisterAllocationType::External; StackAllocator::StackReference temporaryRemainderStackReference; Assembler::RegisterID temporaryRemainderCopy = InvalidGPRReg; if (inputDividend != remainder) { bool registerIsInUse = m_registerAllocator.allocatedRegisters().contains(remainder); if (registerIsInUse) { if (m_registerAllocator.availableRegisterCount()) { temporaryRemainderCopy = m_registerAllocator.allocateRegister(); m_assembler.move(remainder, temporaryRemainderCopy); remainderAllocation = RegisterAllocationType::CopiedToTemporary; } else { temporaryRemainderStackReference = m_stackAllocator.push(remainder); remainderAllocation = RegisterAllocationType::PushedToStack; } } else { m_registerAllocator.allocateRegister(remainder); remainderAllocation = RegisterAllocationType::AllocatedLocally; } } m_assembler.m_assembler.cdq(); // 2) Perform the division with idiv. { LocalRegister divisorRegister(m_registerAllocator); m_assembler.move(Assembler::TrustedImm64(divisor), divisorRegister); m_assembler.m_assembler.idivl_r(divisorRegister); m_assembler.test32(condition, remainder); } // 3) Return RAX and RDX. if (remainderAllocation == RegisterAllocationType::AllocatedLocally) m_registerAllocator.deallocateRegister(remainder); else if (remainderAllocation == RegisterAllocationType::CopiedToTemporary) { m_assembler.move(temporaryRemainderCopy, remainder); m_registerAllocator.deallocateRegister(temporaryRemainderCopy); } else if (remainderAllocation == RegisterAllocationType::PushedToStack) m_stackAllocator.pop(temporaryRemainderStackReference, remainder); if (dividendAllocation == RegisterAllocationType::AllocatedLocally) m_registerAllocator.deallocateRegister(dividend); else if (dividendAllocation == RegisterAllocationType::CopiedToTemporary) { m_assembler.move(temporaryDividendCopy, dividend); m_registerAllocator.deallocateRegister(temporaryDividendCopy); } else if (dividendAllocation == RegisterAllocationType::PushedToStack) m_stackAllocator.pop(temporaryDividendStackReference, dividend); // 4) Branch on the test. return m_assembler.branch(condition); #else #error Modulo is not implemented for this architecture. #endif } void SelectorCodeGenerator::moduloIsZero(Assembler::JumpList& failureCases, Assembler::RegisterID inputDividend, int divisor) { if (divisor == 1 || divisor == -1) return; if (divisor == 2 || divisor == -2) { failureCases.append(m_assembler.branchTest32(Assembler::NonZero, inputDividend, Assembler::TrustedImm32(1))); return; } failureCases.append(modulo(Assembler::NonZero, inputDividend, divisor)); } static void setNodeFlag(Assembler& assembler, Assembler::RegisterID elementAddress, int32_t flag) { assembler.or32(Assembler::TrustedImm32(flag), Assembler::Address(elementAddress, Node::nodeFlagsMemoryOffset())); } void SelectorCodeGenerator::markParentElementIfResolvingStyle(int32_t nodeFlag) { if (m_selectorContext == SelectorContext::QuerySelector) return; Assembler::JumpList skipMarking; { LocalRegister checkingContext(m_registerAllocator); skipMarking.append(jumpIfNotResolvingStyle(checkingContext)); } LocalRegister parentElement(m_registerAllocator); generateWalkToParentElement(skipMarking, parentElement); setNodeFlag(m_assembler, parentElement, nodeFlag); skipMarking.link(&m_assembler); } void SelectorCodeGenerator::markParentElementIfResolvingStyle(JSC::FunctionPtr markingFunction) { if (m_selectorContext == SelectorContext::QuerySelector) return; // if (checkingContext.resolvingMode == ResolvingStyle) { // Element* parent = element->parentNode(); // markingFunction(parent); // } Assembler::JumpList skipMarking; { LocalRegister checkingContext(m_registerAllocator); skipMarking.append(jumpIfNotResolvingStyle(checkingContext)); } // Get the parent element in a temporary register. Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister(); generateWalkToParentElement(skipMarking, parentElement); // Return the register parentElement just before the function call since we don't need it to be preserved // on the stack. m_registerAllocator.deallocateRegister(parentElement); // Invoke the marking function on the parent element. FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(markingFunction); functionCall.setOneArgument(parentElement); functionCall.call(); skipMarking.link(&m_assembler); } void SelectorCodeGenerator::linkFailures(Assembler::JumpList& globalFailureCases, BacktrackingAction backtrackingAction, Assembler::JumpList& localFailureCases) { switch (backtrackingAction) { case BacktrackingAction::NoBacktracking: globalFailureCases.append(localFailureCases); break; case BacktrackingAction::JumpToDescendantEntryPoint: localFailureCases.linkTo(m_descendantEntryPoint, &m_assembler); break; case BacktrackingAction::JumpToDescendantTreeWalkerEntryPoint: localFailureCases.linkTo(m_descendantTreeWalkerBacktrackingPoint, &m_assembler); break; case BacktrackingAction::JumpToDescendantTail: m_descendantBacktrackingFailureCases.append(localFailureCases); break; case BacktrackingAction::JumpToIndirectAdjacentEntryPoint: localFailureCases.linkTo(m_indirectAdjacentEntryPoint, &m_assembler); break; case BacktrackingAction::JumpToIndirectAdjacentTreeWalkerEntryPoint: localFailureCases.linkTo(m_indirectAdjacentTreeWalkerBacktrackingPoint, &m_assembler); break; case BacktrackingAction::JumpToDirectAdjacentTail: m_adjacentBacktrackingFailureCases.append(localFailureCases); break; } } void SelectorCodeGenerator::generateAdjacentBacktrackingTail() { // Recovering tail. m_adjacentBacktrackingFailureCases.link(&m_assembler); m_adjacentBacktrackingFailureCases.clear(); unsigned offsetToAdjacentBacktrackingStart = m_stackAllocator.offsetToStackReference(m_adjacentBacktrackingStart); m_assembler.loadPtr(Assembler::Address(Assembler::stackPointerRegister, offsetToAdjacentBacktrackingStart), elementAddressRegister); m_assembler.jump(m_indirectAdjacentEntryPoint); } void SelectorCodeGenerator::generateDescendantBacktrackingTail() { m_descendantBacktrackingFailureCases.link(&m_assembler); m_descendantBacktrackingFailureCases.clear(); m_assembler.move(m_descendantBacktrackingStart, elementAddressRegister); m_registerAllocator.deallocateRegister(m_descendantBacktrackingStart); m_assembler.jump(m_descendantEntryPoint); } void SelectorCodeGenerator::generateBacktrackingTailsIfNeeded(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { if (fragment.backtrackingFlags & BacktrackingFlag::DirectAdjacentTail && fragment.backtrackingFlags & BacktrackingFlag::DescendantTail) { Assembler::Jump normalCase = m_assembler.jump(); generateAdjacentBacktrackingTail(); generateDescendantBacktrackingTail(); normalCase.link(&m_assembler); } else if (fragment.backtrackingFlags & BacktrackingFlag::DirectAdjacentTail) { Assembler::Jump normalCase = m_assembler.jump(); generateAdjacentBacktrackingTail(); failureCases.append(m_assembler.jump()); normalCase.link(&m_assembler); } else if (fragment.backtrackingFlags & BacktrackingFlag::DescendantTail) { Assembler::Jump normalCase = m_assembler.jump(); generateDescendantBacktrackingTail(); normalCase.link(&m_assembler); } } void SelectorCodeGenerator::generateElementMatching(Assembler::JumpList& matchingTagNameFailureCases, Assembler::JumpList& matchingPostTagNameFailureCases, const SelectorFragment& fragment) { if (fragment.tagName) generateElementHasTagName(matchingTagNameFailureCases, *(fragment.tagName)); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassLink)) generateElementIsLink(matchingPostTagNameFailureCases); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassRoot)) generateElementIsRoot(matchingPostTagNameFailureCases); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassTarget)) generateElementIsTarget(matchingPostTagNameFailureCases); for (unsigned i = 0; i < fragment.unoptimizedPseudoClasses.size(); ++i) generateElementFunctionCallTest(matchingPostTagNameFailureCases, fragment.unoptimizedPseudoClasses[i]); generateElementDataMatching(matchingPostTagNameFailureCases, fragment); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassActive)) generateElementIsActive(matchingPostTagNameFailureCases, fragment); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassHover)) generateElementIsHovered(matchingPostTagNameFailureCases, fragment); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassOnlyChild)) generateElementIsOnlyChild(matchingPostTagNameFailureCases, fragment); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassFirstChild)) generateElementIsFirstChild(matchingPostTagNameFailureCases, fragment); if (fragment.pseudoClasses.contains(CSSSelector::PseudoClassLastChild)) generateElementIsLastChild(matchingPostTagNameFailureCases, fragment); if (!fragment.nthChildFilters.isEmpty()) generateElementIsNthChild(matchingPostTagNameFailureCases, fragment); if (!fragment.notFilters.isEmpty()) generateElementMatchesNotPseudoClass(matchingPostTagNameFailureCases, fragment); if (!fragment.anyFilters.isEmpty()) generateElementMatchesAnyPseudoClass(matchingPostTagNameFailureCases, fragment); if (fragment.langFilter) generateElementIsInLanguage(matchingPostTagNameFailureCases, *fragment.langFilter); if (fragment.pseudoElementSelector) generateElementHasPseudoElement(matchingPostTagNameFailureCases, fragment); } void SelectorCodeGenerator::generateElementDataMatching(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { if (!fragment.id && fragment.classNames.isEmpty() && fragment.attributes.isEmpty()) return; // Generate: // elementDataAddress = element->elementData(); // if (!elementDataAddress) // failure! LocalRegister elementDataAddress(m_registerAllocator); m_assembler.loadPtr(Assembler::Address(elementAddressRegister, Element::elementDataMemoryOffset()), elementDataAddress); failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, elementDataAddress)); if (fragment.id) generateElementHasId(failureCases, elementDataAddress, *fragment.id); if (!fragment.classNames.isEmpty()) generateElementHasClasses(failureCases, elementDataAddress, fragment.classNames); if (!fragment.attributes.isEmpty()) generateElementAttributesMatching(failureCases, elementDataAddress, fragment); } static inline Assembler::Jump testIsHTMLFlagOnNode(Assembler::ResultCondition condition, Assembler& assembler, Assembler::RegisterID nodeAddress) { return assembler.branchTest32(condition, Assembler::Address(nodeAddress, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsHTML())); } static inline bool canMatchStyleAttribute(const SelectorFragment& fragment) { for (unsigned i = 0; i < fragment.attributes.size(); ++i) { const CSSSelector& attributeSelector = fragment.attributes[i].selector(); const QualifiedName& attributeName = attributeSelector.attribute(); if (Attribute::nameMatchesFilter(HTMLNames::styleAttr, attributeName.prefix(), attributeName.localName(), attributeName.namespaceURI())) return true; const AtomicString& canonicalLocalName = attributeSelector.attributeCanonicalLocalName(); if (attributeName.localName() != canonicalLocalName && Attribute::nameMatchesFilter(HTMLNames::styleAttr, attributeName.prefix(), attributeSelector.attributeCanonicalLocalName(), attributeName.namespaceURI())) { return true; } } return false; } void SelectorCodeGenerator::generateSynchronizeStyleAttribute(Assembler::RegisterID elementDataArraySizeAndFlags) { // The style attribute is updated lazily based on the flag styleAttributeIsDirty. Assembler::Jump styleAttributeNotDirty = m_assembler.branchTest32(Assembler::Zero, elementDataArraySizeAndFlags, Assembler::TrustedImm32(ElementData::styleAttributeIsDirtyFlag())); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(StyledElement::synchronizeStyleAttributeInternal); Assembler::RegisterID elementAddress = elementAddressRegister; functionCall.setOneArgument(elementAddress); functionCall.call(); styleAttributeNotDirty.link(&m_assembler); } static inline bool canMatchAnimatableSVGAttribute(const SelectorFragment& fragment) { for (unsigned i = 0; i < fragment.attributes.size(); ++i) { const CSSSelector& attributeSelector = fragment.attributes[i].selector(); const QualifiedName& selectorAttributeName = attributeSelector.attribute(); const QualifiedName& candidateForLocalName = SVGElement::animatableAttributeForName(selectorAttributeName.localName()); if (Attribute::nameMatchesFilter(candidateForLocalName, selectorAttributeName.prefix(), selectorAttributeName.localName(), selectorAttributeName.namespaceURI())) return true; const AtomicString& canonicalLocalName = attributeSelector.attributeCanonicalLocalName(); if (selectorAttributeName.localName() != canonicalLocalName) { const QualifiedName& candidateForCanonicalLocalName = SVGElement::animatableAttributeForName(selectorAttributeName.localName()); if (Attribute::nameMatchesFilter(candidateForCanonicalLocalName, selectorAttributeName.prefix(), selectorAttributeName.localName(), selectorAttributeName.namespaceURI())) return true; } } return false; } void SelectorCodeGenerator::generateSynchronizeAllAnimatedSVGAttribute(Assembler::RegisterID elementDataArraySizeAndFlags) { // SVG attributes can be updated lazily depending on the flag AnimatedSVGAttributesAreDirty. We need to check // that first. Assembler::Jump animatedSVGAttributesNotDirty = m_assembler.branchTest32(Assembler::Zero, elementDataArraySizeAndFlags, Assembler::TrustedImm32(ElementData::animatedSVGAttributesAreDirtyFlag())); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(SVGElement::synchronizeAllAnimatedSVGAttribute); Assembler::RegisterID elementAddress = elementAddressRegister; functionCall.setOneArgument(elementAddress); functionCall.call(); animatedSVGAttributesNotDirty.link(&m_assembler); } void SelectorCodeGenerator::generateElementAttributesMatching(Assembler::JumpList& failureCases, const LocalRegister& elementDataAddress, const SelectorFragment& fragment) { LocalRegister scratchRegister(m_registerAllocator); Assembler::RegisterID elementDataArraySizeAndFlags = scratchRegister; Assembler::RegisterID attributeArrayLength = scratchRegister; m_assembler.load32(Assembler::Address(elementDataAddress, ElementData::arraySizeAndFlagsMemoryOffset()), elementDataArraySizeAndFlags); if (canMatchStyleAttribute(fragment)) generateSynchronizeStyleAttribute(elementDataArraySizeAndFlags); if (canMatchAnimatableSVGAttribute(fragment)) generateSynchronizeAllAnimatedSVGAttribute(elementDataArraySizeAndFlags); // Attributes can be stored either in a separate vector for UniqueElementData, or after the elementData itself // for ShareableElementData. LocalRegister attributeArrayPointer(m_registerAllocator); Assembler::Jump isShareableElementData = m_assembler.branchTest32(Assembler::Zero, elementDataArraySizeAndFlags, Assembler::TrustedImm32(ElementData::isUniqueFlag())); { ptrdiff_t attributeVectorOffset = UniqueElementData::attributeVectorMemoryOffset(); m_assembler.loadPtr(Assembler::Address(elementDataAddress, attributeVectorOffset + UniqueElementData::AttributeVector::dataMemoryOffset()), attributeArrayPointer); m_assembler.load32(Assembler::Address(elementDataAddress, attributeVectorOffset + UniqueElementData::AttributeVector::sizeMemoryOffset()), attributeArrayLength); } Assembler::Jump skipShareable = m_assembler.jump(); { isShareableElementData.link(&m_assembler); m_assembler.urshift32(elementDataArraySizeAndFlags, Assembler::TrustedImm32(ElementData::arraySizeOffset()), attributeArrayLength); m_assembler.addPtr(Assembler::TrustedImm32(ShareableElementData::attributeArrayMemoryOffset()), elementDataAddress, attributeArrayPointer); } skipShareable.link(&m_assembler); // If there are no attributes, fail immediately. failureCases.append(m_assembler.branchTest32(Assembler::Zero, attributeArrayLength)); unsigned attributeCount = fragment.attributes.size(); for (unsigned i = 0; i < attributeCount; ++i) { Assembler::RegisterID decIndexRegister; Assembler::RegisterID currentAttributeAddress; bool isLastAttribute = i == (attributeCount - 1); if (!isLastAttribute) { // We need to make a copy to let the next iterations use the values. currentAttributeAddress = m_registerAllocator.allocateRegister(); decIndexRegister = m_registerAllocator.allocateRegister(); m_assembler.move(attributeArrayPointer, currentAttributeAddress); m_assembler.move(attributeArrayLength, decIndexRegister); } else { currentAttributeAddress = attributeArrayPointer; decIndexRegister = attributeArrayLength; } generateElementAttributeMatching(failureCases, currentAttributeAddress, decIndexRegister, fragment.attributes[i]); if (!isLastAttribute) { m_registerAllocator.deallocateRegister(decIndexRegister); m_registerAllocator.deallocateRegister(currentAttributeAddress); } } } void SelectorCodeGenerator::generateElementAttributeMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, Assembler::RegisterID decIndexRegister, const AttributeMatchingInfo& attributeInfo) { // Get the localName used for comparison. HTML elements use a lowercase local name known in selectors as canonicalLocalName. LocalRegister localNameToMatch(m_registerAllocator); // In general, canonicalLocalName and localName are the same. When they differ, we have to check if the node is HTML to know // which one to use. const CSSSelector& attributeSelector = attributeInfo.selector(); const AtomicStringImpl* canonicalLocalName = attributeSelector.attributeCanonicalLocalName().impl(); const AtomicStringImpl* localName = attributeSelector.attribute().localName().impl(); if (canonicalLocalName == localName) m_assembler.move(Assembler::TrustedImmPtr(canonicalLocalName), localNameToMatch); else { m_assembler.move(Assembler::TrustedImmPtr(canonicalLocalName), localNameToMatch); Assembler::Jump elementIsHTML = testIsHTMLFlagOnNode(Assembler::NonZero, m_assembler, elementAddressRegister); m_assembler.move(Assembler::TrustedImmPtr(localName), localNameToMatch); elementIsHTML.link(&m_assembler); } Assembler::JumpList successCases; Assembler::Label loopStart(m_assembler.label()); { LocalRegister qualifiedNameImpl(m_registerAllocator); m_assembler.loadPtr(Assembler::Address(currentAttributeAddress, Attribute::nameMemoryOffset()), qualifiedNameImpl); bool shouldCheckNamespace = attributeSelector.attribute().prefix() != starAtom; if (shouldCheckNamespace) { Assembler::Jump nameDoesNotMatch = m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(qualifiedNameImpl, QualifiedName::QualifiedNameImpl::localNameMemoryOffset()), localNameToMatch); const AtomicStringImpl* namespaceURI = attributeSelector.attribute().namespaceURI().impl(); if (namespaceURI) { LocalRegister namespaceToMatch(m_registerAllocator); m_assembler.move(Assembler::TrustedImmPtr(namespaceURI), namespaceToMatch); successCases.append(m_assembler.branchPtr(Assembler::Equal, Assembler::Address(qualifiedNameImpl, QualifiedName::QualifiedNameImpl::namespaceMemoryOffset()), namespaceToMatch)); } else successCases.append(m_assembler.branchTestPtr(Assembler::Zero, Assembler::Address(qualifiedNameImpl, QualifiedName::QualifiedNameImpl::namespaceMemoryOffset()))); nameDoesNotMatch.link(&m_assembler); } else successCases.append(m_assembler.branchPtr(Assembler::Equal, Assembler::Address(qualifiedNameImpl, QualifiedName::QualifiedNameImpl::localNameMemoryOffset()), localNameToMatch)); } Assembler::Label loopReEntry(m_assembler.label()); // If we reached the last element -> failure. failureCases.append(m_assembler.branchSub32(Assembler::Zero, Assembler::TrustedImm32(1), decIndexRegister)); // Otherwise just loop over. m_assembler.addPtr(Assembler::TrustedImm32(sizeof(Attribute)), currentAttributeAddress); m_assembler.jump().linkTo(loopStart, &m_assembler); successCases.link(&m_assembler); if (attributeSelector.m_match != CSSSelector::Set) { // We make the assumption that name matching fails in most cases and we keep value matching outside // of the loop. We re-enter the loop if needed. // FIXME: exact case sensitive value matching is so simple that it should be done in the loop. Assembler::JumpList localFailureCases; generateElementAttributeValueMatching(localFailureCases, currentAttributeAddress, attributeInfo); localFailureCases.linkTo(loopReEntry, &m_assembler); } } enum CaseSensitivity { CaseSensitive, CaseInsensitive }; template static bool attributeValueBeginsWith(const Attribute* attribute, AtomicStringImpl* expectedString) { AtomicStringImpl& valueImpl = *attribute->value().impl(); if (caseSensitivity == CaseSensitive) return valueImpl.startsWith(expectedString); return valueImpl.startsWith(expectedString, false); } template static bool attributeValueContains(const Attribute* attribute, AtomicStringImpl* expectedString) { AtomicStringImpl& valueImpl = *attribute->value().impl(); if (caseSensitivity == CaseSensitive) return valueImpl.find(expectedString) != notFound; return valueImpl.findIgnoringCase(expectedString) != notFound; } template static bool attributeValueEndsWith(const Attribute* attribute, AtomicStringImpl* expectedString) { AtomicStringImpl& valueImpl = *attribute->value().impl(); if (caseSensitivity == CaseSensitive) return valueImpl.endsWith(expectedString); return valueImpl.endsWith(expectedString, false); } template static bool attributeValueMatchHyphenRule(const Attribute* attribute, AtomicStringImpl* expectedString) { AtomicStringImpl& valueImpl = *attribute->value().impl(); if (valueImpl.length() < expectedString->length()) return false; bool valueStartsWithExpectedString; if (caseSensitivity == CaseSensitive) valueStartsWithExpectedString = valueImpl.startsWith(expectedString); else valueStartsWithExpectedString = valueImpl.startsWith(expectedString, false); if (!valueStartsWithExpectedString) return false; return valueImpl.length() == expectedString->length() || valueImpl[expectedString->length()] == '-'; } template static bool attributeValueSpaceSeparetedListContains(const Attribute* attribute, AtomicStringImpl* expectedString) { AtomicStringImpl& value = *attribute->value().impl(); unsigned startSearchAt = 0; while (true) { size_t foundPos; if (caseSensitivity == CaseSensitive) foundPos = value.find(expectedString, startSearchAt); else foundPos = value.findIgnoringCase(expectedString, startSearchAt); if (foundPos == notFound) return false; if (!foundPos || value[foundPos - 1] == ' ') { unsigned endStr = foundPos + expectedString->length(); if (endStr == value.length() || value[endStr] == ' ') return true; } startSearchAt = foundPos + 1; } return false; } void SelectorCodeGenerator::generateElementAttributeValueMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, const AttributeMatchingInfo& attributeInfo) { const CSSSelector& attributeSelector = attributeInfo.selector(); const AtomicString& expectedValue = attributeSelector.value(); ASSERT(!expectedValue.isNull()); bool defaultToCaseSensitiveValueMatch = attributeInfo.canDefaultToCaseSensitiveValueMatch(); switch (attributeSelector.m_match) { case CSSSelector::Begin: generateElementAttributeFunctionCallValueMatching(failureCases, currentAttributeAddress, expectedValue, defaultToCaseSensitiveValueMatch, attributeValueBeginsWith, attributeValueBeginsWith); break; case CSSSelector::Contain: generateElementAttributeFunctionCallValueMatching(failureCases, currentAttributeAddress, expectedValue, defaultToCaseSensitiveValueMatch, attributeValueContains, attributeValueContains); break; case CSSSelector::End: generateElementAttributeFunctionCallValueMatching(failureCases, currentAttributeAddress, expectedValue, defaultToCaseSensitiveValueMatch, attributeValueEndsWith, attributeValueEndsWith); break; case CSSSelector::Exact: generateElementAttributeValueExactMatching(failureCases, currentAttributeAddress, expectedValue, defaultToCaseSensitiveValueMatch); break; case CSSSelector::Hyphen: generateElementAttributeFunctionCallValueMatching(failureCases, currentAttributeAddress, expectedValue, defaultToCaseSensitiveValueMatch, attributeValueMatchHyphenRule, attributeValueMatchHyphenRule); break; case CSSSelector::List: generateElementAttributeFunctionCallValueMatching(failureCases, currentAttributeAddress, expectedValue, defaultToCaseSensitiveValueMatch, attributeValueSpaceSeparetedListContains, attributeValueSpaceSeparetedListContains); break; default: ASSERT_NOT_REACHED(); } } static inline Assembler::Jump testIsHTMLClassOnDocument(Assembler::ResultCondition condition, Assembler& assembler, Assembler::RegisterID documentAddress) { return assembler.branchTest32(condition, Assembler::Address(documentAddress, Document::documentClassesMemoryOffset()), Assembler::TrustedImm32(Document::isHTMLDocumentClassFlag())); } void SelectorCodeGenerator::generateElementAttributeValueExactMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, const AtomicString& expectedValue, bool canDefaultToCaseSensitiveValueMatch) { LocalRegisterWithPreference expectedValueRegister(m_registerAllocator, JSC::GPRInfo::argumentGPR1); m_assembler.move(Assembler::TrustedImmPtr(expectedValue.impl()), expectedValueRegister); if (canDefaultToCaseSensitiveValueMatch) failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(currentAttributeAddress, Attribute::valueMemoryOffset()), expectedValueRegister)); else { Assembler::Jump skipCaseInsensitiveComparison = m_assembler.branchPtr(Assembler::Equal, Assembler::Address(currentAttributeAddress, Attribute::valueMemoryOffset()), expectedValueRegister); // If the element is an HTML element, in a HTML dcoument (not including XHTML), value matching is case insensitive. // Taking the contrapositive, if we find the element is not HTML or is not in a HTML document, the condition above // sould be sufficient and we can fail early. failureCases.append(testIsHTMLFlagOnNode(Assembler::Zero, m_assembler, elementAddressRegister)); { LocalRegister document(m_registerAllocator); getDocument(m_assembler, elementAddressRegister, document); failureCases.append(testIsHTMLClassOnDocument(Assembler::Zero, m_assembler, document)); } LocalRegister valueStringImpl(m_registerAllocator); m_assembler.loadPtr(Assembler::Address(currentAttributeAddress, Attribute::valueMemoryOffset()), valueStringImpl); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(WTF::equalIgnoringCaseNonNull); functionCall.setTwoArguments(valueStringImpl, expectedValueRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); skipCaseInsensitiveComparison.link(&m_assembler); } } void SelectorCodeGenerator::generateElementAttributeFunctionCallValueMatching(Assembler::JumpList& failureCases, Assembler::RegisterID currentAttributeAddress, const AtomicString& expectedValue, bool canDefaultToCaseSensitiveValueMatch, JSC::FunctionPtr caseSensitiveTest, JSC::FunctionPtr caseInsensitiveTest) { LocalRegisterWithPreference expectedValueRegister(m_registerAllocator, JSC::GPRInfo::argumentGPR1); m_assembler.move(Assembler::TrustedImmPtr(expectedValue.impl()), expectedValueRegister); if (canDefaultToCaseSensitiveValueMatch) { FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(caseSensitiveTest); functionCall.setTwoArguments(currentAttributeAddress, expectedValueRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } else { Assembler::JumpList shouldUseCaseSensitiveComparison; shouldUseCaseSensitiveComparison.append(testIsHTMLFlagOnNode(Assembler::Zero, m_assembler, elementAddressRegister)); { LocalRegister scratchRegister(m_registerAllocator); // scratchRegister = pointer to treeScope. m_assembler.loadPtr(Assembler::Address(elementAddressRegister, Node::treeScopeMemoryOffset()), scratchRegister); // scratchRegister = pointer to document. m_assembler.loadPtr(Assembler::Address(scratchRegister, TreeScope::documentScopeMemoryOffset()), scratchRegister); shouldUseCaseSensitiveComparison.append(testIsHTMLClassOnDocument(Assembler::Zero, m_assembler, scratchRegister)); } { FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(caseInsensitiveTest); functionCall.setTwoArguments(currentAttributeAddress, expectedValueRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } Assembler::Jump skipCaseSensitiveCase = m_assembler.jump(); { shouldUseCaseSensitiveComparison.link(&m_assembler); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(caseSensitiveTest); functionCall.setTwoArguments(currentAttributeAddress, expectedValueRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } skipCaseSensitiveCase.link(&m_assembler); } } void SelectorCodeGenerator::generateElementFunctionCallTest(Assembler::JumpList& failureCases, JSC::FunctionPtr testFunction) { Assembler::RegisterID elementAddress = elementAddressRegister; FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(testFunction); functionCall.setOneArgument(elementAddress); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } static void setFirstChildState(Element* element) { if (RenderStyle* style = element->renderStyle()) style->setFirstChildState(); } static bool elementIsActive(Element* element) { return element->active() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoClassActive); } static bool elementIsActiveForStyleResolution(Element* element, const CheckingContext* checkingContext) { if (checkingContext->resolvingMode == SelectorChecker::Mode::ResolvingStyle) element->setChildrenAffectedByActive(); return element->active() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoClassActive); } void SelectorCodeGenerator::generateElementIsActive(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { generateSpecialFailureInQuirksModeForActiveAndHoverIfNeeded(failureCases, fragment); if (m_selectorContext == SelectorContext::QuerySelector) { FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(elementIsActive); functionCall.setOneArgument(elementAddressRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } else { if (shouldUseRenderStyleFromCheckingContext(fragment)) { LocalRegister checkingContext(m_registerAllocator); Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext); addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::flagIsaffectedByActive()); notResolvingStyle.link(&m_assembler); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(elementIsActive); functionCall.setOneArgument(elementAddressRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } else { Assembler::RegisterID checkingContext = m_registerAllocator.allocateRegisterWithPreference(JSC::GPRInfo::argumentGPR1); loadCheckingContext(checkingContext); m_registerAllocator.deallocateRegister(checkingContext); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(elementIsActiveForStyleResolution); functionCall.setTwoArguments(elementAddressRegister, checkingContext); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } } } void SelectorCodeGenerator::generateElementIsFirstChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { if (m_selectorContext == SelectorContext::QuerySelector) { Assembler::JumpList successCase = jumpIfNoPreviousAdjacentElement(); failureCases.append(m_assembler.jump()); successCase.link(&m_assembler); LocalRegister parent(m_registerAllocator); generateWalkToParentElement(failureCases, parent); return; } Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister(); generateWalkToParentElement(failureCases, parentElement); // Zero in isFirstChildRegister is the success case. The register is set to non-zero if a sibling if found. LocalRegister isFirstChildRegister(m_registerAllocator); m_assembler.move(Assembler::TrustedImm32(0), isFirstChildRegister); { 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); successCase.link(&m_assembler); } LocalRegister checkingContext(m_registerAllocator); Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext); setNodeFlag(m_assembler, parentElement, Node::flagChildrenAffectedByFirstChildRulesFlag()); m_registerAllocator.deallocateRegister(parentElement); // The parent marking is unconditional. If the matching is not a success, we can now fail. // Otherwise we need to apply setFirstChildState() on the RenderStyle. failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isFirstChildRegister)); if (shouldUseRenderStyleFromCheckingContext(fragment)) addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::setFirstChildStateFlags()); else { FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(setFirstChildState); Assembler::RegisterID elementAddress = elementAddressRegister; functionCall.setOneArgument(elementAddress); functionCall.call(); } notResolvingStyle.link(&m_assembler); failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isFirstChildRegister)); } static bool elementIsHovered(Element* element) { return element->hovered() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoClassHover); } static bool elementIsHoveredForStyleResolution(Element* element, const CheckingContext* checkingContext) { if (checkingContext->resolvingMode == SelectorChecker::Mode::ResolvingStyle) element->setChildrenAffectedByHover(); return element->hovered() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoClassHover); } void SelectorCodeGenerator::generateElementIsHovered(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { generateSpecialFailureInQuirksModeForActiveAndHoverIfNeeded(failureCases, fragment); if (m_selectorContext == SelectorContext::QuerySelector) { FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(elementIsHovered); functionCall.setOneArgument(elementAddressRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } else { if (shouldUseRenderStyleFromCheckingContext(fragment)) { LocalRegisterWithPreference checkingContext(m_registerAllocator, JSC::GPRInfo::argumentGPR1); Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext); addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::flagIsaffectedByHover()); notResolvingStyle.link(&m_assembler); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(elementIsHovered); functionCall.setOneArgument(elementAddressRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } else { Assembler::RegisterID checkingContext = m_registerAllocator.allocateRegisterWithPreference(JSC::GPRInfo::argumentGPR1); loadCheckingContext(checkingContext); m_registerAllocator.deallocateRegister(checkingContext); FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(elementIsHoveredForStyleResolution); functionCall.setTwoArguments(elementAddressRegister, checkingContext); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } } } void SelectorCodeGenerator::generateElementIsInLanguage(Assembler::JumpList& failureCases, const AtomicString& langFilter) { LocalRegisterWithPreference langFilterRegister(m_registerAllocator, JSC::GPRInfo::argumentGPR1); m_assembler.move(Assembler::TrustedImmPtr(langFilter.impl()), langFilterRegister); Assembler::RegisterID elementAddress = elementAddressRegister; FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(matchesLangPseudoClass); functionCall.setTwoArguments(elementAddress, langFilterRegister); failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero)); } 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); setNodeFlag(m_assembler, parentElement, Node::flagChildrenAffectedByLastChildRulesFlag()); m_registerAllocator.deallocateRegister(parentElement); // 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 (shouldUseRenderStyleFromCheckingContext(fragment)) addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::setLastChildStateFlags()); 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 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); setNodeFlag(m_assembler, parentElement, Node::flagChildrenAffectedByFirstChildRulesFlag() | Node::flagChildrenAffectedByLastChildRulesFlag()); m_registerAllocator.deallocateRegister(parentElement); // 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 (shouldUseRenderStyleFromCheckingContext(fragment)) addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::setFirstChildStateFlags() | RenderStyle::NonInheritedFlags::setLastChildStateFlags()); 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()) return; // Load the QualifiedNameImpl from the input. LocalRegister qualifiedNameImpl(m_registerAllocator); m_assembler.loadPtr(Assembler::Address(elementAddressRegister, Element::tagQNameMemoryOffset() + QualifiedName::implMemoryOffset()), qualifiedNameImpl); const AtomicString& selectorLocalName = nameToMatch.localName(); if (selectorLocalName != starAtom) { // Generate localName == element->localName(). LocalRegister constantRegister(m_registerAllocator); m_assembler.move(Assembler::TrustedImmPtr(selectorLocalName.impl()), constantRegister); failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(qualifiedNameImpl, QualifiedName::QualifiedNameImpl::localNameMemoryOffset()), constantRegister)); } const AtomicString& selectorNamespaceURI = nameToMatch.namespaceURI(); if (selectorNamespaceURI != starAtom) { // Generate namespaceURI == element->namespaceURI(). LocalRegister constantRegister(m_registerAllocator); m_assembler.move(Assembler::TrustedImmPtr(selectorNamespaceURI.impl()), constantRegister); failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(qualifiedNameImpl, QualifiedName::QualifiedNameImpl::namespaceMemoryOffset()), constantRegister)); } } void SelectorCodeGenerator::generateElementHasId(Assembler::JumpList& failureCases, const LocalRegister& elementDataAddress, const AtomicString& idToMatch) { // Compare the pointers of the AtomicStringImpl from idForStyleResolution with the reference idToMatch. LocalRegister idToMatchRegister(m_registerAllocator); m_assembler.move(Assembler::TrustedImmPtr(idToMatch.impl()), idToMatchRegister); failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(elementDataAddress, ElementData::idForStyleResolutionMemoryOffset()), idToMatchRegister)); } void SelectorCodeGenerator::generateElementHasClasses(Assembler::JumpList& failureCases, const LocalRegister& elementDataAddress, const Vector& classNames) { // Load m_classNames. LocalRegister spaceSplitStringData(m_registerAllocator); m_assembler.loadPtr(Assembler::Address(elementDataAddress, ElementData::classNamesMemoryOffset()), spaceSplitStringData); // If SpaceSplitString does not have a SpaceSplitStringData pointer, it is empty -> failure case. failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, spaceSplitStringData)); // We loop over the classes of SpaceSplitStringData for each class name we need to match. LocalRegister indexRegister(m_registerAllocator); for (unsigned i = 0; i < classNames.size(); ++i) { LocalRegister classNameToMatch(m_registerAllocator); m_assembler.move(Assembler::TrustedImmPtr(classNames[i]), classNameToMatch); m_assembler.move(Assembler::TrustedImm32(0), indexRegister); // Beginning of a loop over all the class name of element to find the one we are looking for. Assembler::Label loopStart(m_assembler.label()); // If the pointers match, proceed to the next matcher. Assembler::Jump classFound = m_assembler.branchPtr(Assembler::Equal, Assembler::BaseIndex(spaceSplitStringData, indexRegister, Assembler::timesPtr(), SpaceSplitStringData::tokensMemoryOffset()), classNameToMatch); // Increment the index. m_assembler.add32(Assembler::TrustedImm32(1), indexRegister); // If we reached the last element -> failure. failureCases.append(m_assembler.branch32(Assembler::Equal, Assembler::Address(spaceSplitStringData, SpaceSplitStringData::sizeMemoryOffset()), indexRegister)); // Otherwise just loop over. m_assembler.jump().linkTo(loopStart, &m_assembler); // Success case. classFound.link(&m_assembler); } } void SelectorCodeGenerator::generateElementIsLink(Assembler::JumpList& failureCases) { failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(elementAddressRegister, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsLink()))); } static void setElementChildIndex(Element* element, int index) { element->setChildIndex(index); } static void setElementChildIndexAndUpdateStyle(Element* element, int index) { element->setChildIndex(index); if (RenderStyle* childStyle = element->renderStyle()) childStyle->setUnique(); } void SelectorCodeGenerator::generateElementIsNthChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister(); generateWalkToParentElement(failureCases, parentElement); Vector, 32> validSubsetFilters; validSubsetFilters.reserveInitialCapacity(fragment.nthChildFilters.size()); for (const auto& slot : fragment.nthChildFilters) { int a = slot.first; int b = slot.second; // Anything modulo 1 is zero. Unless b restricts the range, this does not filter anything out. if (a == 1 && (!b || (b == 1))) continue; validSubsetFilters.uncheckedAppend(slot); } if (validSubsetFilters.isEmpty()) { m_registerAllocator.deallocateRegister(parentElement); return; } if (m_selectorContext == SelectorContext::QuerySelector) m_registerAllocator.deallocateRegister(parentElement); // Setup the counter at 1. LocalRegisterWithPreference elementCounter(m_registerAllocator, JSC::GPRInfo::argumentGPR1); m_assembler.move(Assembler::TrustedImm32(1), elementCounter); // Loop over the previous adjacent elements and increment the counter. { LocalRegister previousSibling(m_registerAllocator); m_assembler.move(elementAddressRegister, previousSibling); // Getting the child index is very efficient when it works. When there is no child index, // querying at every iteration is very inefficient. We solve this by only testing the child // index on the first direct adjacent. Assembler::JumpList noMoreSiblingsCases; Assembler::JumpList noCachedChildIndexCases; generateWalkToPreviousAdjacentElement(noMoreSiblingsCases, previousSibling); noCachedChildIndexCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(previousSibling, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagHasRareData()))); { LocalRegister elementRareData(m_registerAllocator); m_assembler.loadPtr(Assembler::Address(previousSibling, Node::rareDataMemoryOffset()), elementRareData); LocalRegister cachedChildIndex(m_registerAllocator); m_assembler.load16(Assembler::Address(elementRareData, ElementRareData::childIndexMemoryOffset()), cachedChildIndex); noCachedChildIndexCases.append(m_assembler.branchTest32(Assembler::Zero, cachedChildIndex)); m_assembler.add32(cachedChildIndex, elementCounter); noMoreSiblingsCases.append(m_assembler.jump()); } noCachedChildIndexCases.link(&m_assembler); m_assembler.add32(Assembler::TrustedImm32(1), elementCounter); Assembler::Label loopStart = m_assembler.label(); generateWalkToPreviousAdjacentElement(noMoreSiblingsCases, previousSibling); m_assembler.add32(Assembler::TrustedImm32(1), elementCounter); m_assembler.jump().linkTo(loopStart, &m_assembler); noMoreSiblingsCases.link(&m_assembler); } // Tree marking when doing style resolution. if (m_selectorContext != SelectorContext::QuerySelector) { 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(Element::setChildrenAffectedByForwardPositionalRules); functionCall.setOneArgument(parentElement); functionCall.call(); if (shouldUseRenderStyleFromCheckingContext(fragment)) { addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::flagIsUnique()); Assembler::RegisterID elementAddress = elementAddressRegister; FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(setElementChildIndex); functionCall.setTwoArguments(elementAddress, elementCounter); functionCall.call(); } else { Assembler::RegisterID elementAddress = elementAddressRegister; FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls); functionCall.setFunctionAddress(setElementChildIndexAndUpdateStyle); functionCall.setTwoArguments(elementAddress, elementCounter); functionCall.call(); } notResolvingStyle.link(&m_assembler); } // Test every the nth-child filter. for (const auto& slot : validSubsetFilters) { int a = slot.first; int b = slot.second; if (!a) failureCases.append(m_assembler.branch32(Assembler::NotEqual, Assembler::TrustedImm32(b), elementCounter)); else if (a > 0) { if (a == 2 && b == 1) { // This is the common case 2n+1 (or "odd"), we can test for odd values without doing the arithmetic. failureCases.append(m_assembler.branchTest32(Assembler::Zero, elementCounter, Assembler::TrustedImm32(1))); } else { if (b) failureCases.append(m_assembler.branchSub32(Assembler::Signed, Assembler::TrustedImm32(b), elementCounter)); moduloIsZero(failureCases, elementCounter, a); } } else { LocalRegister bRegister(m_registerAllocator); m_assembler.move(Assembler::TrustedImm32(b), bRegister); failureCases.append(m_assembler.branchSub32(Assembler::Signed, elementCounter, bRegister)); moduloIsZero(failureCases, bRegister, a); } } } void SelectorCodeGenerator::generateElementMatchesNotPseudoClass(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { for (const auto& subFragment : fragment.notFilters) { Assembler::JumpList localFailureCases; generateElementMatching(localFailureCases, localFailureCases, subFragment); // Since this is a not pseudo class filter, reaching here is a failure. failureCases.append(m_assembler.jump()); localFailureCases.link(&m_assembler); } } void SelectorCodeGenerator::generateElementMatchesAnyPseudoClass(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { for (const auto& subFragments : fragment.anyFilters) { RELEASE_ASSERT(!subFragments.isEmpty()); // Don't handle the last fragment in this loop. Assembler::JumpList successCases; for (unsigned i = 0; i < subFragments.size() - 1; ++i) { Assembler::JumpList localFailureCases; generateElementMatching(localFailureCases, localFailureCases, subFragments[i]); successCases.append(m_assembler.jump()); localFailureCases.link(&m_assembler); } // At the last fragment, optimize the failure jump to jump to the non-local failure directly. generateElementMatching(failureCases, failureCases, subFragments.last()); successCases.link(&m_assembler); } } void SelectorCodeGenerator::generateElementHasPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { if (!shouldUseRenderStyleFromCheckingContext(fragment)) { LocalRegister checkingContext(m_registerAllocator); loadCheckingContext(checkingContext); failureCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::ResolvingStyle, checkingContext)); } } void SelectorCodeGenerator::generateRequestedPseudoElementEqualsToSelectorPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment& fragment, Assembler::RegisterID checkingContext) { // Make sure that the requested pseudoId equals to the pseudo element of the rightmost fragment. // If the rightmost fragment doesn't have a pseudo element, the requested pseudoId need to be NOPSEUDO to succeed the matching. // Otherwise, if the requested pseudoId is not NOPSEUDO, the requested pseudoId need to equal to the pseudo element of the rightmost fragment. if (shouldUseRenderStyleFromCheckingContext(fragment)) { if (!fragment.pseudoElementSelector) failureCases.append(m_assembler.branch8(Assembler::NotEqual, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(NOPSEUDO))); else { Assembler::Jump skip = m_assembler.branch8(Assembler::Equal, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(NOPSEUDO)); // When mode is StyleInvalidation, the pseudo element of the rightmost fragment is treated as NOPSEUDO. // Since the requested pseudoId is not NOPSEUDO here, it becomes failure. failureCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::StyleInvalidation, checkingContext)); failureCases.append(m_assembler.branch8(Assembler::NotEqual, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(CSSSelector::pseudoId(fragment.pseudoElementSelector->pseudoElementType())))); skip.link(&m_assembler); } } } void SelectorCodeGenerator::generateElementIsRoot(Assembler::JumpList& failureCases) { LocalRegister document(m_registerAllocator); getDocument(m_assembler, elementAddressRegister, document); failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(document, Document::documentElementMemoryOffset()), elementAddressRegister)); } void SelectorCodeGenerator::generateElementIsTarget(Assembler::JumpList& failureCases) { LocalRegister document(m_registerAllocator); getDocument(m_assembler, elementAddressRegister, document); failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(document, Document::cssTargetMemoryOffset()), elementAddressRegister)); } void SelectorCodeGenerator::generateMarkPseudoStyleForPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment& fragment) { LocalRegister checkingContext(m_registerAllocator); loadCheckingContext(checkingContext); // When fragment doesn't have a pseudo element, there's no need to mark the pseudo element style. if (!fragment.pseudoElementSelector) return; Assembler::JumpList successCases; // When the requested pseudoId isn't NOPSEUDO, there's no need to mark the pseudo element style. successCases.append(m_assembler.branch8(Assembler::NotEqual, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(NOPSEUDO))); // When resolving mode is SharingRules or StyleInvalidation, there's no need to mark the pseudo element style. successCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::SharingRules, checkingContext)); successCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::StyleInvalidation, checkingContext)); // When resolving mode is ResolvingStyle, mark the pseudo style for pseudo element. PseudoId dynamicPseudo = CSSSelector::pseudoId(fragment.pseudoElementSelector->pseudoElementType()); if (dynamicPseudo < FIRST_INTERNAL_PSEUDOID) { failureCases.append(branchOnResolvingModeWithCheckingContext(Assembler::NotEqual, SelectorChecker::Mode::ResolvingStyle, checkingContext)); addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::flagPseudoStyle(dynamicPseudo)); } // When resolving mode is not SharingRules or StyleInvalidation (In this case, ResolvingStyle or CollectingRules), // the checker including pseudo elements needs to fail for the matching request. failureCases.append(m_assembler.jump()); successCases.link(&m_assembler); } }; // namespace SelectorCompiler. }; // namespace WebCore. #endif // ENABLE(CSS_SELECTOR_JIT)