{"id":50819454,"url":"https://github.com/zig-utils/zig-js","last_synced_at":"2026-06-13T12:35:36.392Z","repository":{"id":360735338,"uuid":"1251434560","full_name":"zig-utils/zig-js","owner":"zig-utils","description":"A JavaScript engine in pure Zig.","archived":false,"fork":false,"pushed_at":"2026-06-10T20:09:46.000Z","size":6309,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T21:08:31.212Z","etag":null,"topics":["engine","javascript","javascriptcore","js","jsc","test262","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zig-utils.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-27T15:16:11.000Z","updated_at":"2026-06-10T20:09:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zig-utils/zig-js","commit_stats":null,"previous_names":["zig-utils/zig-js"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/zig-utils/zig-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zig-utils","download_url":"https://codeload.github.com/zig-utils/zig-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34285190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["engine","javascript","javascriptcore","js","jsc","test262","zig"],"created_at":"2026-06-13T12:35:35.655Z","updated_at":"2026-06-13T12:35:36.387Z","avatar_url":"https://github.com/zig-utils.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zig-js\n\nA **JavaScript engine written in pure Zig**, with a **JavaScriptCore C-API-compatible** surface. No JSC, no V8, no external C libraries — just Zig.\n\n`zig-js` is a small, embeddable engine for Zig applications, tools, and runtimes that want to own their JS stack. Use it directly as a Zig module, or link it in place of `JavaScriptCore.framework` when a host already targets the JSC C API.\n\nIt tracks the ECMAScript spec closely and is graded against the **real [tc39/test262](https://github.com/tc39/test262) corpus** — currently **42,771 / 47,930 (89.2%)** of the scored \"can we run it\" tests pass. See [Conformance](#conformance) for the full breakdown.\n\n```zig\nconst js = @import(\"js\");\n\nconst ctx = try js.Context.create(allocator);\ndefer ctx.destroy();\n\nconst v = try ctx.evaluate(\"let x = 40; x + 2\");\n// v == .{ .number = 42 }\n```\n\n## Contents\n\n- [How it works](#how-it-works)\n- [Conformance](#conformance)\n- [Performance](#performance)\n- [Language \u0026 runtime coverage](#language--runtime-coverage)\n- [Using it](#using-it)\n- [Used by](#used-by)\n- [Architecture](#architecture)\n- [Build \u0026 test](#build--test)\n- [Multithreading roadmap](#multithreading-roadmap)\n- [License](#license)\n\n## How it works\n\nThe engine has **two execution tiers that share one object model**, so behavior is identical no matter which runs:\n\n- A **tree-walking interpreter** — the correctness oracle and the fallback for anything not yet lowered.\n- A **suspendable stack bytecode VM** — lowers the hot subset of the language plus generators, async functions, and async generators (their bodies must suspend/resume, so they run *only* on the VM).\n\nTop-level and function code compiles to bytecode and runs on the VM; any construct the compiler can't yet lower transparently falls back to the tree-walker. A shared microtask queue drives Promises and async jobs.\n\n\u003e **Status: maturing.** Most of the language and the core built-in library are implemented and spec-faithful enough to satisfy test262's `propertyHelper` (brand checks, attribute fidelity, exact error types). The main gaps are `Intl`/CLDR locale data, `Temporal` edge cases, full regex-engine coverage, and a handful of early-error subsystems.\n\n## Conformance\n\nMeasured by `zig build test262` against the pinned tc39/test262 submodule. The score is split on two honest axes so a weak parser can't flatter itself — **valid** tests measure whether we can *run* a program, **negative** tests measure *strictness* (rejecting invalid input). Mixing them lets a parser \"pass\" negatives by failing to parse valid code too, so they're kept apart:\n\n| axis | meaning | passing |\n| ---- | ------- | ------: |\n| **valid** | can we run the program? (scored corpus) | **42,771 / 47,930 (89.2%)** |\n| negative | do we reject invalid input? (early errors — partial) | 3,213 / 4,668 (68.8%) |\n\nOf the valid corpus: **119 parse failures**, **5,040 runtime failures**, **0 host failures**. The runner currently skips 579 tests that need more harness work (top-level-await modules, some async-harness protocols, unloadable includes). Remaining valid failures concentrate in `intl402` (CLDR data), `Temporal` edge cases, `language`, `staging`, `RegExp`, `String`, and Annex B.\n\n### Per area (valid)\n\n| area | passing | area | passing |\n| ---- | ------: | ---- | ------: |\n| `language` | 17,600 / 19,070 (92.3%) | `Object` | 3,368 / 3,411 (98.7%) |\n| `Array` | 2,977 / 3,081 (96.6%) | `RegExp` | 1,567 / 1,687 (92.9%) |\n| `String` | 1,122 / 1,223 (91.7%) | `TypedArray` | 1,446 / 1,446 (100%) |\n| `TypedArrayConstructors` | 738 / 738 (100%) | `Uint8Array` | 70 / 70 (100%) |\n| `Map` | 204 / 204 (100%) | `Set` | 383 / 383 (100%) |\n| `BigInt` | 77 / 77 (100%) | `Symbol` | 98 / 98 (100%) |\n| `Boolean` | 51 / 51 (100%) | `Math` | 327 / 327 (100%) |\n| `DataView` | 561 / 561 (100%) | `Number` | 340 / 340 (100%) |\n| `WeakSet` | 85 / 85 (100%) | `WeakMap` | 141 / 141 (100%) |\n| `WeakRef` | 29 / 29 (100%) | `FinalizationRegistry` | 47 / 47 (100%) |\n| `Temporal` | 3,464 / 4,603 (75.3%) | `intl402` | 1,651 / 3,341 (49.4%) |\n| `annexB` | 962 / 1,071 (89.8%) | `staging` | 705 / 1,028 (68.6%) |\n| `SharedArrayBuffer` | 104 / 104 (100%) | `ArrayBuffer` | 221 / 221 (100%) |\n| `Atomics` | 390 / 390 (100%) | — | — |\n| `SuppressedError` | 22 / 22 (100%) | `ThrowTypeError` | 14 / 14 (100%) |\n| `AbstractModuleSource` | 8 / 8 (100%) | `AggregateError` | 25 / 25 (100%) |\n| `parseFloat` | 54 / 54 (100%) | `parseInt` | 55 / 55 (100%) |\n| `decodeURI` | 55 / 55 (100%) | `decodeURIComponent` | 56 / 56 (100%) |\n| `encodeURI` | 31 / 31 (100%) | `encodeURIComponent` | 31 / 31 (100%) |\n| `AsyncIteratorPrototype` | 13 / 13 (100%) | `eval` | 10 / 10 (100%) |\n| `global` | 29 / 29 (100%) | `Function` | 509 / 509 (100%) |\n| `Proxy` | 310 / 310 (100%) | `Reflect` | 153 / 153 (100%) |\n\n\u003e `zig build test262` prints each subtree's pass rate plus `parse-fail` / `runtime-fail` / `host-fail` counts, so the work stays data-driven. `zig build conformance` keeps a separate 33/33 always-green smoke suite for fast iteration. Refresh the corpus with `git submodule update --remote test262`.\n\n## Performance\n\nEach tier is gated by test262 (never regress correctness for speed) and timed by `zig build bench`:\n\n| tier | what | status | vs tree-walk |\n| ---- | ---- | :----: | -----------: |\n| 0 | tree-walk interpreter | ✅ | 1× (baseline) |\n| 1 | **stack bytecode VM** — lowers nearly the whole language (objects, arrays, members, `new`, methods, `++`, `instanceof`) | ✅ | ~1.1× |\n| 2 | **slot-allocated locals + frame-linked closures** — params/locals resolved to a flat frame array at compile time | ✅ | 1.3–1.85× |\n| 3 | **object shapes (hidden classes) + inline caches** — shared shape-transition tree, flat slots, monomorphic IC per property site | ✅ | **1.6–1.7×** |\n| 4 | NaN-boxed values | next | — |\n| 5 | generational GC (replaces the arena) | planned | — |\n| 6 | baseline → optimizing JIT | planned | — |\n\nTier-2 nearly doubled compute/call-heavy code; tier-3 brought object-property churn from a 1.33× laggard up to 1.73× (objects no longer allocate a per-instance hashmap, and repeat property access is an inline-cache hit). The tree-walker remains the oracle and the fallback for not-yet-lowered constructs.\n\n## Language \u0026 runtime coverage\n\n**Literals \u0026 operators** — numbers (int/float/hex/octal/binary/exp, spec `ToString`), strings (full escape set incl. `\\u{…}`), `true`/`false`/`null`/`undefined`, objects (shorthand, computed keys, getters/setters, spread), arrays (incl. holes/sparse), regex literals, template literals + tagged templates; the full operator set incl. `**`, `??`, `?.`, `\u0026\u0026=`/`||=`/`??=`, bitwise/shift, `in`/`instanceof`/`typeof`/`delete`/`void`, comma.\n\n**Bindings \u0026 scope** — `var`/`let`/`const`, block scoping + TDZ, destructuring (array/object, defaults, rest) in declarations, parameters, and assignment; `with`; `eval` (direct \u0026 indirect).\n\n**Functions** — declarations/expressions (incl. named-expression self-binding), arrows, default/rest params (including destructuring rest), `arguments` (mapped \u0026 unmapped), closures, `new`, `new.target`, getters/setters; `Function.prototype` `call`/`apply`/`bind`/`toString`.\n\n**Classes** — fields, private members + methods, `static` members + blocks, accessors, `super` (calls and member access), derived constructors, `extends`.\n\n**Generators \u0026 async** — `function*` + `yield`/`yield*` (with throw/return delegation, destructuring-assignment-with-yield), `async` functions + `await`, `async function*` + `for await … of` — all driven on the suspendable VM.\n\n**Control flow** — `if`/`else`, `while`/`do…while`, `for`/`for-in`/`for-of`, `switch`, labels, `break`/`continue`, `throw`/`try`/`catch`/`finally`.\n\n**Modules** — `import`/`export` (default, named, namespace, re-export, `export *`), graph linking with live bindings and live namespace objects (see [Conformance](#conformance) for scoring status).\n\n**Built-in library** — `Object`, `Function`, `Array` (incl. holes/sparse, `fromAsync`, freeze/seal), `String` + a homegrown `RegExp` backed by [`zig-regex`](../zig-regex), `Number`, `Boolean`, `Math`, `JSON`, `Symbol` (+ well-known symbols), `Map`/`Set`/`WeakMap`/`WeakSet`, `Promise` (combinators, subclassing/species, microtask ordering), `Date`, the `Error` family, `Proxy`/`Reflect`, `globalThis`, typed arrays + `ArrayBuffer`/`SharedArrayBuffer`/`DataView`/`Atomics`, `WeakRef`/`FinalizationRegistry`, and partial `Temporal` + `Intl`. Each is brand-checking and attribute-faithful enough to satisfy test262's `propertyHelper`.\n\n## Using it\n\n### As a Zig module\n\n```zig\nconst js = @import(\"js\");\n\nconst ctx = try js.Context.create(allocator);\ndefer ctx.destroy();\nconst v = try ctx.evaluate(\"let x = 40; x + 2\");\n// v == .{ .number = 42 }\n```\n\n### As a JavaScriptCore C-API drop-in\n\nLink `libzig-js.a` in place of `JavaScriptCore.framework`. The exported symbols match Apple's `\u003cJavaScriptCore/JSValueRef.h\u003e` / `\u003cJSObjectRef.h\u003e`:\n\n```c\nJSGlobalContextRef ctx = JSGlobalContextCreate(NULL);\nJSStringRef script = JSStringCreateWithUTF8CString(\"1 + 1\");\nJSValueRef result = JSEvaluateScript(ctx, script, NULL, NULL, 0, NULL);\ndouble n = JSValueToNumber(ctx, result, NULL); // 2.0\n```\n\nImplemented C-API symbols:\n\n- **Context lifecycle** — `JSGlobalContextCreate`/`Release`/`Retain`, `JSContextGetGlobalObject`, `JSEvaluateScript`, `JSGarbageCollect`.\n- **Value inspection** — `JSValueGetType`, `JSValueIs*`, `JSValueIsEqual`/`StrictEqual`.\n- **Constructors \u0026 coercion** — `JSValueMake*`, `JSValueTo*`, `JSValueProtect`/`Unprotect`.\n- **Objects** — `JSObjectMake`, `JSObjectMakeArray`, `JSObjectGet`/`SetProperty`, `JSObjectGetPropertyAtIndex`, `JSObjectCallAsFunction`, `JSObjectCallAsConstructor`, `JSObjectMakeFunctionWithCallback`, `JSObjectIsFunction`/`IsConstructor`.\n- **Strings** — `JSStringCreateWithUTF8CString`, `JSStringRetain`/`Release`, `JSStringGetLength`, `JSStringGetUTF8CString`.\n\n`JSObjectCallAsFunction`/`CallAsConstructor` drive the interpreter, so JS functions and the built-in `Error` constructors are callable across the C boundary; thrown JS values surface as the C-API `exception` out-param. `JSObjectMakeDeferredPromise` raises a `NotImplemented` exception until the deferred-promise plumbing lands.\n\n### Used by\n\n- [home-lang/craft](https://github.com/home-lang/craft)\n\n## Architecture\n\n```\n                          ┌─► compiler ─► bytecode ─► VM ──┐  (hot subset + generators/async)\nsource ─► lexer ─► parser ─┤                                ├─► Value\n              (AST)        └─► tree-walk interpreter ───────┘  (oracle + fallback)\n                                                     │\n                                          c_api.zig (JSC drop-in exports)\n```\n\n| file | responsibility |\n| ---- | -------------- |\n| `src/value.zig` | `Value` union + `ToBoolean`/`ToNumber`/`ToString`/`typeof`, equality, `Object` (shapes, per-index attrs, accessors, array elements/holes) |\n| `src/lexer.zig` | single-pass tokenizer |\n| `src/ast.zig` | unified expression/statement/module node |\n| `src/parser.zig` | recursive-descent + precedence climbing (`parseProgram` / `parseModule`) |\n| `src/interpreter.zig` | tree-walking evaluator, environments, and the built-in library |\n| `src/compiler.zig` | AST → stack bytecode (functions, generators, async) |\n| `src/bytecode.zig` | instruction set + chunk/function templates |\n| `src/vm.zig` | the suspendable bytecode VM (frames, generators, async drivers) |\n| `src/shape.zig` | hidden-class (shape) transition tree |\n| `src/promise.zig` | Promise state machine + microtask queue |\n| `src/context.zig` | engine instance (arena, persistent global env, module loader/linker) |\n| `src/jsstring.zig` | refcounted `JSStringRef` backing |\n| `src/c_api.zig` | the exported JavaScriptCore C-API symbols |\n| `src/root.zig` | `@import(\"js\")` entry point |\n\n## Build \u0026 test\n\nRequires Zig **0.17.0-dev**.\n\n```sh\nzig build                       # builds libzig-js.a (the JSC drop-in)\nzig build test                  # runs the unit + C-API test suite\nzig build conformance           # runs the always-green smoke suite (33/33)\nzig build threads-test          # runs the green WebKit PR-249 threads corpus (27/27)\nzig build test262               # runs the real tc39/test262 corpus, prints pass %\nzig build test262 -Dtest262=DIR # …with an explicit corpus root\nzig build bench                 # times the bytecode VM against the tree-walker\n```\n\nThe test262 corpus is vendored as the `test262/` git submodule (`git submodule update --init`); `zig build test262` uses it by default and skips cleanly if it isn't present. For speed it runs `ReleaseFast` under subprocess isolation, so a single pathological test can't abort the run.\n\n## Multithreading roadmap\n\n`Context.createWith(.{ .enable_threads = true })` now exposes an experimental shared-realm `Thread`, `Lock`, `Condition`, `ThreadLocal`, and property-`Atomics.wait` surface. That path is serialized by a VM lock and is tracked against the vendored WebKit PR-249 threads corpus; the current green allowlist is **27/27**.\n\nThat is a useful host-threading layer, but full JavaScript multithreading needs a broader agent model:\n\n- **Agent boundaries** — make ordinary `Context` ownership explicit, keep C-API handles agent-local, and define which values can cross threads.\n- **Worker agents** — one `Context` per OS thread with its own global object, realms, job queues, allocator state, module loader hooks, cancellation, and teardown.\n- **Structured clone \u0026 transfer** — implement `structuredClone`, message passing, ArrayBuffer transfer/detach, and host hooks for worker lifecycle.\n- **Shared-memory baseline** — finish `SharedArrayBuffer`, typed-array views over shared storage, `Atomics`, `Atomics.wait`/`notify`, and the real test262 `$262.agent` harness.\n- **Scheduler \u0026 queues** — keep per-agent microtask queues separate from host task queues, define blocking behavior for waits, and preserve deterministic promise-job ordering.\n- **Heap \u0026 lifetime model** — replace or contain the arena before shared lifetimes leak between agents; a future GC needs roots, write barriers, and cross-agent ownership rules.\n- **Concurrency tests** — grow the PR-249 corpus, then stress transfer/detach races, shared typed-array atomics, worker teardown, and host callback reentrancy before optimizing.\n\nThe [TC39 structs proposal](https://github.com/tc39/proposal-structs) is worth tracking. Fixed-layout structs, shared structs, `Atomics.Mutex`, and `Atomics.Condition` could become a natural data model for parallel JS because shared structs are designed to cross agents without copying. They should layer on top of the worker, structured clone, `SharedArrayBuffer`, and `Atomics` foundation rather than replace it.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzig-utils%2Fzig-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzig-utils%2Fzig-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzig-utils%2Fzig-js/lists"}