Bug 135733 - CSS JIT: support :scope
Summary: CSS JIT: support :scope
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: CSS (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Yusuke Suzuki
URL:
Keywords:
Depends on:
Blocks: 135255
  Show dependency treegraph
 
Reported: 2014-08-07 15:55 PDT by Yusuke Suzuki
Modified: 2014-08-11 13:24 PDT (History)
2 users (show)

See Also:


Attachments
Patch (23.44 KB, patch)
2014-08-07 16:47 PDT, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (21.65 KB, patch)
2014-08-07 18:37 PDT, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (36.99 KB, patch)
2014-08-08 16:41 PDT, Yusuke Suzuki
benjamin: review+
benjamin: commit-queue-
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Yusuke Suzuki 2014-08-07 15:55:38 PDT
Supporting :scope. It makes CheckingContext more similar to SelectorChecker::SelectorCheckingContext.
Comment 1 Yusuke Suzuki 2014-08-07 16:47:32 PDT
Created attachment 236240 [details]
Patch
Comment 2 Yusuke Suzuki 2014-08-07 16:51:21 PDT
Comment on attachment 236240 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=236240&action=review

Added comments.

> Source/WebCore/cssjit/SelectorCompiler.cpp:528
> +        return FunctionType::SelectorCheckerWithCheckingContext;

When the context is QuerySelecor, it requires CheckingContext. Otherwise, :scope works as :root since context.scope is nullptr.

> Source/WebCore/cssjit/SelectorCompiler.cpp:-1629
> -    RELEASE_ASSERT(m_selectorContext == SelectorContext::RuleCollector);

Now, QueryingRules can accept CheckingContext.

> Source/WebCore/dom/SelectorQuery.cpp:384
> +    checkingContext.scope = rootNode.isDocumentNode() ? nullptr : &rootNode;

searchRootNode may be filtered with id. So taking rootNode additionally and set it to context.scope.
Comment 3 Yusuke Suzuki 2014-08-07 17:01:55 PDT
Ah, since :scope process is very small, CSS JIT can inline it. I'll submit the patch.
Comment 4 Yusuke Suzuki 2014-08-07 18:37:03 PDT
Created attachment 236252 [details]
Patch
Comment 5 Benjamin Poulain 2014-08-08 14:54:56 PDT
Comment on attachment 236252 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=236252&action=review

Great patch. Some nitpick below:

> Source/WebCore/ChangeLog:17
> +        * cssjit/SelectorCompiler.cpp:

addFlagsToElementStyleFromContext() should get an ASSERT(m_selectorContext == SelectorContext::QuerySelector).

Maybe shouldUseRenderStyleFromCheckingContext() could get one too. It is not really incorrect to call that for QuerySelector but that is suspicious if that happen. You would need to refactor some test functions.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2971
> +    RELEASE_ASSERT(m_selectorContext == SelectorContext::QuerySelector);

This could be a regular assertion. Your code below handle the null case correctly if that were to happen.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2985
> +    Assembler::Jump successCase;
> +    {
> +        LocalRegister checkingContext(m_registerAllocator);
> +        loadCheckingContext(checkingContext);
> +
> +        Assembler::Jump scopeIsNull = m_assembler.branchTestPtr(Assembler::Zero, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, scope)));
> +        successCase = m_assembler.branchPtr(Assembler::Equal, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, scope)), elementAddressRegister);
> +        failureCases.append(m_assembler.jump());
> +
> +        scopeIsNull.link(&m_assembler);
> +    }
> +    generateElementIsRoot(failureCases);
> +    successCase.link(&m_assembler);

Alternative idea to write this with fewer jumps:

1) Reserve register "scope".
2) Load "checkingContext, OBJECT_OFFSETOF(CheckingContext, scope)" into "scope".
3) Check for nullity, if null, jump to [5]
4) Load the root in scope with getDocument().
5) Compare scope and elementAddressRegister, fail if they are not equal.

Please put the update for review if you do this.

> Source/WebCore/cssjit/SelectorCompiler.cpp:3002
>      // When fragment doesn't have a pseudo element, there's no need to mark the pseudo element style.
>      if (!fragment.pseudoElementSelector)
>          return;

Ooops, loadCheckingContext() should have been after that if() :)

> LayoutTests/ChangeLog:13
> +        * fast/selectors/querySelector-scope-filtered-root.html: Added.

Good idea to test that!
Comment 6 Yusuke Suzuki 2014-08-08 16:37:32 PDT
Comment on attachment 236252 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=236252&action=review

Thank you for your great review!

>> Source/WebCore/ChangeLog:17
>> +        * cssjit/SelectorCompiler.cpp:
> 
> addFlagsToElementStyleFromContext() should get an ASSERT(m_selectorContext == SelectorContext::QuerySelector).
> 
> Maybe shouldUseRenderStyleFromCheckingContext() could get one too. It is not really incorrect to call that for QuerySelector but that is suspicious if that happen. You would need to refactor some test functions.

Seeing the implementation, threre's a lot of `if (m_selectorContext == SelectorChecker::QuerySelector)` check on the function entry of the test functions. As the result, the content of the function is splitted into 2.
In this time, I've refactored them a little(1); Making the function's structure consistent like,

if (m_selectorContext == SelectorChecker::QuerySelector) {
   .... // content for QuerySelector
   return;    // return early
}
ASSERT(m_selectorContext == SelectorChecker::QuerySelector);
...  // content for RuleCollector


And inserting assertions.


But I think there's the more aggressive way(2) to do this;
Pushing selectorContext value into SelectorCodeGenerator's template variable (SelectorCodeGenerator<SelectorContext>) and split these test functions to `SelectorCodeGenerator<QuerySelector>::generateXXXTest` and `SelectorCodeGenerator<RuleCollector>::generateXXXTest`.
This can guarantee that SelectorContext is QuerySelector (vice versa) statically by the compiler.
Maybe this is a little bit radical change, in the following patch, I took (1). But if (2) is preferable, I'll open the new bug and fix it :)
What do you think about the plan (2)?

>> Source/WebCore/cssjit/SelectorCompiler.cpp:2971
>> +    RELEASE_ASSERT(m_selectorContext == SelectorContext::QuerySelector);
> 
> This could be a regular assertion. Your code below handle the null case correctly if that were to happen.

Fixed!

>> Source/WebCore/cssjit/SelectorCompiler.cpp:2985
>> +    successCase.link(&m_assembler);
> 
> Alternative idea to write this with fewer jumps:
> 
> 1) Reserve register "scope".
> 2) Load "checkingContext, OBJECT_OFFSETOF(CheckingContext, scope)" into "scope".
> 3) Check for nullity, if null, jump to [5]
> 4) Load the root in scope with getDocument().
> 5) Compare scope and elementAddressRegister, fail if they are not equal.
> 
> Please put the update for review if you do this.

That's quite nice! I'll update and upload the patch for your review :)

>> Source/WebCore/cssjit/SelectorCompiler.cpp:3002
>>          return;
> 
> Ooops, loadCheckingContext() should have been after that if() :)

Yeah!
Comment 7 Yusuke Suzuki 2014-08-08 16:41:23 PDT
Created attachment 236323 [details]
Patch
Comment 8 Yusuke Suzuki 2014-08-08 16:45:49 PDT
Comment on attachment 236323 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=236323&action=review

> Source/WebCore/cssjit/SelectorCompiler.cpp:1345
> +    if (m_selectorContext != SelectorContext::QuerySelector && m_functionType == FunctionType::SelectorCheckerWithCheckingContext) {

Since it is clear that the pseudo element has no effect when the context is QuerySelector, I wrote this guard here.
Is it preferable to pushing this guard into `generateRequestedPseudoElementEqualsToSelectorPseudoElement`?

> Source/WebCore/cssjit/SelectorCompiler.cpp:1592
> +    ASSERT(m_selectorContext != SelectorContext::QuerySelector);

Now `addFlagsToElementStyleFromContext` is called after ASSERT(m_selectorContext != SelectorContext::QuerySelector); assertion is passed in the caller side :)
Comment 9 Yusuke Suzuki 2014-08-08 16:46:51 PDT
Comment on attachment 236323 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=236323&action=review

> Source/WebCore/cssjit/SelectorCompiler.cpp:3005
> +    failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, scope, elementAddressRegister));

Quite simplified :)
Comment 10 Benjamin Poulain 2014-08-10 16:00:43 PDT
(In reply to comment #6)
> But I think there's the more aggressive way(2) to do this;
> Pushing selectorContext value into SelectorCodeGenerator's template variable (SelectorCodeGenerator<SelectorContext>) and split these test functions to `SelectorCodeGenerator<QuerySelector>::generateXXXTest` and `SelectorCodeGenerator<RuleCollector>::generateXXXTest`.
> This can guarantee that SelectorContext is QuerySelector (vice versa) statically by the compiler.
> Maybe this is a little bit radical change, in the following patch, I took (1). But if (2) is preferable, I'll open the new bug and fix it :)
> What do you think about the plan (2)?

That seems reasonable but I am afraid this would hurt binary size if the whole compiler becomes a template. ARM CPUs have a tiny Instruction Cache and increasing the binary size tend to hurt a lot. In this code, branch performance is not a bottleneck, we cannot justify increases in binary size.
Comment 11 Benjamin Poulain 2014-08-10 16:25:22 PDT
Comment on attachment 236323 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=236323&action=review

Brilliant work as usual. Some minor comments below.

>> Source/WebCore/cssjit/SelectorCompiler.cpp:1345
>> +    if (m_selectorContext != SelectorContext::QuerySelector && m_functionType == FunctionType::SelectorCheckerWithCheckingContext) {
> 
> Since it is clear that the pseudo element has no effect when the context is QuerySelector, I wrote this guard here.
> Is it preferable to pushing this guard into `generateRequestedPseudoElementEqualsToSelectorPseudoElement`?

IMHO, this code is clear with this check...

> Source/WebCore/cssjit/SelectorCompiler.cpp:1398
> +    if (m_selectorContext != SelectorContext::QuerySelector && m_functionType == FunctionType::SelectorCheckerWithCheckingContext) {

...and it is consistent with the counterpart here.

> Source/WebCore/cssjit/SelectorCompiler.cpp:1642
> +    ASSERT(m_functionType == FunctionType::SelectorCheckerWithCheckingContext);

A RELEASE_ASSERT() could be better here to guard any invalid access to m_checkingContextStackReference.

> Source/WebCore/cssjit/SelectorCompiler.cpp:1827
> +    ASSERT(m_selectorContext != SelectorContext::QuerySelector);
> +

In WebKit we generally avoid ASSERT that repeat a early return.

What you can do is use ASSERT_WITH_MESSAGE to explain the early return.
E.g.: ASSERT_WITH_MESSAGE(m_selectorContext != SelectorContext::QuerySelector, "We should never mark the tree from QuerySelector because that would cause useless style invalidation.")

...or you can remove this assertion.

> Source/WebCore/cssjit/SelectorCompiler.cpp:1848
> +    ASSERT(m_selectorContext != SelectorContext::QuerySelector);
> +

ditto.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2444
> +    ASSERT(m_selectorContext != SelectorContext::QuerySelector);

ditto.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2446
> +    if (shouldUseRenderStyleFromCheckingContext(m_selectorContext, fragment)) {

This actually looks quite a bit better with your early return.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2479
> +    ASSERT(m_selectorContext != SelectorContext::QuerySelector);

Same comment as above regarding the assertion.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2601
> +    ASSERT(m_selectorContext != SelectorContext::QuerySelector);

ditto.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2673
> +    ASSERT(m_selectorContext != SelectorContext::QuerySelector);

ditto.

> Source/WebCore/cssjit/SelectorCompiler.cpp:2957
> +    ASSERT_WITH_MESSAGE(m_selectorContext != SelectorContext::QuerySelector, "When the fragment has pseudo element, the selector becomres CannotMatchAnything for QuerySelector and this test function is not called.");

Typo: "becomres"
Comment 12 Yusuke Suzuki 2014-08-11 12:28:29 PDT
Comment on attachment 236323 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=236323&action=review

Thank you for your review! So I'll land it manually with changes you pointed :)

>>> Source/WebCore/cssjit/SelectorCompiler.cpp:1345
>>> +    if (m_selectorContext != SelectorContext::QuerySelector && m_functionType == FunctionType::SelectorCheckerWithCheckingContext) {
>> 
>> Since it is clear that the pseudo element has no effect when the context is QuerySelector, I wrote this guard here.
>> Is it preferable to pushing this guard into `generateRequestedPseudoElementEqualsToSelectorPseudoElement`?
> 
> IMHO, this code is clear with this check...

OK, so I'll use this form :)

>> Source/WebCore/cssjit/SelectorCompiler.cpp:1642
>> +    ASSERT(m_functionType == FunctionType::SelectorCheckerWithCheckingContext);
> 
> A RELEASE_ASSERT() could be better here to guard any invalid access to m_checkingContextStackReference.

Nice, I've changed it to RELEASE_ASSERT.

>> Source/WebCore/cssjit/SelectorCompiler.cpp:1827
>> +
> 
> In WebKit we generally avoid ASSERT that repeat a early return.
> 
> What you can do is use ASSERT_WITH_MESSAGE to explain the early return.
> E.g.: ASSERT_WITH_MESSAGE(m_selectorContext != SelectorContext::QuerySelector, "We should never mark the tree from QuerySelector because that would cause useless style invalidation.")
> 
> ...or you can remove this assertion.

Thank you for your pointing. So in this time, I've simply dropped it :)
Comment 13 Yusuke Suzuki 2014-08-11 13:24:54 PDT
Committed r172408: <http://trac.webkit.org/changeset/172408>