1 of 20

An introduction to the LibJS JavaScript engine

Linus Groh

November 2022 TC39 Meeting

2 of 20

Overview

  • In development since March 2020 by the SerenityOS community
  • Written in modern C++20, CMake build system
  • 2-clause BSD license
  • Completely from scratch, no 3rd party dependencies (beyond the C++ compiler)
  • AST and Bytecode interpreter, no JIT
  • Strong focus on spec compliance and completeness
  • Goal: Engine capable of handling the modern web (including Annex B!)

3 of 20

History

  • October 2018: Andreas Kling starts developing SerenityOS
  • June 2019: LibHTML added to support rich text
  • Slowly turns into a proper web engine (now LibWeb) -> “We need a JS engine!”
  • March 2020: LibJS development started
  • Early iterations similar to JSC in some ways as Andreas worked on WebKit

4 of 20

5 of 20

What’s with the name?

  • All applications and supporting libraries developed in-house, names don’t need to stand out and can be descriptive instead:
    • Browser, Calculator, File Manager, PDF Viewer, Terminal, Text Editor, … (45 in total)
    • LibDNS, LibGL, LibGfx, LibHTTP, LibTLS, LibWasm, LibWeb, LibXML, … (64 in total)
  • Naturally, “LibJS” was chosen as the name
  • Later on: use of the “Ladybird” name for the entire browser stack
  • “SerenityOS JS engine” / “Ladybird JS engine” both fine, depending on the context

6 of 20

Characteristics

  • Developed from scratch, no external dependencies
  • Bleeding edge & legacy JS features implemented at the same time, not in historical order
  • Source code remains extremely close to spec, helping with correctness & discoverability
  • Optimizations and non-standard code paths clearly marked as such
  • No roadmap, contributors choose what they will or won’t work on themselves
  • Development regularly recorded & published as YouTube videos

7 of 20

8 of 20

What’s in scope?

Everything!

  • We target draft specs of ECMA-262 and ECMA-402
  • Proposals usually considered from stage 3
  • Annex B
  • Host hooks & [[HostDefined]]
  • Pure ECMAScript engine, concepts defined elsewhere are handled in other Libraries (e.g. WebAssembly)

9 of 20

Implemented Proposals

Stage 3

  • Array Grouping
  • Change Array by copy
  • Hashbang Grammar
  • Import Assertions
  • JSON Modules
  • Legacy RegExp features
  • RegExp v flag
  • ShadowRealm
  • Symbols as WeakMap keys
  • Temporal

Stage 1

  • Error Stacks

Stage 3 (Intl)

  • DurationFormat
  • NumberFormat V3
  • Enumeration API
  • Locale Info

10 of 20

Performance

  • At this point still largely unoptimized!
  • Long term: no JIT, make bytecode as fast a possible instead
  • See also: “Super Duper Secure Mode” in Edge / “Lockdown Mode” in Safari
  • A handful of optimizations that we do have:
    • Object shape transitions
    • On-demand UTF-16 encoding (strings stored as UTF-8 internally)
    • Rope strings
    • NaN boxing
    • Environment lookup caching
    • Lazy initialization of intrinsics upon first access
    • Avoidance of temporary primitive wrapper object creation
    • Various optimization passes to improve generated bytecode

11 of 20

Testing

  • Custom test runner (test-js) with jest-like test framework
  • Own test suite, ~50k LOC across 1k files
  • Development of a test262 runner started early, but not widely used until a year later (could not fully run the test harness)
  • CI integration:
    • Custom tests complete within seconds, run on every push to any branch�Test failure = CI failure
    • test262 taking a bit longer, run on every push to main branch (mostly PR merges)�Unexpected test failures need to be handled manually afterwards
  • Currently passing 88% (AST) / 73% (Bytecode) of test262

12 of 20

Who’s using it?

Within SerenityOS:

  • Standalone JS REPL/interpreter with�Syntax highlighting and pretty-printing
  • Browser/LibWeb, of course
  • Spreadsheet
  • Assistant’s calculator feature (cf. macOS�Spotlight)

Outside of SerenityOS:

  • Ladybird Browser
  • Cosmo (Half-Life 2 plugin adding�a JS runtime)

13 of 20

14 of 20

15 of 20

Problems we’ve faced

  • Not using the spec as the source of truth = non-compliant engine!
    • Dozens, perhaps hundreds of small but observable inconsistencies (evaluation order of steps, duplicate calls of observable ToString, etc.)
    • Solution: coding style requires copying of the entire spec text into engine source code
    • Extreme, but solves the problem, makes review a lot easier, as well as syncing the implementation with editorial changes without having to trace current code to previous spec text
  • Not getting some fundamentals right from the beginning
  • Not testing at a large scale (test262) early enough

16 of 20

Contributors

  • 78 individual contributors (git shortlog -sn -E --grep='(LibJS:|LibJS\\+)'), many more when considering supporting libraries
  • 8 contributors with 100+ commits (>90% of contributions)
  • Core team largely unchanged over time
  • Newcomers still encouraged, e.g. through good-first-issue on GitHub

17 of 20

18 of 20

How can I try it?

  • SerenityOS (./Meta/serenity.sh run)
  • JS interpreter runs on Linux / macOS (./Meta/serenity.sh run lagom js)
  • Integrated into esvu and eshost (Thanks Idan!)
  • Ladybird Browser
  • Online REPL via Wasm at https://libjs.dev/repl (Thanks Ali!)

19 of 20

Questions?

20 of 20

Links