EarendilEarendildjsHow djs works
Source

What djs optimizes for

Every unusual choice in djs serves one of five goals, and when two goals pull in different directions, the one listed first wins. This page states each goal, why a codemode script needs it, and which chapters show how djs meets it. Two constraints and a list of non-goals complete the picture. The other chapters refer to the goals by the names used here.

In short. In priority order: cheap data passing between the sandbox and its host, fast startup in wasm, compiling with no syntax tree, snapshots of the whole VM, and the ability to suspend anywhere. Under all of them: wasm32 is the target that counts, and behavior matches QuickJS.

Cheap data passing

A codemode script exists to combine tool results: long strings, arrays of similar objects, base64 images. They have to get into the VM, and the script’s output has to get out, with as little copying and conversion as possible, because for many scripts moving data is most of the work.

djs meets it with a value encoding the host can decode without help (values), a heap in one byte array the host reads in place (the arena), and a compact binary format for values coming in (the host interface).

Fast startup in wasm

A codemode script runs once, for milliseconds, and is thrown away. Whatever happens before its first instruction (instantiating the engine, building the global object, compiling the script) is pure latency, and a large wasm module costs download and compile time in the host.

djs meets it by shipping its initialized realm as a prebuilt image, so creating a VM is a copy (snapshots), by compiling in one pass (next goal), and by counting every kilobyte of the module, which is why most builtins are bytecode rather than Rust (builtins).

No syntax tree

Scripts are written fresh every time, so compile time is part of every run. djs compiles the way QuickJS does: a parser that emits bytecode while it parses, without building a syntax tree first. That keeps compilation fast and its memory small, at the price of a parser that must settle every ambiguity before it emits code (the compiler).

Snapshots

A script may wait minutes for a tool, or a person. The host should be able to save the whole VM while it waits (heap, stacks, pending promise jobs, pending tool calls), release the memory, and continue later, possibly in another process.

djs meets it by keeping all of its state in one byte array without pointers, so that a snapshot is a copy of that array (the arena, snapshots), and by making the copy compact and canonical (the garbage collector).

Suspend anywhere

The host must keep control: a script may loop forever, must not hog the thread that runs it, and may be cancelled. Execution therefore has to be able to stop between any two instructions, also inside a map callback, a getter, a toString call or a promise job, and continue later, or be snapshotted at that point.

djs meets it with an interpreter that never keeps JavaScript state on the Rust stack (the interpreter), with fuel as the unit of work (suspending anywhere), and with builtins that can stop in the middle (builtins).

Two constraints

wasm32 is the target that counts. djs runs mainly as a WebAssembly module inside a JavaScript host, so memory and speed are judged in V8’s wasm tier, not natively (D016). Layouts assume a 32-bit address space, memory grows in 64 KiB pages and never shrinks, and code size matters as much as speed.

Behavior matches QuickJS. djs replaces QuickJS in pi’s codemode, and both models and pi’s tests read what the engine prints, down to the text of error messages. Where the specification leaves room, djs does what QuickJS does, and every deliberate difference is declared in the tests (how djs is tested).

Non-goals

djs is always in strict mode. It has no with, no direct eval, no ES modules (import() compiles and rejects), no Intl, Temporal, SharedArrayBuffer or workers, and no JIT. Each of these would cost size or complexity for scripts that do not need them. Its conformance target is the part of the test262 conformance suite that codemode scripts can reach, not test262 for its own sake.

Decisions on this page:

  • D016: wasm32 is the primary target for performance and memory design
  • D008: Codemode top level is a function body
  • D1100: Difftest compares what pi’s codemode shows the model