Skip to content
brson edited this page Dec 5, 2012 · 65 revisions

This is a very high-level overview of Servo's design. Servo remains an early prototype and portions of the design are not yet represented in the actual code. Some important aspects of the system have not been thought through in detail.

Overview and goals

Servo is a research project to develop a new web browser engine. Our goal is to create an architecture that takes advantage of parallelism at many levels while eliminating common sources of security vulnerabilities associated with incorrect memory management.

To those ends Servo is written in Rust, a new language designed specifically with Servo's requirements in mind. Rust provides a strong type system, task-based parallelism, and data-race freedom.

Servo is explicitly not aiming to create a full web browser - it is focused on just the engine, not the chrome.

Strategies for parallelism

Some ideas we are exploring or plan to.

  • Task-based architecture - major components in the system should be factored into actors with isolated heaps. This alone will provide some incidental parallelism, but more importantly will force the system to be decomposed types and interfaces that are amenable to future parallel research. (implemented)
  • Copy-on-write DOM - in Servo, the DOM is a versioned data structure that is shared between the content (JS) task and the layout task, allowing layout to run even while scripts are reading and writing DOM nodes. (implemented)
  • Parallel rendering - Rendering is decoupled from layout, has its own thread, no GC pauses, maintains high frame rate (implemented)
  • Tiled rendering - Divide the screen into a grid of tiles and render each one in parallel. Needed for mobile performance regardless of its benefits for parallelism. (implemented)
  • Layered rendering - Divide the display list into subtrees that are transformed independently of each other (think moving sprites), either automatically or not, and render them in parallel.
  • Selector matching - embarrassingly parallel. Unlike gecko, Servo will will do selector matching in a separate pass from layout so that it may be more easily parallelized.
  • Parallel layout - this is a very hard problem. Will defer to future. Meyerovich has done lots of interesting research here.
  • Text shaping - a more limited attack at parallel layout. Text shaping is fairly costly and can be somewhat independent of layout.
  • Parsing - various things. JS parsing most promising, but requires SpiderMonkey changes. HTML could be beneficial. CSS probably easiest but least beneficial. May not be a win because of speculation.
  • Image decoding - (implemented)
  • Decoding of other resources - less important probably than image decoding, but anything that needs to be loaded by a page can be done in parallel, e.g. parsing entire style sheets.

Challenges

  • Parallel data structures - maintaining good straight-line performance, making complex shared types type-safe
  • Language immaturity - crashy Rust compiler, no libraries, changing language
  • Thread-hostile libraries - some 3rd party libraries we need don't play well in multi-threaded environments. fonts in particular have been difficult. sometimes there are big global locks that destroy parallelism.

The task architecture

Each engine instance can for now be thought of as a single tab or window, and manages a pipeline of tasks that accepts input, runs JavaScript against the DOM, performs layout, builds display lists, renders display lists to tiles and finally composites the final image to a surface.

The pipeline consists of three main tasks:

  • Content - Content's primary mission is to create and own the DOM and execute the JavaScript engine. It receives events from multiple sources, including navigation events, and routes them as necessary. When the content task needs to query information about layout it must send a request to the layout task.
  • Layout - Layout takes a snapshot of the DOM, calculates styles, and constructs the main layout data structure, the flow tree. The flow tree is used to calculate the layout of nodes and from there build a display list, which is sent to the render task.
  • Renderer - The renderer receives a display list and renders visible portions to one or more tiles, possibly in parallel, and composites them to a surface for the application to display.

Two complex data structures are involved in multi-task communication in this pipeline, the DOM and the display list. The DOM is communicated from content to layout and the display list from layout to the renderer. Figuring out an efficient and type-safe way to represent, share, and/or transmit these two structures is one of the major challenges for the project.

The COW DOM

Servo's DOM is a tree with versioned nodes that may be shared between a single writer and many readers. The DOM uses a copy-on-write strategy to allow the writer to modify the DOM in parallel with readers. The writer is always the content task and the readers are always layout tasks or subtasks thereof.

DOM nodes are Rust values whose lifetimes are managed by the JavaScript garbage collector. JavaScript accesses DOM nodes directly, as JSNatives - there is no XPCOM.

The interface to the DOM is not currently type safe - it is possible to manage nodes incorrectly and end up dereferencing bogus pointers. Eliminating this unsafety is a high priority, but as DOM nodes have a necessarily complex lifecycle it will be difficult.

The display list

Servo's rendering is entirely driven by a display list, a sequence of high-level drawing commands output by the layout task. Servo's display list is deeply immutable so that it make be shared by parallel renderers and is entirely self-contained. This is in contrast to WebKit's renderer, which does not use a display list, and Gecko's, which uses a display list, but also consults additional information, some directly from the DOM.

Multi-process architecture

Pretty much like chrome, but we don't intend

I/O and resource management

Web pages depend on a wide variety of external resources, with many mechanisms of retrieval and decoding. These resources get cached at multiple levels - on disk, in memory, in decoded form, distributed between parallel workers.

Traditionally, browsers have been single-threaded, performing I/O on the 'main' thread, where the computation happens. This leads to latency problems. In Servo there is no 'main' thread and the loading of all external resources is handled by a single resource manager task.

Browsers have a lot of caches and with Servo's task architecture it will probably have more than most (e.g. you might have both a global task-based cache and a task-local cache that stores results from the global cache to save the round trip through the scheduler). Servo should have a unified caching story, with caches that can communicate about low-memory situations, among other things.

Clone this wiki locally
You can’t perform that action at this time.