emscripten

2 posts

figma

Debugging Data Corruption with Emscripten | Figma Blog (opens in new tab)

Figma encountered intermittent save-file corruption caused by an elusive C++ memory-safety bug. Conventional debugging tools failed because the web app’s asynchronous behavior made the problem nondeterministic. A keyboard-and-mouse fuzzer eventually produced reproducible failures, while understanding Emscripten’s C++-to-JavaScript memory model helped narrow the investigation. ## Detecting the Corruption - Invalid save files appeared occasionally and could not be reliably reproduced. - Figma’s files used ZIP containers around Google FlatBuffers documents. - The serialized bytes looked mostly valid, but some offsets were unexpectedly zeroed. - Data being written to the wrong location suggested a memory-safety violation such as: - Use before initialization - Use after free - Out-of-bounds access ## Why C++ Made the Bug Difficult - C++ was valuable for Figma because it provided: - Access to libraries such as FreeType, HarfBuzz, and Skia - Low-level control suitable for graphics software - Mature debugging and optimization tools - However, C++ offers no built-in protection against memory errors. - The team tried avoiding deallocation, enabling malloc diagnostics, fixing Valgrind and Clang Analyzer findings, and upgrading the compiler, but none exposed the corruption. ## Reproducing the Failure with Fuzzing - The team planned to eliminate nondeterminism by recording user events and replaying them deterministically. - Building a complete session recorder was too large a project, so they limited inputs to keyboard and mouse events. - A fuzzer generated random event sequences and ran them against the application. - After several days, it produced multiple save failures, providing reproducible cases for debugging. ## Emscripten’s Emulated Memory Model - Figma’s C++ editor ran in the browser through Emscripten, which compiled C++ into JavaScript. - JavaScript typed arrays and shared `ArrayBuffer` storage allowed Emscripten to emulate contiguous C++ memory. - In the generated code: - Pointer loads became typed-array reads. - Pointer stores became typed-array writes. - Registers became local variables. - Shared buffers enabled pointer reinterpretation between types. - Emscripten generated asm.js-style JavaScript, using type annotations and operations optimized for JavaScript JIT compilers. The combination of deterministic fuzzing and knowledge of Emscripten’s low-level memory representation provided the path toward isolating the corruption, even though the ultimate fix was reportedly only a three-line change.

figma

Building a professional design tool on the web | Figma Blog (opens in new tab)

Figma set out to prove that a professional, high-fidelity design tool could run reliably in the browser. The challenge was that the web exposed specialized features rather than general-purpose graphics primitives, so Figma effectively had to build “a browser inside a browser.” Its solution combined C++ compiled through Emscripten with a custom rendering engine, enabling tighter control over memory, performance, and cross-platform consistency. ## Building Beyond the Web’s Original Design - The web was originally designed for documents, with application features added later as isolated APIs. - This limits advanced applications: - CSS provides sophisticated text layout but does not expose or customize the layout process. - Browsers have optimized GPU compositors, but developers cannot directly modify compositing or add custom blend modes. - Image decoders are highly optimized but offer limited control over details such as EXIF orientation and color-space handling. - Technologies such as WebGL and asm.js began exposing lower-level access to hardware, making demanding browser-based graphics applications practical. ## C++ and Emscripten - Figma’s editor was written in C++ and cross-compiled to JavaScript using Emscripten. - Emscripten targeted asm.js, allowing JavaScript engines to generate predictable and compact machine code. - This approach provided: - Direct control over memory layout, including compact 32-bit floats and bytes instead of JavaScript’s 64-bit numbers. - Manual allocation that avoids garbage-collection pauses and helps maintain 60 fps. - LLVM optimization and C++ template specialization for performance approaching native code. - More predictable execution because asm.js avoids the deoptimization points common in regular JavaScript. ## Memory Constraints and Indirect Buffers - Large contiguous typed-array allocations caused problems, especially in 32-bit Chrome on Windows, where address-space fragmentation from ASLR could prevent allocations as small as 256 MB. - Figma created an `IndirectBuffer` API to reference external typed arrays from C++. - Moving large image and geometry buffers outside the main heap: - Reduced fragmentation during long sessions. - Allowed better use of limited 32-bit address space. - Helped bypass typed-array size limits in 64-bit browsers. - The post also points toward future improvements from WebAssembly, which would reduce asm.js parsing costs, and shared typed arrays, which would enable shared-memory multithreading. ## Custom Rendering - Figma implemented its own rendering engine to achieve fast, consistent output across platforms. - Rather than relying entirely on browser graphics implementations, the team began building the rendering layer needed for a professional design application. Figma’s broader recommendation is to use low-level browser capabilities such as WebGL and compiled code when standard web APIs cannot provide the required performance or control.