Repository navigation
ZIPP v0.0.26
ZIPP 0.0.26
ZIPP is now introduced as the Zippy Interpreter for Polyglot
Programming: a portable polyglot runtime, one virtual machine that every
supported language compiles to, with JavaScript and Python supported today.
This release changes how it is delivered and embedded, moves Test262 to the
latest corpus, and continues the interpreter work against QuickJS.
- Plain downloads. A Windows portable
.exeand a per-user.msi
installer, Linux binaries for x86-64, arm64 and armv7, one.wasmfile
per WebAssembly engine, and an embed kit: no more archives to unpack. - Embedding WebAssembly is one file. Every engine
.wasmcarries its
own bindings, sozipp.mjsloads any of them, and torch is an optional
second file. - Sandboxed by default.
createSandbox()runs a program in its own
Worker with nothing permitted: storage, files, network (allowlisted, no
cookies) and host functions are each granted explicitly, with step and
time limits. - Latest Test262, run once and unmodified: 96,229 of 96,230 executions
on tc39/test262 of 13 October 2026; the arm64 CLI passes the same corpus. - Interpreter against Bellard's QuickJS: on 21 diagnostic workloads ZIPP's interpreter now takes 0.931x the time of Bellard's QuickJS's (1.199x at the start of this cycle), and 0.791x on the 17 hostile workloads (0.834x).
Downloads
| File | What it is |
|---|---|
zipp-0.0.26-windows-x86_64-portable.exe |
The Windows command line; run it from anywhere. |
zipp-0.0.26-windows-x86_64-installer.msi |
The same binary, installed for the current user into %LOCALAPPDATA%\Programs\ZIPP and added to PATH; no administrator rights. Uninstall from Settings > Apps. |
zipp-0.0.26-linux-x86_64 |
The Linux command line, x86-64 (glibc 2.35 or later). chmod +x it after downloading. |
zipp-0.0.26-linux-arm64 |
The same for 64-bit Arm. |
zipp-0.0.26-linux-armv7 |
The same for 32-bit Arm (hard float); interpreter only. |
zipp-0.0.26-javascript.wasm |
The JavaScript engine for browsers, Node, Deno and Bun. |
zipp-0.0.26-javascript-lite.wasm |
The JavaScript core without Intl and Temporal, built for size. |
zipp-0.0.26-javascript-python.wasm |
JavaScript and Python, torch built in. |
zipp-0.0.26-javascript-python-base.wasm |
JavaScript and Python without torch. |
zipp-0.0.26-torch-package.wasm |
The torch package for the -python-base engine. |
zipp-0.0.26-<engine>.wasm.br |
Each WebAssembly module compressed with Brotli (quality 11), 74% to 81% smaller: javascript 1,196,785 bytes, javascript-lite 723,299, javascript-python 1,964,927, javascript-python-base 1,588,216, torch-package 403,240. Serve one with Content-Encoding: br and Content-Type: application/wasm and the browser decodes it; the plain .wasm is the default. Added to the release after publication, with SHA256SUMS regenerated to cover them. |
zipp-0.0.26-wasm-embed-kit.zip |
zipp.mjs (the loader and sandbox) and its types, the host SDK, the engine's TypeScript declarations, the browser GPU adapter, docs and three examples. Replaced after publication by a kit with loader 1.2.0, which also loads a .wasm.br: decoded by the server (Content-Encoding: br), decompressed by the loader where the runtime can (Node, Firefox), or by falling back to the plain .wasm beside it. Its modules.json lists the .wasm.br files and BUILD-INFO.txt names the modules' commit and the kit's source (kit-source=619deb1d); the examples, types, host SDK and GPU adapter are unchanged, and SHA256SUMS was regenerated. |
SHA256SUMS |
Checksums of every file. |
The macOS builds that were planned for this release are not included.
Embedding: one file per engine, sandboxed by default
import { loadZipp } from "./zipp.mjs";
const zipp = await loadZipp("./zipp-0.0.26-javascript.wasm");
const sandbox = await zipp.createSandbox({
limits: { instructions: 50_000_000, timeoutMs: 5000 },
permissions: { storage: true, files: { "data.csv": "a,b\n1,2" }, network: ["https://api.example.com/"] },
});
const { output } = await sandbox.run(code);
await sandbox.close();- How loading works. Each engine module carries the wasm-bindgen glue
generated for it in azipp.gluecustom section and azipp.metarecord
of its version, commit and languages.zipp.mjscompiles the module,
imports that glue (from ablob:URL, ordata:in Node), checks that
the engine reports the build its record names, and starts it. A module
and its bindings can no longer come from different builds. The release
workflow strips the sections again and byte-compares the result with the
module its Node boundary suite tested, so the custom sections change
nothing the engine runs. - What a sandbox permits. Nothing by default: no
localStorage, no
files, nofetch(it does not exist), no host functions, and nothing of
the page or of Node (document,process,requireare undefined).
permissions.storagegives a private in-memorylocalStoragewith a
quota;permissions.filesa read-only (or writable) file map reached with
host.callSync("fs.read", path)and kin;permissions.networkafetch
that reaches only the listed origins or URL prefixes, with no cookies or
credentials, the Fetch standard's forbidden headers dropped, redirects
refused unless allowed, and time and size limits;hostthe functions a
program may call withhost.call. Unknown option names are errors. - Limits.
limits.instructionsis the engine's step budget;
limits.timeoutMsends a sandbox's Worker whenrunorcalltakes
longer, the only way to stop a loop that never yields. - Python and torch:
loadZipp({ engine: "...-javascript-python-base.wasm", packages: ["...-torch-package.wasm"] }).
A Python program'sfilesare its importable modules. - The host SDK (1.3.0) takes
wasmUrlalone and loads it through the
same loader;moduleUrlstill selects the separate glue file of 1.2.
The site's new sandbox playground
runs this in the browser, with a switch for each permission and limit.
Checked: the kit's Node tests and sandbox tests (16 checks, in-process and in
worker threads, against a local HTTP server), and Chromium, Firefox and WebKit
(18 checks each: the examples, isolated sandboxes for every engine, the host
SDK, both content types GitHub and ordinary servers send, and the recommended
Content-Security-Policy).
Test262
The pin moves from tc39/test262 4249661 (26 May 2026) to d53ebc8b
(13 October 2026). On the new corpus, ZIPP passes 96,229 of 96,230
executions with zero skips, unmodified; the original DateTimeFormat ECMA-402
shard passes 490 of 490. CI now runs the corpus once against an
expected-failure manifest with one entry, an Annex B test whose final
assertion is ES2017 wording that ES2018 deleted (Node fails it too). The five
corrections and second, patched run the old pin needed are gone: upstream
fixed what four of them corrected.
The new corpus found 236 executions ZIPP failed, all fixed:
Iterator.prototype.chunks,windowsandincludes, the stage 4 iterator
chunking and includes proposals (218 executions);takeanddropreject a limit above 2^53 − 1 (12);Promise.tryreturns an already-resolved promise of its constructor
unwrapped, and a subclass gets its own capability (4);- the test host's module-source object and
import.sourceof it (2).
The arm64 Linux CLI, built and run on an Arm runner, passes the same corpus (96,229 of 96,230 with the same expected failure), the first full run of its JIT; the armv7 CLI passes the 32-bit-sensitive parts of it under emulation.
Fixes found by our own testing
- An iterator closing a generator ignored an overridden
return: a
for-ofbreak, destructuring,Array.from's close and an iterator
helper'sreturn()resumed the generator with its intrinsicreturn
instead of calling an own or replaced one (a57c51b4). - Frozen and sealed classes stayed writable, a sealed class's static
could be deleted, and assigning to a function's own accessor property
replaced it instead of calling its setter (5f86d140). - A computed-key class member lost its class's private brand chain
(0ecf155e); frozen and sealed objects now take the shape of their new
descriptors (3fd7ff7b). evalcode saw a builtin global the program had replaced or deleted
as the original (b628f56d); builtin writes on a frozen, sealed or
non-extensible global are pinned (a34c60e6).toFixed,toExponentialandtoPrecisionround exactly
(07b09f9c).- Python: a glob whose last component is
**matches files too, as in
Python 3.13 (57068197); all 196 corpus programs match CPython 3.13. - Indices and lengths past 2^32 were truncated on 32-bit builds, which
every WebAssembly engine is, 0.0.25's included, as is the new armv7 CLI:
new ArrayBuffer(8).slice(2, 2**32)was empty andslice(2**32, 7)held
7 bytes; array-like methods near 2^53 (findLast,slice,indexOf,
copyWithin,spliceandmapspecies lengths) read or passed the wrong
index; a RegExp'slastIndexpast 2^32 was written back as 2^32 − 1;
charAt,substr,padStart,DataViewoffsets andBigInt.asIntNhad
the same flaw. A value was converted to the 32-bitusizebefore it was
clamped; it is clamped in 64 bits now (2d337999), and a full Test262 run
on a 32-bit build passes (96,229 of 96,230). CI now runs one (65c1c1d8).
On 64-bit,"abc".padStart(2**32, "")returns"abc"as the
specification says, where it threw a RangeError.
Performance
This cycle's interpreter work was measured against Bellard's QuickJS
(2026-06-04), interpreter against interpreter (ZIPP with ZIPP_NOJIT=1),
on canonical profile-guided builds trained on a held-out corpus, in
startup-adjusted process cycles (lower is faster):
| Suite | Cycle start (46f8cafe) |
This release's code (35f73b1a) |
|---|---|---|
| 21 expansion workloads, medium size | 1.199x; QuickJS faster on 14 | 0.931x; ZIPP faster on 10, QuickJS on 10, 1 even |
| 17 hostile workloads | 0.834x; QuickJS faster on 10 | 0.791x; ZIPP faster on 9, QuickJS on 6, 2 even |
The work behind it: inline property-cache hits and stores, superinstructions
for common instruction pairs, a second, smaller interpreter loop for simple
functions and loops, dictionaries that leave a hole on delete, Proxy traps
without descriptor objects, per-site private-name resolution, direct
generator resumption, one-dispatch switch cases, exact number formatting,
DataView fast paths, and direct calls for WeakMap, Map/Set and string
methods. QuickJS is still faster on large-heap churn (1.80x), generators
(1.49x), class features (1.47x), accessors and Proxy (1.45x), a hot module
graph (1.66x), closure calls (1.30x), eval (1.29x) and string building
(1.22x).
No new comparison with Node, Bun or Deno, or with 0.0.25's JIT, was run for
this release; the canonical engine capture remains fa8e3b86's
(docs/PERFORMANCE.md).
import torch in the browser. The WebAssembly allocator asked for
memory a few pages at a time, so importing torch grew the memory about
2,700 times, and WebKit copies the whole memory on every grow. The memory
now grows in steps of half its size (at most 64 MiB) (7eb926e9), a
compiled library module is shared between programs instead of copied into
each (f091e2fe), and each compiled function's code is trimmed to its
length (a4099b74). The first import torch in a fresh engine, on one
Windows machine: WebKit from about 32 s to under 0.4 s, Chromium from
0.64 s to about 0.27 s, Node from 0.78 s to about 0.3 s; a later import in
Node from 0.105 s to about 0.09 s; WebKit's first Python run from 1.4 to
1.6 s to about 0.16 s. Four engines that imported torch now hold 215 MB in
Node, where they held 538 MB.
WebAssembly size
Each engine's code, against 0.0.25's published modules, raw / Brotli-11
(Windows build of 57068197):
| Module | 0.0.25 | 0.0.26 code | Larger by | 0.0.26 file, with bindings |
|---|---|---|---|---|
| JavaScript | 4,466,781 / 1,146,621 | 4,618,917 / 1,184,634 | 3.4% / 3.3% | 4,671,080 / 1,197,420 |
| Lite | 2,491,457 / 683,049 | 2,596,967 / 711,825 | 4.2% / 4.2% | 2,649,124 / 723,155 |
| Python without torch | 6,187,748 / 1,535,660 | 6,342,528 / 1,575,546 | 2.5% / 2.6% | 6,402,163 / 1,588,748 |
| Complete, torch built in | 8,220,591 / 1,910,130 | 8,381,571 / 1,950,561 | 2.0% / 2.1% | 8,441,201 / 1,964,429 |
| torch package | 2,156,660 / 401,562 | 2,162,554 / 403,062 | 0.3% / 0.4% | 2,162,699 / 403,120 |
The bindings each engine now carries add about 52 to 60 KB raw, 11 to 13 KB
after Brotli: the glue file that used to be a separate download.
Other changes
- Release workflow. Builds move to
release-build.yml, which
release.ymlcalls at the tag and which can be dispatched on any commit to
try a release. Its packaging steps are repository scripts that run the
same way on a workstation (tools/release/,crates/zipp-wasm/embed-kit/). - Build fixes: the sandbox and WebAssembly lockfiles record zipp-vm's
memchrdependency (bdbdc2b1); the arm64 test build compiles again and
is warning-free off x86-64 (57374b30). - Documentation: a shorter README, with its detail in
docs/pages and
the previous README archived; the site presents ZIPP as a polyglot runtime.
Publishing
As for earlier releases, the engines the site serves are refreshed AFTER this
release exists, in one commit that:
- sets
zippEngineReleaseinlanding/package.jsonto0.0.26; - refreshes
landing/public/playground-runtimeandlanding/public/wasmfrom
zipp-0.0.26-javascript-python.wasmandzipp-0.0.26-javascript.wasm
(node crates/zipp-wasm/embed-kit/package.mjs extract, then
sync-playground.mjs --refresh-engine), SHA256SUMS-verified; - syncs the sandbox playground (
node landing/scripts/sync-sandbox.mjswith
the release's files); - updates the version, size and SHA-256 that
landing/src/App.tsxand
landing/README.mdstate about the bundled engine.