Repository navigation
ZIPP v0.0.23
ZIPP 0.0.23
Fixes for seven wrong results in 0.0.22, smaller WebAssembly engines, a
faster native CLI and a faster Python frontend. The native CLI runs the
JavaScript benchmark rows in 4.9% less time and the Python suite in 0.77x
of 0.0.22's time (1.30x of CPython 3.13, from 1.79x), builtins call
compiled callbacks without the interpreter loop, and the browser modules
are 1.6% to 3.8% smaller on the wire.
Downloads
zipp-0.0.23-x86_64-pc-windows-msvc.zip: native CLI for Windows, complete: JavaScript, Python, torch and native GPU.zipp-0.0.23-x86_64-unknown-linux-gnu.tar.gz: the same native CLI for Linux.zipp-wasm-0.0.23-web.zip: JavaScript-only browser engine, TypeScript bindings and Worker host SDK.zipp-wasm-0.0.23-web-lite.zip: ZIPP Lite, the JavaScript core withoutIntlandTemporal, built for size, with the same bindings and host SDK.zipp-wasm-0.0.23-web-python-base.zip: JavaScript + Python without torch; add torch fromweb-torchwhen a page needs it.zipp-wasm-0.0.23-web-python.zip: JavaScript + Python complete, torch built in, with the browser GPU adapter and CPU WASM kernels.zipp-wasm-0.0.23-web-torch.zip: the torch package,zipp_torch.wasmand itszipp_torch.jsloader, added toweb-python-basethroughaddPythonPackage.SHA256SUMSfor every asset.
Every WASM archive includes BUILD-INFO.txt, PROFILE.json and SHA256SUMS.
Publishing requires CI and the full security workflow at the exact tagged commit.
Fixes
Wrong results in 0.0.22, each fixed with a test (those that print
expect Node 24's output):
- Memory that grows in place never reached the collector. An array
pushed or index-stored to a megabyte, an object gathering thousands of
keys or a filling Map allocates no new object, so a loop that built and
dropped such things ran no collection. In the browser engines the heap
ceiling ended a script whose live set was a megabyte ("script exceeded
its memory budget"), or the instance trapped at the end of its linear
memory; natively, 2,000 calls that each built an 80,000-element array
held 1.2 GB (Node: about 170 MB). The engine now reads what its
allocator has outstanding and collects on that growth: the same program
peaks at 60 MB. var s = "number"; s += xin a loop rewrote the stringtypeof
returns, sotypeof 1printednumber012...afterwards. Shared
strings can no longer be appended to in place.- Native CLI: an array callback's unassigned local kept the previous
element's value.[7, 1].map(function (x) { var t; if (x > 5) t = x; return t; })gave[7, 7]; it gives[7, undefined].map,filter,
forEach,reduce, the find family andsortreused one register window
for every call of a compiled callback. - Native CLI: an array callback kept reading a deleted or redefined
global. Afterdelete globalThis.git returned NaN where a
ReferenceError is due, and after a getter was defined ongit read the
old value. - Native CLI: a builtin run directly as a promise job could abort the
process.Promise.resolve().then(Array.prototype.forEach.bind(arr, f))
panicked (index out of bounds) oncefwas compiled and called another
function. - Browser engines: calling or constructing a long chain of bound
functions threw "Maximum call stack size exceeded" (300 levels of
g = g.bind(null, i)); each level was a native recursion under the
browser profile's cap of 32. The chain is now unwrapped in a loop. BigInt64ArrayandBigUint64Arraysort exactly. Their default sort
compared elements as doubles, so[2n**60n + 1n, 2n**60n]stayed
unsorted, as didBigUint64values above 2**63.
Other changes a program can see
- Identifiers follow Unicode 17. 12,823 code points that are
unassigned in Unicode 17 were accepted in identifiers (var \u{558});
they are rejected, as Node 24 rejects them. - A class's static keys from
"4294967295"up list among the string
keys in creation order, as Node lists them. - Python
str.strip()andstr.split()use Python's whitespace: U+FEFF
is no longer whitespace, and U+001C to U+001F and U+0085 are;
split(None, n)no longer strips the remainder.hash(str)values
change. - Collections run where none did (memory grown in place, and compiled
loops that only concatenate strings or call natives), so
finalization-registry callbacks and WeakRef clearing can come earlier. - Step metering in the browser engines: JavaScript programs charge
the same steps as in 0.0.22. Python programs charge different counts:
starting the runtime charges about 270 steps more, and loops that read
properties, build comprehensions, raise and catch, or sort by a key
charge fewer (2,000 to 4,600 fewer on loops of a few hundred
iterations). A budget tuned to 0.0.22's Python counts buys a little more
work. - Embedders:
zipp_vm::note_large_blockandnote_every_blocklet a
host's allocator report the blocks it hands out, so growth in place is
collected (one that reports nothing behaves as 0.0.22 did), and
zipp_vm::embed::parse_f64parses numbers as the engine does.
WebAssembly size
Module sizes on a Windows build of 20123116, the release's source but for
its last change to the regex compiler (which makes Lite 45 bytes smaller raw
and 113 after Brotli), raw and Brotli-11, beside 0.0.22's:
| Module | 0.0.22 | 0.0.23 | Smaller by, raw / Brotli |
|---|---|---|---|
| JavaScript | 4,876,583 / 1,227,452 | 4,636,144 / 1,180,609 | 4.9% / 3.8% |
| Lite | 2,634,413 / 710,436 | 2,573,530 / 697,741 | 2.3% / 1.8% |
| Python without torch | 6,519,418 / 1,597,418 | 6,331,117 / 1,566,620 | 2.9% / 1.9% |
| Complete, torch built in | 8,443,452 / 1,950,080 | 8,254,998 / 1,918,013 | 2.2% / 1.6% |
The bytes came out of tables: the time zone transitions and the zone table
are packed by column, the likely-subtags table is packed into fixed-width
keys, identifier characters are read from the regex engine's Unicode
tables, a regex literal's early errors are checked by the parser the VM
already has, decimal text is parsed by the engine's own parser in every
module (so core's 10 KB table is gone), and two sorts go through the
integer sort the engine already has. The torch package is unchanged at
about 0.38 MB.
WebAssembly speed
The browser engines interpret, in a checked profile with no unsafe. On
the size pass's corpus under Node 24 (24 cases; one process a case,
pinned, four configurations), against 0.0.22's modules:
- The JavaScript module runs it in 0.84 to 0.86 of the time, Lite in 0.84.
A call that allocates nothing takes 0.11 to 0.18 of the time, a string
built by appending 0.56 to 0.64,for-inover an object 0.51 to 0.60. - Rows that are slower: object allocation (3% to 7% in the JavaScript
module: every allocation is now reported, so that memory grown in place
is collected), and in Lite
the error constructors,delete,matchAlland engine start (2% to 9%).
Lite's Unicode RegExp compile read 17% slower in that screen and is
fixed in this release (\p{Letter}compiles in 0.93 of the fourteenth
pass's time); the screen ran before the fix. - The two Python modules were not screened against 0.0.22 for this
release. The fourteenth pass measured them at 0.92 to 0.93 of 0.0.22's
time.
Native CLI
The 13 JavaScript benchmark rows take 4.9% less time than in 0.0.22
(paired, 95% interval 4.3% to 5.7%), in an ordinary, not profile-guided,
release build: async-promise-chain and polymorphic-objects 18% less,
json-large 15% less, and every row prints the same bytes.
class-prototype-hot is the exception, 3% slower. The headline ten rows
are at 0.90x of Node 24's time, and all 13 at 0.63x.
- Builtins call compiled functions cheaply. A compiled callback runs
over one reused register window inmap,filter,forEach,reduce,
some,every,find,findIndex,sort,Array.fromand
Map/SetforEach, and any other builtin's call of a compiled function
(a promise reaction, a getter, areplacefunction) skips the interpreter
loop: 6M capturingmapandforEachcallbacks take 79 ms where they
took 232 in 0.0.22 (Node: 64),s.replace(re, f)over 800k matches 77 ms
where it took 261 (Node: 62),Array.from(array, f)of 3M elements 59 ms
where it took 611 (Node: 88). - Class construction. A class keeps its constructor's function value
instead of making it again for everynew, a class field is appended
without a descriptor object, and a loop that constructs is compiled: a
million constructions of a class with two fields take 274 ms where they
took 2,059 in 0.0.22 (Node: 34), a derived class 537 where it took 864
(Node: 38). Construction is still several times Node's time. - Generators and iterators.
for (v of gen)takes each step without
its result object (3M steps 170 ms where they took 538; Node: 65), and
iterator result objects share one key plan. - Strings and keyed access.
s += tappends in place, a keyed access
predicts its slot from the shape and the key, andfor-inand
Object.keysreuse a shape's key list. A word count through
counts[w] = (counts[w] || 0) + 1takes 377 ms where it took 640
(Node: 140). - Memory. Besides growth in place, compiled loops that only build
strings now poll the collector: such a loop held 675 MB where Node holds
30, and holds 30.
Python
The native CLI runs the 36 programs of the Python benchmark suite that
CPython also runs at 1.30x of CPython 3.13's in-script time, where 0.0.22
took 1.79x, and the suite's 44 programs in 0.77x of 0.0.22's time. Eleven
programs now take less time than in CPython (seven in 0.0.22): among them
range_loop, int_arith, global_read, list_index and builtin_len.
The slowest against CPython are str_methods (3.7x), instance_new
(3.7x), try_raise_catch (3.2x) and fstring (3.2x).
Twenty-nine changes, among them: a callee's frame pushed from the call
site's cache, small-int arithmetic and comparisons settled in the dispatch
loop, compiled subscripts, stores and value instructions calling their own
helpers, a raise caught in its own frame without leaving the compiled
loop, a traceback frame recorded while unwinding, plain classes
constructed inline, and generators stepped from compiled loops. The 179
programs of tests/python_corpus print what CPython 3.13 prints, except
the five that need a GPU or a transport.
Validation
Test262 passes with the same nine documented expected failures as before
(95,671 of 95,680 executions). These figures are from one Windows machine;
the measurements, the gates and what was not measured are in the
validation notes.
Publishing
As for 0.0.22, the engines the site serves are refreshed AFTER this release
exists, in one commit that:
- sets
zippEngineReleaseinlanding/package.jsonto0.0.23; - refreshes
landing/public/playground-runtimefrom the
zipp-wasm-0.0.23-web-pythonasset (sync-playground.mjs --refresh-engine)
andlanding/public/wasmfromzipp-wasm-0.0.23-web, both SHA256SUMS-verified; - updates the version, size and SHA-256 that
landing/src/App.tsxand
landing/README.mdstate about the bundled engine.
Full Changelog: v0.0.22...v0.0.23