Hi everyone,

I've been discussing this idea with a few people in person over the past week, and nobody told me it was completely insane. ;) So I thought I'd send this idea around.

By now many people have heard of WebKit's CSS JIT. It's a surprisingly small amount of code. One of the issues that they cite in their blog post [1] is compilation time: the compilation has to be very fast in order to avoid nullifying the gains. With that in mind, I had an idea: what if the "AST" that the CSS parser generated was actually machine code?

The AST for the CSS grammar is extremely small: 176 lines of Rust for the whole thing [2], and the selector grammar is just a fraction of that. This is because selectors describe a very simple language, even with all the extensions that have been added over the years. So one could write a simple template-emitting compiler that takes CSS selectors and writes a small template of machine code. (This is in fact the approach that WebKit uses.) For example, I could imagine that the JIT code for the selector ".foo #a" might look like (assuming that "#a" has already been matched in the ID hash):

        ; assume that node is in ebx
    l1: mov ebx,[ebx+offsetof(parentNode)]
        test ebx,ebx
        jz nomatch
        mov edx,[ebx+offsetof(class)]
        xor ecx,ecx
    l2: cmp [edx+ecx*4],"foo"
        je match
        inc ecx
        cmp ecx,[ebx+offsetof(classcount)]
        jne l2
        jmp l1

This is just 29 bytes of code when assembled. This is likely larger than the equivalent `nsRuleNode`, but `nsRuleNode` instances are not a large amount of the heap in my cursory glances at `about:memory`.

The major question is: If you don't have an AST, then how do you implement the CSSOM and style debugging? Here's the trick: Since CSS selectors are so simple compared to, say, the output of a JS JIT, it should be possible to *disassemble* the code by simply pattern-matching. Since the compiler is nothing more than a very simple template processor (and it has to be, for speed reasons) it should be possible to reverse the output and go from JIT code back to the CSS selectors. This would be done by performing some very simple analysis (not full disassembly) on the machine code. For instance, on the above selector, it might do:

        ; start with `#a` from the rule hash

    l1: mov ebx,[ebx+offsetof(parentNode)]
        test ebx,ebx
        jz nomatch
        ; found a test for parentNode, so we must be a descendant or
        ; child selector (depending on whether we see a backwards jump)

        mov edx,[ebx+offsetof(class)]
        xor ecx,ecx
    l2: cmp [edx+ecx*4],"foo"
        je match
        inc ecx
        cmp ecx,[ebx+offsetof(classcount)]
        jne l2
        ; found a test for the "foo" class

        jmp l1
        ; found a backwards jump, so we know we're a descendant selector
        ; result: `.foo #a`

So you wouldn't need a full disassembler, just a simple pattern matcher.

Note that one doesn't have to JIT everything: for more complex tests such as "attribute contains substring", we can just call out to functions implemented in Rust in the JIT code. The disassembler can just pattern match on those function addresses and arguments. This also increases the maintainability of this approach in the presence of new stuff that the CSS working groups add to the spec: we can implement all new features as functions at first, and only JIT them if they become widely used.

Thoughts?

Patrick

[1]: https://www.webkit.org/blog/3271/webkit-css-selector-jit-compiler/

[2]: https://github.com/mozilla-servo/rust-cssparser/blob/master/ast.rs
_______________________________________________
dev-servo mailing list
dev-servo@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-servo

Reply via email to