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.