# Operation of the Renderer

The Renderer process' main job is to convert HTML, CSS and JavaScript into a web page that the user can interact with.

## Parsing - DOM Construction

When the renderer process starts to receive HTML data, it begins to build up an internal representation of the page by turning it into a **D**ocument **O**bject **M**odel (DOM). The browser also exposes the data structure and API that the web developer can interact with via JavaScript.

### Subresource Loading

At the same time as DOM parsing, a "preload scanner" runs concurrently and sends network requests for any referenced resources. Classic examples include the `href` attributes inside `<link>` tags or the `src` attributes into `<script>` or `<img>` tags.

When the HTML parser reaches a `<script>` tag, it has to pause parsing to load, parse ande execute the JavaScript. This is because JavaScript can change the layout of the DOM using `document.write` or similar functionality, which can change the DOM structure.

## JavaScript Execution

JavaScript gets parsed and then turned into an [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (AST). The AST is then used by the **interpreter** to generate **bytecode**, which is then run. At this point, the engine is running JavaScript code.

To make the JavaScript run faster, the bytecode can be sent to an **optimizing compiler**, which makes certain assumptions based on the **profiling data** it has. It then uses these assumptions to produce highly-optimized machine code. If one of the assumptions ends up being incorrect, the optimizing compiler **deoptimizes** and goes back to execution via the interpreter.

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2Fb4S1Gcr2pSU33dUDttqs%2Fjs-engine-pipeline.svg?alt=media&#x26;token=29002928-27bf-476d-a07c-7ca6bf685b36" alt=""><figcaption><p>Credit: Mathias Bynens at <a href="https://mathiasbynens.be/notes/shapes-ics">https://mathiasbynens.be/notes/shapes-ics</a></p></figcaption></figure>

Based on the JavaScript engine in mind, this execution box at the bottom can vary in appearance. Until 2021, V8 looked exactly like it:

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2FJ4yufnYT4o4eDFnWobZU%2Finterpreter-optimizing-compiler-v8.svg?alt=media&#x26;token=24471001-c959-4c3a-b936-c9d27ab14679" alt=""><figcaption><p>Credit: Mathias Bynens at <a href="https://mathiasbynens.be/notes/shapes-ics">https://mathiasbynens.be/notes/shapes-ics</a></p></figcaption></figure>

The JavaScript interpreter in V8 is called **Ignition**, and the optimizing compiler is [**Turbofan**](https://ir0nstone.gitbook.io/notes/binexp/browser-exploitation/an-introduction-to-turbofan) - we'll be meeting it shortly!

{% hint style="info" %}
As of 2021, V8 has another **non-optimizing** compiler called [Sparkplug](https://v8.dev/blog/sparkplug) in the engine. Sparkplug is designed to convert bytecode to machine code blazingly fast, for a slight speedup.\
As of 2023, V8 added the [Maglev](https://v8.dev/blog/maglev) optimizing compiler that sits between Sparkplug and Turbofan.
{% endhint %}

Firefox's SpiderMonkey engine has two compilers, one more optimized and powerful than the other:

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2F8iv1p5g7JkKxH17xMsyV%2Finterpreter-optimizing-compiler-spidermonkey.svg?alt=media&#x26;token=192acb6c-8658-4750-a75d-83c14a4619b1" alt=""><figcaption><p>Credit: Mathias Bynens at <a href="https://mathiasbynens.be/notes/shapes-ics">https://mathiasbynens.be/notes/shapes-ics</a></p></figcaption></figure>

Meanwhile the JavaScriptCore engine, used by Apple's WebKit, has **three** optimizing compilers, each providing varying levels of optimization:

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2F9TbqgUSttPNt4m2Zl4Vm%2Finterpreter-optimizing-compiler-jsc.svg?alt=media&#x26;token=b8ff1695-2b13-45a8-af33-2353639dcbf9" alt=""><figcaption><p>Credit: Mathias Bynens at <a href="https://mathiasbynens.be/notes/shapes-ics">https://mathiasbynens.be/notes/shapes-ics</a></p></figcaption></figure>

To understand what we could use multiple compilers for, we can take a look at the [Webkit documentation](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html), which describes when each stage kicks in:

* **Baseline JIT** kicks in for functions that are invoked at least 6 times, or take a loop at least 100 times (or some combination - like 3 invocations with 50 loop iterations total)
* **DFG JIT** kicks in for functions that are invoked at least 60 times, or that took a loop at least 1000 times
* **FTL JIT** kicks in for functions that are invoked thousands of times, or loop tens of thousands of times

The documentation is very clear that these numbers are approximate and based on additional heuristics. Both LLInt and Baseline collect profiling information, so that should function usage reach **DFG** levels, it has sufficient informaton to perform **speculation**. The DFG and FTL can both deoptimze back to Baseline if assumptions are broken. Check out the [Introduction to Turbofan](https://ir0nstone.gitbook.io/notes/binexp/browser-exploitation/an-introduction-to-turbofan) for more talk on speculation.

Ultimately, different optimiser are used depending on how **hot** a function is - how many times and how often it is called. Pre-2021 V8 would reach a certain point in execution in Ignition before it loaded up the Turbofan optimizer on a different thread:

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2FExrz2hm2w1DdWmFONvjo%2Fpipeline-detail-v8.svg?alt=media&#x26;token=5162b941-83b3-417f-a680-7acdac9ca4ad" alt=""><figcaption><p>Credit: Mathias Bynens at <a href="https://mathiasbynens.be/notes/prototypes">https://mathiasbynens.be/notes/prototypes</a></p></figcaption></figure>

JavaScriptCore has a similar operation, but three times:

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2F56AWYyS1xbuvLhWkG10Q%2Fpipeline-detail-javascriptcore.svg?alt=media&#x26;token=3038bcb7-a4cc-4817-82ce-675a6069f00c" alt=""><figcaption><p>Credit: Mathias Bynens at <a href="https://mathiasbynens.be/notes/prototypes">https://mathiasbynens.be/notes/prototypes</a></p></figcaption></figure>

Why do some engines have different levels of optimization? [Mathias Bynens](https://mathiasbynens.be/notes/prototypes) explains it like this:

> The reason JavaScript engines have different optimization tiers is because of a fundamental trade-off between *generating code quickly* like with an interpreter, or *generating quick code* with an optimizing compiler. It’s a scale, and adding more optimization tiers allows you to make more fine-grained decisions at the cost of additional complexity and overhead. In addition, there’s a trade-off between the optimization level and the memory usage of the generated code. This is why JavaScript engines try to optimize only **hot** functions.
