Architecture
Wren is one executable, written in Odin, with a handful of statically linked libraries. This page walks through the parts and the rules that hold them together. The source is the reference: github.com/sachahjkl/wren.
The layers
Wren's own code · the WebAssembly boundary · a library or a system service
All the Rust is one static library, wren_native.lib, linked into wren.exe: nothing sits beside the executable.
The packages
| Package | Role |
|---|---|
| base | Scopes (an ownership tree with cleanups) and generational handles with stable pointers. |
| host | The event loop: thread-safe posts with coalesced wake-ups, timers, a pool for blocking work. |
| net | Transport only: HTTP(S), HTTP/3 and WebSockets on a Rust stack (WinHTTP stays selectable), per-host and global queues, back-pressure, cancellation. |
| fetch | The policy layer of the Fetch Standard: modes, CORS and preflights, credentials, redirects, HSTS, referrer policy, mixed content, MIME checks. |
| url · cookies · csp · http | WHATWG URLs and the Public Suffix List, the cookie jar (RFC 6265bis), Content Security Policy 3, headers and MIME types. |
| html · dom | HTML parsing with Lexbor, charset sniffing, quirks mode; the document tree, shadow trees and slots, event dispatch with retargeting. |
| css · style | Tokenizer, parser, selectors, media and container queries; the cascade with layers, var(), a rule index, a Bloom filter and partial restyles. |
| layout | Block, inline, flex, grid and tables, bidi and line breaking, incremental relayout, display lists cut into paint chunks. |
| wasm | Wasmtime: instances, imports defined on the linker with address checks, deadlines, limits, compiled-code caches, the WASI subset the JavaScript engine needs, pre-initialization of its image. |
| js · native/js | The JavaScript engine, written in Odin and compiled to WebAssembly (src/engine), with its glue: bindings generated from the specifications' Web IDL, hand-written members; on the browser side, its preferences and the bytecode cache. |
| page | One document: its DOM, Wasm instances, JavaScript realm and script loading, requests, timers, origin and CSP; the implementations behind the generated imports. |
| images · webfont | Image decoding on threads (Rust crates for every format; WIC for HEIF and on request), WOFF and WOFF2. |
| browser | The shell: window, tabs, navigation, address bar, painter and compositor, about:config, status bar, profiling. |
Rules that keep it from breaking
- One ownership tree. Process, tab, navigation, page, then the page's instances, DOM, requests and timers. Closing a tab or navigating closes a scope; cleanups run newest first, children before parents, so nothing outlives what it points to.
- Handles, never pointers, cross boundaries. A handle is a generation and an index. A destroyed or recycled slot fails to resolve: late network events and callbacks for dead objects are dropped.
- One UI thread, everything else posts. The network stack's threads, the blocking pool, image decoders and the colour glyph renderer only post results back. The DOM and the Wasm instances of a page live on the UI thread; text measurement runs in parallel on layout threads.
- Mechanism and policy are separate.
netmoves bytes and never follows a redirect or stores a cookie.fetchdecides, per hop, with the document's environment in hand. A module, a module load and a navigation all go through the same code. - Everything is bounded, each bound with a reason. Six requests per host and 64 in total, at most 20
redirects, cookies per site and in total, the bytes a page may hold, the memory of a module, the duration of
every call into it. The limits a user may want to change are preferences in
about:config. - A module cannot reach browser memory. Every address and length an import receives is checked against the module's own memory before the browser reads or writes it.
The WebAssembly boundary
Every import is described once, in a dialect of Web IDL (api/*.widl: modules,
functions, typed handles, enums, flags, structs, callbacks, errors, availability in windows and workers).
A generator, bindgen, writes everything else.
Host trampolines
One per import, defined directly on the Wasmtime linker. It checks addresses, validates UTF-8, looks up handles by kind, range-checks enums, then calls the implementation with ordinary Odin values. A missing or mistyped implementation does not compile.
The Odin SDK
sdk/odin/web: distinct handle types, strings and slices,
(T, Error) results. Modules whose memory is 64-bit get the same imports with 64-bit
addresses.
Documentation
docs/api:
every function of the 58 modules, with the ABI, so that SDKs for other languages can follow.
Conformance tests
2,256 generated cases over 1,482 functions: addresses out of bounds, invalid UTF-8, dead handles, enums out of range, buffers too small. A pointer outside the memory is a trap, a bad argument is an error or a trap, never undefined behaviour.
Modules are compiled by Cranelift. Compiled code is kept in memory (keyed by the SHA-256 of the module) and on disk in the profile, authenticated with an HMAC whose key is protected by Windows DPAPI. Interruption uses Wasmtime's epochs, advanced by a thread that only runs while a call is in progress: an idle browser wakes nothing.
JavaScript, as one more module
Wren has no native JavaScript engine. The scripts of a page run in a WebAssembly module, so they are held to the same rules as every other guest: its own instance, its own memory, a deadline on every call.
Our own engine, in WebAssembly
An engine written in Odin (a compiler to QuickJS-ng bytecode, an interpreter, a loop JIT), compiled to a WebAssembly module linked by the wasm-ld that ships with Odin, with no C library. Each page and each JavaScript worker gets its own instance, a realm. A trap loses that realm
(the status bar says JS stopped), never the browser.
Pre-initialized at build time
The interfaces, prototypes and preludes of the web platform are built once, when the browser is built, and saved in the module's memory image, which is embedded in the executable. A new realm starts in about 1.3 ms.
Bindings from the specifications
bindgen reads the Web IDL of webref
and writes the glue with Web IDL's rules: conversions, dictionaries, unions, overloads, reflected attributes,
iterables. The engine reaches the browser through the same api/*.widl imports as Wasm pages.
A member that is not implemented is absent.
Two heaps, one lifetime
A wrapper keeps its DOM node alive; the wrappers of one
DOM tree are grouped at each collection, so a detached tree that nothing holds any more is freed on both
sides. Scripts load as HTML specifies: parser order, defer, async, modules,
document.write.
Rendering
Retained compositor
The display list is cut into paint chunks, recorded once as GPU pictures and replayed. Scrolling, sticky offsets and transform animations change a matrix or a slot; nothing is re-recorded. Only chunks whose content changed are recorded again.
Effects kept between frames
Filtered layers, backdrops and blended pixels are cached on the GPU while their content is unchanged; colour matrices are folded into the composition. Animating opacity or a blend runs no extra pass.
Colours like the web
Blending happens on sRGB values as other browsers do, gradients and box shadows are computed exactly in the shader, and text uses grayscale anti-aliasing with contrast tuned like Chromium's.
Frame pacing
The refresh rate is read from the display path, frames are stamped with
the vblank they start on, and animations, requestAnimationFrame and smooth scrolling advance in
whole periods. Nothing is drawn when nothing changes.
Measurements
Medians or percentiles from the repository's tools (wasmbench, wrentool bench perf,
wrentool bench js, wrentool engine test262), on the development machine (RTX 4070 SUPER). Frame budgets are those of a
180 Hz display: 5.56 ms.
| Measure | Result |
|---|---|
| Call from a module into the browser, no arguments | 7.3 ns |
| Call with an address and a length | 8.4 ns |
| Call with a 64-byte string, UTF-8 checked | 12.1 ns |
| Instantiating a module already compiled (memory cache) | 0.03 ms |
| Loading the same module from the disk cache after a restart | 0.52 ms |
| Numeric loop, 50 M iterations: Cranelift, against the interpreter Wren used before | 124 ms vs 3146 ms |
| A new JavaScript realm, from the pre-initialized image | 1.3 ms |
| Octane, QuickJS-ng in Wasm / the same engine native / Edge (measured before the Odin engine) | 1664 / 2169 / 91318 |
From JavaScript: node.nodeType, getElementById, createElement + appendChild | 64 / 193 / 1311 ns |
| test262, engine in Wasm: runs passed | 99.92 % of 82,220 |
| First frame of the start page after launch (shaders precompiled) | 1.79 → 0.29 s |
| Wheel scrolling 10,000 paragraphs, CPU / GPU per frame, 99th percentile | 1.41 / 2.28 ms |
| Painting while scrolling over content already seen (median) | 0.013 ms |
| The effects page (blurs, backdrops, 18 blend modes), GPU per frame, 99th percentile | 3.2–3.6 → 1.2–1.3 ms |
| about:config, building the interface per frame while scrolling | 34.4 → 0.29 ms |
| Resizing the window: layout of one step on 10,000 paragraphs | 7.1 → 2.5 ms |
Arrows show a measured improvement, before and after the change that made it.
Credits
Wren is written by sachahjkl. It stands on these projects:
- Odin, the language everything is written in.
- gui_toolkit, the GPU drawing, text and UI layer.
- Wasmtime (Bytecode Alliance), the WebAssembly engine.
- QuickJS-ng, after Fabrice Bellard's QuickJS: the reference of Wren's engine, whose bytecode format it follows and whose number and Unicode libraries it ports.
- emmalloc, through Odin's
base:runtime, the heap of the JavaScript module. - webref, the Web IDL of the specifications.
- Lexbor, HTML parsing, URLs and encodings.
- hyper, rustls, quinn and tungstenite, the network stack.
- Fontations, HarfRust and ICU4X, fonts, shaping and line breaks.
- AccessKit, the accessibility tree.
- image, PNG, JPEG, GIF and TIFF.
- resvg, SVG rendering.
- rav1d and zenavif-parse, AVIF.
- image-webp, WebP.
- qcms, colour management.
- brotli-decompressor, WOFF2 fonts.
- Public Suffix List, sites and cookies.
- test262 and web-platform-tests, the conformance suites.
- Windows: Direct3D 11, Media Foundation, WASAPI, UI Automation; DirectWrite, WinHTTP and WIC as options.
- The web standards of the WHATWG, the W3C, TC39 and the WebAssembly Community Group.