Optimizing Two Zero-Copy Candidates

wasm-counts found two components with memory_copies=0 and large scalar copy loops. qipdb then confirmed that those loops ran for representative inputs. Replacing the loops with a guarded memory.copy path made both components faster.

memory_copies=0 is only a search filter. A validator or encoder can have no bytes that are safe to copy as one block. Inspect the source and an executed path before you change it.

Unicode NFC Normalization #

The old unicode-17-normalize-nfc.wasm decoded, decomposed, reordered, composed, and encoded every ASCII byte. An 8,439-byte Markdown file did not finish within qipdb's 10-million-instruction budget.

The new path scans for a byte with its high bit set. If there is none, the input is valid UTF-8 and is already in NFC. One memory.copy writes the output. qipdb now completes the same input in 143,505 instructions. It records 8,440 memory reads and one memory write instruction. The one write is a block copy, not a one-byte write.

The optimization also exposed correctness defects. The old component omitted 1,035 canonical singleton decompositions. It also failed to compose some Unicode 17 pairs when the second code point had canonical combining class zero. The old artifact failed 2,477 of the 100,170 NFC relationships in the Unicode 17.0.0 normalization test suite.

The corrected component passes all 100,170 relationships. Its Compliance oracle adds 24 small, portable cases. A Node.js test adds 810 deterministic sequences, maximum-size ASCII input, and malformed UTF-8 cases. The generated singleton table comes from the pinned UnicodeData.txt file in this repository.

NFC composition cannot increase the decomposed code-point count. The component now composes in place and removes its second scratch array. This reduces fixed linear memory from 22.1 MiB to 14.1 MiB.

For the 8,439-byte ASCII fixture, 100 alternating runs gave these measurements:

MeasurementOld artifactNew artifactChange
wazero total7.154 ms0.285 ms25.1× faster
wazero render6.779 ms0.149 ms45.5× faster
V8 render207.2 µs4.16 µs49.9× faster
Fixed linear memory22.1 MiB14.1 MiB-8 MiB
Raw Wasm35,191 B39,504 B+4,313 B
gzip Wasm15,164 B18,344 B+3,180 B

The larger module contains the missing Unicode mappings. That size increase is the cost of correct Unicode 17 NFC behavior, not the ASCII fast path alone. The fast path added 67 raw bytes to the corrected build.

A 5,684-byte page with non-ASCII text selects the general Unicode path. Its V8 render time stayed near 216 µs in one 200-run comparison. The new wazero total was 4% slower in that run. The exact ratio varies, but the conformance fix and 8 MiB memory reduction apply to every runtime.

CSS Data URI Wrapping #

data-uri-to-css-url.wasm scans its input once to calculate escaped output size. The old component then copied every byte backwards, even when no byte needed escaping.

The new component uses memory.copy when the first scan finds no escaped bytes. It keeps the scalar backwards loop for quotes, backslashes, control bytes, and other escaped input. Marking the small escape predicate as inline also removes one call for each scanned byte.

For a 20,477-byte clean data URI, qipdb reports:

qipdb counterBeforeAfterChange
Executed instructions2,396,027962,648-60%
Function calls40,9540-100%
Branches taken61,43940,962-33%
Copy-loop iterations20,4770-100%
memory.copy sites used01+1

Ten thousand alternating runs measured the same input and output:

MeasurementBeforeAfterChange
wazero total809.5 µs389.5 µs2.08× faster
wazero render778.8 µs359.5 µs2.17× faster
V8 render38.34 µs15.48 µs2.48× faster
Raw Wasm918 B1,017 B+99 B
gzip Wasm594 B624 B+30 B
Fixed linear memory64 KiB64 KiBNo change

The existing 12-case Compliance oracle and JavaScript oracle still pass. The tests cover escaped input, invalid data URIs, maximum expansion, and a maximum-size clean input.

Reproduce The Checks #

Build the components and oracles:

make -j \
  text/unicode-17-normalize-nfc.wasm \
  compliance/unicode-17-normalize-nfc.comply.wasm \
  text/uri-list/data-uri-to-css-url.wasm \
  compliance/data-uri-to-css-url.comply.wasm

./qip comply \
  text/unicode-17-normalize-nfc.wasm \
  --with compliance/unicode-17-normalize-nfc.comply.wasm

./qip comply \
  text/uri-list/data-uri-to-css-url.wasm \
  --with compliance/data-uri-to-css-url.comply.wasm

Download the published Unicode suite and run every NFC relationship:

curl -L \
  https://www.unicode.org/Public/17.0.0/ucd/NormalizationTest.txt \
  -o /tmp/NormalizationTest-17.0.0.txt

node tools/check-unicode-nfc.mjs \
  /tmp/NormalizationTest-17.0.0.txt \
  text/unicode-17-normalize-nfc.wasm

The downloaded file used for this study has SHA-256 5019ffd530751a741900c849c0e010332f142a3612234639bd200b82138a87db.

Use the exact fixtures and run counts shown above only to reproduce these numbers. Choose inputs from your own workload before you use either speedup as a capacity estimate.