A JavaScript engine written in pure Zig, with a JavaScriptCore C-API-compatible subset. No JSC, no V8, no external C libraries.
zig-js is a small embeddable engine for Zig applications, tools, experiments, and runtimes that want to own their JavaScript stack. Use it directly as a Zig module, or link libzig-js.a for hosts that only need the implemented public JavaScriptCore C API subset.
The configured conformance runner is green against the pinned tc39/test262 corpus it scores: 48,506 / 48,506 valid and 4,669 / 4,669 negative, with 0 parse, 0 runtime, 0 host, 0 skipped, and 0 excluded failures. That is a scoped result, not a claim of full ECMAScript completion.
const js = @import("js");
const ctx = try js.Context.create(allocator);
defer ctx.destroy();
const v = try ctx.evaluate("let x = 40; x + 2");
// v == .{ .number = 42 }- Status
- How It Works
- Conformance
- Performance
- Language And Runtime Coverage
- Using It
- Architecture
- Build And Test
- Threads And GC
- What Is Not Implemented
- License
Current public status is evidence-scoped:
- test262 totals come from docs/.data/test262.json, regenerated from docs/.data/test262-run-2026-07-05.txt.
- The skipped-test inventory is docs/.data/test262-skips.tsv, currently zero.
- The exact excluded-file inventory is docs/.data/test262-excluded.tsv, currently zero.
- Benchmark numbers below come from docs/.data/bench-2026-07-04.txt.
- C API scope comes from the exported symbols in src/c_api.zig.
- Threading and GC status are documented under docs/threads and docs/architecture.md.
The documentation guardrails live in docs/DOCS_ACCURACY_PLAN.md. Public claims should be tied to a run transcript, generated data file, source file, or current command output.
The engine has two execution paths sharing one object model:
- Tree-walking interpreter - the semantic baseline and fallback path.
- Suspendable stack bytecode VM - the compiled path for supported top-level code, functions, generators, async functions, and async generators.
The compiler lowers the subset it knows. Unsupported constructs fall back to the tree-walker where that is semantically safe, so VM coverage can grow without sacrificing correctness. Object shapes and inline caches are implemented for property access, and function activations use frame slots/upvalues for compiled code.
Default contexts use arena lifetime: values and objects are released when the context is destroyed. Opt-in GC contexts (Context.createWith(.{ .enable_gc = true })) use the precise collector described in docs/architecture.md and docs/threads/P7-gc-design.md.
Measured by zig build test262 against the pinned test262/ submodule. The runner scores two axes separately:
| axis | meaning | passing |
|---|---|---|
| valid | can the engine run the program? | 48,506 / 48,506 (100.0%) |
| negative | does the engine reject invalid input? | 4,669 / 4,669 (100.0%) |
Failure shape on the valid axis: 0 parse failures, 0 runtime failures, 0 host failures.
Skipped tests are excluded from both denominators. Current skipped count: 0. Current excluded count: 0.
Two non-normative SpiderMonkey staging files are removed from the configured corpus definition because their esid: pending expectations contradict the normative Annex B arguments tests in test/annexB. They are tracked in conformance/test262.zig as removed corpus inputs, not as engine failures, skips, or exclusions.
Representative green areas from the saved run:
| area | passing | area | passing |
|---|---|---|---|
test/language |
saved-run subtrees 100% | test/annexB |
1,071 / 1,071 |
test/intl402 |
saved-run subtrees 100% | test/staging |
1,476 / 1,476 |
Array |
3,081 / 3,081 | Object |
3,411 / 3,411 |
RegExp |
saved-run subtrees 100% | String |
1,223 / 1,223 |
Temporal |
4,603 / 4,603 | TypedArray |
1,446 / 1,446 |
Atomics |
390 / 390 | SharedArrayBuffer |
104 / 104 |
Map |
204 / 204 | Set |
383 / 383 |
WeakMap |
141 / 141 | WeakSet |
85 / 85 |
WeakRef |
29 / 29 | FinalizationRegistry |
47 / 47 |
zig build conformance is a separate fast smoke suite; the July 4, 2026 verification run passed 33/33 cases. Use zig build test262 for the full configured corpus.
zig build bench currently times the bytecode VM against the tree-walker on a small set of microbenchmarks. The latest saved local run is docs/.data/bench-2026-07-04.txt:
| case | VM ns/op | tree ns/op | VM/tree |
|---|---|---|---|
fib(27) recursion |
172,360,029 | 166,933,604 | 0.97x |
| tight loop sum to 100k | 8,614,833 | 8,547,850 | 0.99x |
| object property churn | 7,563,963 | 7,356,334 | 0.97x |
| array push/sum | 8,484,360 | 8,475,588 | 1.00x |
| deep recursion, depth 500 | 112,897 | 115,296 | 1.02x |
Those numbers show current VM/tree-walk parity on these microbenchmarks, not a broad speedup claim. The same benchmark run also prints no-shared-state thread throughput scaling:
| threads | wall ns | scaling |
|---|---|---|
| 1 | 258,529,500 | 1.00x |
| 2 | 297,057,916 | 1.74x |
| 4 | 315,458,875 | 3.28x |
| 8 | 362,099,959 | 5.71x |
Implemented performance machinery includes the bytecode VM, frame slots/upvalues, object shapes, inline caches, the engine-wide 8-byte NaN-boxed Value, and GC slab backing. Future performance work includes tighter VM/codegen coverage, nursery/generational GC work, and possible baseline/JIT exploration. There is no optimizing JIT today.
The configured test262 coverage for these surfaces is green.
Syntax and operators - literals, strings, regex literals, template literals, objects, arrays, destructuring, spread/rest, optional chaining, nullish coalescing, logical assignment, exponentiation, bitwise/shift operators, in, instanceof, typeof, delete, void, and comma.
Bindings and scope - var/let/const, TDZ, block scope, closures, direct and indirect eval, with, destructuring in declarations/parameters/assignment, and mapped/unmapped arguments.
Functions and classes - declarations, expressions, arrows, default/rest parameters, this, new, new.target, getters/setters, class fields, private members, static members/blocks, super, derived constructors, and extends.
Control flow - if, loops, for-in, for-of, for await, switch, labels, break, continue, throw, try, catch, finally, and using/disposal syntax covered by the configured runner.
Generators and async - function*, yield, yield*, async functions, async generators, await, Promise jobs, microtask ordering, proper tail calls in strict return-position calls, and module+async/top-level-await tests in the configured surface.
Modules - imports, exports, default/named/namespace re-exports, export *, live bindings, namespace objects, import.meta, dynamic import(), dynamic-import catch-target behavior, import defer async-module behavior, and top-level-await graph ordering covered by the configured runner.
Built-ins - Object, Function, Array, String, RegExp via zig-regex, Number, Boolean, Math, JSON, Symbol, Map, Set, WeakMap, WeakSet, Promise, Date, errors, Proxy, Reflect, globalThis, typed arrays, ArrayBuffer, SharedArrayBuffer, DataView, Atomics, WeakRef, FinalizationRegistry, broad Temporal, and Intl coverage.
const js = @import("js");
const ctx = try js.Context.create(allocator);
defer ctx.destroy();
const v = try ctx.evaluate("let x = 40; x + 2");Link libzig-js.a for hosts that use the implemented subset of Apple's public <JavaScriptCore/JSValueRef.h> / <JSObjectRef.h> surface:
JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
JSStringRef script = JSStringCreateWithUTF8CString("1 + 1");
JSValueRef result = JSEvaluateScript(ctx, script, NULL, NULL, 0, NULL);
double n = JSValueToNumber(ctx, result, NULL); // 2.0The current exported C surface has 50 functions:
- Context lifecycle -
JSGlobalContextCreate,ZJSGlobalContextCreateThreaded(gil),JSGlobalContextRelease,JSGlobalContextRetain,JSContextGetGlobalObject,JSEvaluateScript,JSGarbageCollect. - Values -
JSValueGetType,JSValueIs*,JSValueIsEqual,JSValueIsStrictEqual,JSValueMake*,JSValueTo*,JSValueProtect,JSValueUnprotect. - Objects -
JSObjectMake,JSObjectMakeArray,JSObjectMakeDeferredPromise,JSObjectGetProperty,JSObjectSetProperty,JSObjectGetPropertyAtIndex,JSObjectCallAsFunction,JSObjectCallAsConstructor,JSObjectMakeFunctionWithCallback,JSObjectIsFunction,JSObjectIsConstructor. - Strings -
JSStringCreateWithUTF8CString,JSStringRetain,JSStringRelease,JSStringGetLength,JSStringGetUTF8CString. - Worker extension -
JSWorkerCreate,JSWorkerPostMessage,JSWorkerReceive,JSWorkerTerminate,JSWorkerRelease.
ZJSGlobalContextCreateThreaded and JSWorker* are zig-js extensions, not public JSC symbols. JSObjectMakeDeferredPromise returns a pending native Promise plus paired resolving functions; callers observe settlement at the next microtask checkpoint (for example, after JSEvaluateScript returns).
See docs/api.md and docs/HOME_INTEGRATION.md for the fuller embedding story and the important warning that zig-js is not a drop-in replacement for Bun/Home's private JSC internals.
| file | responsibility |
|---|---|
| src/value.zig | Value, Object, coercions, equality, object slots/elements/accessors |
| src/lexer.zig | tokenizer |
| src/ast.zig | AST node model |
| src/parser.zig | recursive-descent parser |
| src/interpreter.zig | tree-walking evaluator and built-in library |
| src/compiler.zig | AST to bytecode compiler |
| src/bytecode.zig | bytecode instruction and template definitions |
| src/vm.zig | suspendable bytecode VM |
| src/shape.zig | hidden-class/shape transition tree |
| src/promise.zig | Promise state and microtask queue |
| src/context.zig | context lifecycle, module loader/cache, GC/thread options |
| src/gc.zig | opt-in precise GC |
| src/jsthread.zig | shared-realm Thread API |
| src/worker.zig | isolated worker agents |
| src/jsstring.zig | refcounted JSStringRef backing |
| src/c_api.zig | exported C API |
| src/root.zig | @import("js") entry point |
Requires Zig 0.17.0-dev.
zig build # builds libzig-js.a
zig build test # unit + C-API tests
zig build conformance # fast smoke suite
zig build test262 # configured tc39/test262 corpus
zig build test262-bin # build the test262 runner only
./zig-out/bin/test262 --list-skips > docs/.data/test262-skips.tsv
./zig-out/bin/test262 --list-excluded > docs/.data/test262-excluded.tsv
bun run docs:data
bun run docs:build
zig build bench # VM/tree-walk and thread-scaling benchmark
zig build threads-test # WebKit PR-249 thread allowlist
zig build threads-reference-audit # classify non-promoted PR-249 files
python3 tools/threads-reference-audit.py --probe-candidates
python3 tools/threads-reference-audit.py --run-probes --expect-current-blockers --probe-timeout 60
zig build threadfuzz
zig build threadfuzz -Dfuzz-midgc=true
zig build threadfuzz -Dfuzz-lifecycle=true
zig build test -Dtsan=true
zig build threads-profile
zig build gc-profileThe test262 corpus is vendored as the test262/ git submodule. zig build test262 uses it by default and skips cleanly if it is absent.
Context.createWith(.{ .enable_threads = true }) installs the shared-realm Thread, Lock, Condition, ThreadLocal, property-mode Atomics.*, proposal-aligned Atomics.Mutex / Atomics.Condition, and related surfaces. Shared-realm threads run true-parallel by default; .gil = true is available as a serialized compatibility mode.
const parallel = try js.Context.createWith(gpa, .{ .enable_threads = true });
const serialized = try js.Context.createWith(gpa, .{ .enable_threads = true, .gil = true });The isolated Worker implementation lives in src/worker.zig and is exposed to C embedders through the JSWorker* extension functions.
Current thread status is tracked in:
The README intentionally avoids duplicating the detailed thread/GC implementation log; those docs and the commands above are the source of truth.
Do not read the green configured runner as "the whole JavaScript universe is finished." Known non-implemented or non-scored areas include:
- full JavaScriptCore framework/private internals, Objective-C bridge, inspector/debugger APIs, and Bun/Home private JSC ABI;
- WebAssembly and JIT shell hooks from the PR-249 reference corpus;
- nursery/generational GC and any optimizing JIT.
MIT - see LICENSE.