Skip to content

Latest commit

 

History

History
301 lines (284 loc) · 19.1 KB

File metadata and controls

301 lines (284 loc) · 19.1 KB

Implementation status

quickjs-oxide is an unsafe-free Rust rewrite targeting semantic Feature Parity with QuickJS 2026-06-04. It is runnable on the command line and as the real Rust/WASM engine in the GitHub Pages playground, but it is not yet at Feature Parity.

Current baseline

The authoritative R3fb Test262 vector has:

  • 79,597 full-corpus passes out of 102,037 variants (78.008%)
  • 79,647 eligible variants out of 102,037 (78.057%)
  • 79,597 passes out of 79,647 runnable variants (99.937%, secondary quality metric)
  • 50 classified failures and no timeouts among eligible variants

The pass count includes three exact (path, variant) results where Rust passes a test listed in pinned QuickJS's known-error file. These narrow target deviations are registered in deviations.md; they do not imply that the pinned engine passes those tests.

The exact profile, inputs, summary, line counts, and report hashes live in dev-support/test262/current.conf.

Implemented architecture

  • Rust compiler, verified bytecode, runtime, jobs, modules, and embedding API
  • byte-exact Script and ECMAScript Module embedding APIs and loader payloads, with QuickJS-compatible malformed UTF-8, WTF-8/CESU-8, source retention, and diagnostic locations
  • byte-exact strict and extended JSON module-loader payloads plus CLI file ingestion, preserving malformed-byte semantics and diagnostic locations
  • binary data, typed arrays, shared memory, and Atomics slices
  • collections, weak references, finalization, Promises, and iterator slices
  • Unicode 17 case, identifier, normalization, and property data
  • physical dense Array storage shared by literals, builtin results, and JSON
  • public and private instance/static data fields, private methods and accessors across ordinary, generator, async, and async-generator forms, static blocks, and private brand checks with #name in object, including QuickJS-matched early errors
  • static-module graphs, live namespaces, default exports, top-level await with async dependency/SCC scheduling, static import attributes with loader validation, strict and QuickJS-extended JSON synthetic modules, and canonical host-populated import.meta objects
  • Script-goal dynamic import() with FIFO load/finish jobs, live host-loader callback sampling, import attributes, cached cycle-root evaluation Promises, namespace reuse, Promise assimilation, and exact propagation of arbitrary JavaScript values thrown by normalize, attribute-check, and load callbacks
  • initiating-Context access for module-host callbacks, same-Context compiled module results, synchronous nested loader compilation with QuickJS-matched callback depth and evaluation order, and catchable native-stack exhaustion
  • QuickJS-ordered parse-time module publication: the construction identity is cache-visible before the first token, each request prefix is visible before its attribute callback, successful completion preserves the same identity, and failed/referenced identities remain deterministic without unsafe raw pointer reuse
  • native command-line execution, including byte-preserving file-module goal detection and filesystem dependencies, top-level-await settlement, and import.meta url/main; qjs-compatible side-effect-free structured print/console.log output with byte-exact WTF-8 String transport; plus a Rust/WASM browser playground

The public API and Test262 runner now report the same engine diagnostics. Detached public bytecode/VM execution has been retired, the Test262 runner loads its profile and exact admissions from hash-authenticated data instead of compiling milestone identity or admission tables, and its $262 realm/agent host is isolated behind a non-default feature.

Remaining parity work

Major open frontiers include remaining module-host lifetime and allocation-failure edge matrices and the unsupported/failed leaves recorded by the current Test262 vector. The private BC_VERSION 5 foundation now has bounded wire primitives and the pinned BigInt payload codec, including QuickJS's asymmetric 16,385-limb writer edge. A heap-independent WireGraph slice now validates and canonically rewrites primitives, ordinary objects, arrays, ArrayBuffers, TypedArrays, primitive wrapper ObjectValues, Dates, shared identity, and cycles with explicit decode and emitted-traversal budgets. Its data-object semantics include header atom interning, tagged decimal keys, first-slot/last-value duplicate properties, compatible null-atom consumption, depth-first output atom rebuilding, fixed-versus-resizable ArrayBuffer state, and per-buffer plus aggregate current-backing-store byte limits. All 12 pinned TypedArray kinds preserve view/backing identity, byte offsets, element counts, alignment, and current-byte-length bounds. The writer also preserves QuickJS's observable RAB-shrink asymmetry: it can emit a zero-length out-of-bounds view that the reader itself rejects. ObjectValue preserves Boolean, Int32/Float64 bit-level Number, narrow/wide String, and canonical BigInt wrapper payloads. Its reader also matches QuickJS's asymmetric JS_ToObject path: object children reuse the existing NodeId and append another reference-table entry, including pending Ordinary/Array ancestors, while the canonical writer rebuilds identity without the redundant tag. Date keeps its own identity and the exact Int32-versus- Float64 payload representation. Reader-only Float64 values retain -0, infinities, subnormals, and every NaN payload bit without applying the normal JavaScript constructor's TimeClip; non-number children are rejected only after their complete subtree has been read, matching QuickJS's allocation and diagnostic order. A later heap materializer must retain that numeric wire representation and install it without reusing the runtime's TimeClip Date constructor path. TemplateObject now preserves the dense cooked-element sequence, its mandatory raw wire child, preorder identity, and cycles through either child. Elements consume the same per-container and aggregate budgets as Array elements; the fixed raw slot does not. Pinned QuickJS consumes an undefined raw child but omits the own .raw property, and prevents extensions on every decoded template Array. Its indexed properties are enumerable but non-writable and non-configurable; a defined .raw has all three flags clear. Unlike a language-level tagged-template object, its Array length remains writable. The later heap materializer must reproduce those descriptor decisions and must not reuse template_object::seal_template_array, which intentionally makes the language-level template length non-writable. QuickJS's writer selects this tag only for a non-extensible Array under the bytecode flag, so the eventual public writer must retain that flag-dependent selection even though the pure graph layer can canonically re-emit an explicitly represented tag. BC5 atom handling now has an explicit caller-selected data or bytecode namespace, a release-pinned 242-entry predefined-atom catalog, and separate checked codecs for metadata ULEB atoms and raw u32 opcode operands. The data graph uses that shared namespace without auto-detection or local first_atom arithmetic, while an authenticated differential gate checks every catalog ID, kind, spelling, and ordering against the pinned quickjs-atom.h. The final function-code ABI now has a separately authenticated 244-entry opcode catalog, including 66 short opcodes whose first 19 wire IDs overlap QuickJS's 19 temporary compiler descriptors. A bounded, heap-independent scanner safely rejects the reserved invalid opcode, unknown opcodes, truncated instructions, offset overflow, and independent byte/instruction/relocation budget excesses. Both authenticated catalogs share one fail-closed C/Rust source inspector; production manifest attributes and imports are exact allowlists, while conditional compilation, external crates, macros, and all exclamation tokens are rejected before either frozen digest is accepted. It records structural instruction spans, resolves all 21 fixed-width atom operands into typed bytecode-namespace identities, preserves QuickJS's end-of-payload invalid-atom diagnostic position, and can canonically re-encode in the same namespace. The scanned CodeImage remains deliberately non-executable: it does not validate stack or control-flow semantics, create runtime atoms, or bypass the existing verified-bytecode publication path. A bounded FunctionRecordPrefix layer now reads and canonically writes the fixed FunctionBytecode body after tag 12: flags, frame metadata, locals, closures, scanned code, and optional debug bytes. It stops immediately before the first of pending_constant_pool_count recursively encoded values and never admits the record to execution. A complete, bounded BytecodeImage reader now owns the remaining traversal. It reads the bytecode header once, normalizes numeric, predefined-string, narrow/wide, and duplicate slots into one semantic atom namespace, and immediately relocates every function metadata and opcode atom. It preserves private and symbol identities and QuickJS's strict-reject versus compatible-omit disposition for null property keys. Strict mode rejects aliased narrow fields and reserved flag bits; compatible mode preserves QuickJS's u32-to-u16 truncation, while signed negative-size and decrement-overflow spellings remain hard safety rejections.

The whole-image reader uses one heterogeneous frame stack, one DataMachine, one ObjectArena, and preorder function and module tables across the root, every constant pool, every request-attributes value, and every module function object. FunctionBytecode and Module records never consume object-reference IDs. Their frames retain linear, source-bound data completions until a consuming whole-image finalizer unwraps every nested value by move. Function and Module IDs carry the same non-wrapping machine-source token, are checked against reserved slots before publication, and cannot index a different image. Aggregate limits independently bound function and module counts, mixed traversal depth, constant-pool entries, locals, closures, code bytes, instruction spans, atom relocations, debug bytes, and all four module metadata tables in addition to the per-record, wire, and graph limits. Each function prefix and each staged module table receives the intersection of its per-record cap and the remaining whole-image budget before table allocation or payload copying/scanning. The completed BytecodeImage is deliberately non-executable: it has no heap materializer, verifier bypass, or evaluation entry point. The matching canonical writer consumes that immutable image through a source-bound authentication plan before exposing any bytes. Decode and encode share the same whole-image totals, remaining-budget intersection, and per-function-versus-aggregate error attribution. The plan rebuilds dynamic atoms in QuickJS first-use order, including the request-name/attributes continuation, regenerates opcode atom operands from typed relocations, assigns object-reference IDs in one preorder spanning every nested value, and never assigns those IDs to FunctionBytecode or Module records. Module writes preserve unknown non-zero export types, normalize boolean bytes, and retain arbitrary request attributes and function-object values without imposing linker-only relationships on the archival codec. Ordinary-object writes match JS_WriteObjectTag by omitting enumerable symbol and private-name properties before their values can affect atoms, traversal, references, or resource accounting. A complete encoded-size proof precedes the final bounded little-endian emission; failed authentication never returns a partial buffer. This remains an internal archival codec, not an execution or public bytecode-loading path. An authenticated public-C-API oracle pins stripped 42; as a 25-byte BC5 vector, reads it in a fresh QuickJS runtime, evaluates it to 42, and gates the Rust prefix codec against the exact bytes. A second authenticated 110-byte vector pins a root-to-outer-to-inner constant-pool chain, the captured closure descriptor, and fresh-runtime evaluation to 42. A third authenticated 75-byte reference vector proves that neither the outer nor nested FunctionBytecode record consumes an object-reference ID: a cpool TemplateObject is ID 1, its raw object is ID 2, and the enclosing root later refers back to ID 1. A fresh runtime also observes the cpool result and root property as the same object. An authenticated 33-byte ancestor-reference vector adds the inverse topology: an enclosing Ordinary object is reference ID 0, its FunctionBytecode property has no reference ID, and that function's constant pool resolves ObjectReference(0); fresh QuickJS execution proves root.f() is the identical root object. Authenticated negative vectors also pin QuickJS's three diagnostic classes when FunctionBytecode appears as the child of ObjectValue, Date, or TypedArray; a truncated-record probe proves that all three parents first decode the complete function child before applying their typed rejection. Writer-specific public-C-API vectors additionally pin nested keep-source, strip-source, and strip-debug shapes. For pinned 2026-06-04, JS_WRITE_OBJ_BSWAP does not change any of those output bytes, and both flag forms load in a fresh runtime and evaluate to 42. A separate oracle constructs enumerable string, symbol, and private properties whose two non-string values share a circular object; bytecode writing without the reference flag still succeeds, emits only keep: 42, and fresh-runtime inspection observes no symbol or private properties. Rust tests reproduce that exact 13-byte canonical output and verify that the skipped values are never traversed. A Module-specific public-C-API oracle now pins a 109-byte stripped BC5 Module through fresh-runtime read, byte-exact reserialization, resolve, evaluation, and a global 42 receipt. Its JS_WRITE_OBJ_BSWAP bytes are identical. A second 283-byte vector records the complete request/attribute, local and indirect export, star export, default/named/namespace import, top-level-await, and FunctionBytecode-body topology. The Rust whole-image reader and canonical writer now preserve both vectors byte exactly without pretending that the archival image can link or execute them yet. The data decoder separates preorder identity registration from value completion: every parent/root attachment now uses one completed-subtree delivery path owned by the decode state. Its reference state is an independent generic ObjectArena; the whole-image decoder carries one instance through constant pools and module children without registering FunctionBytecode or Module records themselves. The data-value and container state machine is now independently generic over value and property-key carriers as DataMachine/DataFrame. The data-only facade still owns its own header interning, key timing, frame stack, root delivery, and unconditional cursor finalization, and still rejects FunctionBytecode and Module without consuming their payloads. The separate whole-image driver carries authenticated function and module identities through ordinary properties, Arrays, and TemplateObjects while reusing the same budgets and arena; TypedArray, ObjectValue, and Date expose typed failures when such an identity is invalid in their child position. The arena represents incomplete identities with kind-checked pending/ready slots. Source-bound linear node reservations, opaque data frames, and linear completed values prevent stale or cross-machine commits; machine identities never wrap, and raw node values cannot be rebranded as caller-produced opaque values. The value adapter is sealed inside the graph reader, so sibling modules cannot substitute a classifier which hides raw node identities. Atomic reference reservations keep alias publication indivisible, while independently bounded reference entries can alias pending or ready identities without consuming another node. Malicious TypedArray placeholder paths are rejected deterministically instead of reproducing pinned QuickJS's native crashes. The data-only graph still rejects SharedArrayBuffer, FunctionBytecode, and Module. The BytecodeImage reader admits FunctionBytecode and Module but still rejects SharedArrayBuffer, and neither reader is a public binary-object API yet. A heap materializer, native-code semantic verifier/translator, public read/write flags, and a public authenticated whole-image facade remain future milestones. In addition, num-bigint lacks fallible construction, so heap materialization, decoder OOM mapping, and allocator fault-injection remain hardening gates before untrusted input admission. Failed acyclic source graphs retry like QuickJS. Parse-time resolution success and one-shot failure latches match the pinned callback order; an incomplete graph is non-executable and dynamic import rejects deterministically. Rust safely uses an Aborted identity where pinned QuickJS can retain a dangling pointer whose subsequent use enters native undefined behavior. Native crash and allocator-aliasing probes are deliberately not automated. Reclaiming the resulting vacant module-cache slots remains architecture work. A Feature Parity claim additionally requires the acceptance contract in parity.md, including QuickJS differential evidence and non-Test262 behavior.

The R3en architecture-hygiene pass is complete. Cargo integration-test targets fell from 186 to 5 (one shared oracle harness). Repeated runtime-completion, value, property, CLI, and QuickJS transport helper families now share support code; a token-level gate rejects the retired and shared-provider fingerprints without conflating domain-specific prelude or spelling helpers. Path-sensitive non-oracle tests remain separate, while $262 oracle modules are feature-gated inside the shared harness. Negative diagnostics use a source-authenticated data contract. The ModuleImportBinding, public-class-field, public-static-initialization, private-data-field, and private-callable cohorts gate the exact QuickJS error message and line/column policy as well as phase and type. Logical-assignment, optional-chain-assignment, generator-yield collateral cases, and both direct and parenthesized assignment-target cohorts discovered during admission are exact-contracted too. R3el adds the QuickJS-matched single-statement function, lexical, and class declaration diagnostics, plus strict-code with statement diagnostics.

The active tree now retains only 23 referenced tests/test262-* artifacts; 313 superseded manifests and ledgers are authenticated in the R3eh history release. Fast CI rejects any new unreferenced Test262 bookkeeping file.

Verification

cargo test --locked --workspace --all-targets
cargo test --locked --features test262-host --lib --bins
./scripts/test-quickjs-c-oracles.sh --check
./scripts/test-test262.sh --check
./scripts/test-test262.sh --focused
TEST262_WORKERS=2 ./scripts/test-test262.sh --full
./scripts/test-web-playground.sh

Historical milestone gates, profiles, result vectors, baselines, and the former long-form ledgers are preserved in the release archive indexed under dev-support/test262/archive.