November 14, 2017·6 min read·engineeringrustconcurrencybrowsersownership

Stylo brings parallel style computation into Firefox Quantum

Firefox ships a Rust style engine that shares immutable style data and computes child styles in parallel. Clone-on-write and scoped borrowing explain how its concurrency model works and where the C++ boundary still needs care.

Computing the style of a web page has an ordering constraint, but it does not require every element to wait for every other element. A parent style must be known before its children can be computed. Once that parent result exists, the children can be processed independently. Stylo uses that structure to parallelize a substantial part of browser work, and it now ships in Firefox Quantum.

Manish Goregaokar describes it in his November 14 account, identifying Stylo as the first major Servo component integrated into Firefox. It reports replacing approximately 160,000 lines of C++ with 85,000 lines of Rust. Those figures describe this component migration. They do not mean the browser has become an all-Rust application or that line-count reduction explains its performance.

I find this a persuasive use of Rust because the concurrency design depends on the ownership model in ordinary operation. The value is more concrete than a general claim that a safer language produces faster code. Immutable access and controlled mutation make a particular parallel algorithm easier to implement without data races.

The tree supplies independent work

The announcement describes styling as parsing CSS, deciding which rules apply and their precedence, then computing the final style of each element while traversing the DOM. Parent results affect children through the cascade and inherited values. The dependency has a top-down shape.

This produces useful units of parallel work after a parent has been processed. Different children can use the established parent style without modifying it. Their own style results can be calculated independently, subject to the relationships the style system represents.

Stylo uses Rayon to drive a work-stealing cascade algorithm. The announcement connects that mechanism to the tree structure, rather than presenting parallelism as simply running the same loop on several threads. The scheduling needs ready work whose inputs are valid, and the ownership rules need to keep those inputs safe while workers use them.

The article reports a thirty-percent page-load improvement for the Amazon homepage. That is a stated example for one page, not a universal browser speedup. It also reports two earlier unsuccessful attempts to parallelize the Firefox style system in C++. The practical significance is that this implementation has reached the shipping browser after those difficulties.

Style sharing avoids repeated data

Computed properties are grouped into style structures, such as a group for font properties and another for backgrounds. Many elements can share the same group. A child may inherit a font, and siblings may have matching styles even when those styles differ from their parent.

Copying every group for every element would discard that opportunity to share. The article describes atomically reference-counted ownership for these structures. Several elements can retain references to the same immutable contents, with ownership keeping the contents alive while those references exist.

The versioned style-builder source makes the borrowing relationship visible. It stores the inherited style as an immutable reference to computed values. Its comments describe retaining references to parent or default values without cloning them until the style is built or a value needs mutation.

That distinction matters to the cost model. Borrowing an established input is different from allocating a fresh owned style for every step. The builder can defer ownership work until the output actually requires it. The type of access also limits what code computing the child can do to the parent input.

Mutation first establishes unique ownership

Shared style data eventually needs a path for changes. If a child chooses another font size, it must not modify the same shared structure other elements still use. The account describes a clone-on-write operation wrapped by a font-mutation helper.

When the style structure has one owner, the helper can mutate its contents in place. When other owners still share it, the operation obtains a separate copy and changes that copy. The other elements retain the original contents. This preserves sharing where values remain the same and pays for a copy when a new result diverges.

The Rust 1.21 Arc source demonstrates the standard-library form of that pattern through Arc::make_mut. A small example shows the distinction between cloning a pointer and cloning the underlying value.

use std::sync::Arc;

fn main() {
    let mut child = Arc::new(12u32);
    let parent = Arc::clone(&child);
    *Arc::make_mut(&mut child) = 14;
    assert_eq!(*parent, 12);
    assert_eq!(*child, 14);
}

This is an illustration of the ownership operation, not extracted browser code. Cloning the Arc creates another owner of the same allocation. Calling make_mut with that other owner present makes the contents unique before returning a mutable reference. The assertions show why changing the child value does not alter the value observed through the parent pointer.

The browser article names the actual font mutation as a thin wrapper around this form of operation. The important invariant is the same. A mutable reference is obtained only after the implementation has established an owned value that can be changed without modifying the shared input of other elements.

Reference counting is not a universal safety wrapper

The standard-library documentation is precise about what Arc makes thread-safe. It provides shared ownership through atomic reference counts. It does not make arbitrary contents safe for concurrent access. Its Send and Sync implementations depend on the corresponding properties of the contained type.

That prevents a misleading explanation of the browser mechanism. Wrapping a value with unsynchronized interior mutation in Arc would not automatically make it a safe shared style structure. The contents and the access methods still need to satisfy the concurrency requirements.

The documentation gives RefCell as a counterexample because its borrowing state uses non-atomic operations. For shared mutation, it points to synchronization types such as mutexes or atomics. Stylo benefits from sharing immutable structures and using an ownership-changing operation when a distinct value needs to change.

I like how these rules constrain the explanation as well as the code. Saying that everything sits behind a reference-counted pointer would omit the central condition. The design works because the permitted access to the contents matches what concurrent workers are allowed to do.

Scoped borrowing preserves the parent input

Not every shared relationship needs reference counting. The account describes Rayon allowing the parent style to remain behind an immutable borrowed pointer during the relevant work. The lifetime relationship ensures that the data remains alive for the workers that use it.

This provides a different guarantee from shared ownership. Reference counting permits owners to retain an allocation while they need it. A scoped borrow can instead establish that the work finishes within the lifetime of an already existing input. The article emphasizes that this avoids reference-counting overhead for that relationship.

The parent input is created while processing the parent and then becomes read-only for subsequent child work. A worker cannot obtain a mutable reference through the immutable borrowed reference it receives. The data-flow organization and the access rule reinforce each other, so the parallel algorithm does not depend solely on a comment asking workers not to change the parent.

The versioned builder declaration supports this reading. It records inherited_style and related parent/default inputs through immutable references. The implementation can still produce owned output where needed, but the input reference does not grant mutation of the computed values of the parent.

The foreign boundary retains its obligations

Stylo still calls into Firefox C++ code. Goregaokar reports thread-safety bugs appearing there when work that formerly happened on the main thread encountered caches or global mutable state from other threads. These observations belong to his account of the project, not to a personal incident being claimed here.

This boundary limits what the checks in Rust can establish about the complete browser. Rust can constrain the access described by its types, but a foreign function may have an additional main-thread assumption. A correct wrapper needs to represent or enforce that external requirement.

The article also reports that fuzzing Rust portions tended to find explicit panics, which were easier to diagnose than memory-safety failures on the C++ side. That is useful evidence about the development process, but it does not mean Rust has removed all browser bugs. Panics and logical errors remain meaningful failures to investigate.

The shipping result demonstrates a particular adoption path. A component with substantial internal parallelism can be integrated into an existing browser, with immutable sharing and scoped lifetimes supporting its work. Calls across the C++ boundary still need review for the thread on which they occur and the state they access. That work remains part of delivering the style computation in Firefox Quantum.