Skip to content

Releases

v0.14.0

Choose a tag to compare

@tannevaled tannevaled released this 31 Oct 22:33
9508f17

What's Changed

  • README and NOTICE: the header validation and fuzzing (#36, v0.13.8) by @tannevaled in #37
  • CI: qemufix, which the fleet sweep did not reach (this repo is a fork) by @tannevaled in #38
  • security: a sample ceiling; a pixel limit does not bound this decoder by @tannevaled in #40

Full Changelog: v0.13.8...v0.14.0

v0.13.8

Choose a tag to compare

@tannevaled tannevaled released this 26 Oct 12:43
b7401ad

Release backfilled on 2026-10-26; tag created 2026-10-20.

Commits since v0.13.7:

  • d429aba README and NOTICE: the 5/3 change (#34) (#35)
  • b7401ad Fuzz the decoder, and refuse the headers that crashed it (#36)

v0.13.7

Choose a tag to compare

@tannevaled tannevaled released this 26 Oct 12:43
bce3e33

Release backfilled on 2026-10-26; tag created 2026-10-18.

Commits since v0.13.6:

  • bce3e33 5/3 synthesis: a plain loop, and the vertical pass a row at a time (#34)

v0.13.6

Choose a tag to compare

@tannevaled tannevaled released this 26 Oct 12:43
827a889

Release backfilled on 2026-10-26; tag created 2026-10-18.

Commits since v0.13.5:

  • 827a889 NOTICE: state the 25 changes it did not; README: our own import path (#33)

v0.13.5

Choose a tag to compare

@tannevaled tannevaled released this 26 Oct 12:43
4fe757d

Release backfilled on 2026-10-26; tag created 2026-10-18.

Commits since v0.13.4:

  • 4fe757d RCT: a plain loop on every architecture (22.8x faster on amd64, 2.1x on arm64) (#32)

v0.13.4

Choose a tag to compare

@tannevaled tannevaled released this 26 Oct 12:43
2522b0d

Release backfilled on 2026-10-26; tag created 2026-10-18.

Commits since v0.13.3:

  • ad33abc Drop the hand-rolled module cache, which named a toolchain we no longer use (#29)
  • 7e3e27d ci: move the Go pin from 1.27.1 to 1.27.2 (#30)
  • 2522b0d Use github.com/go-simd/highway v0.0.13 in place of ajroetker/go-highway v0.0.12 (#31)

v0.8.0 — the entropy coder was the wall, not the transform

Choose a tag to compare

@tannevaled tannevaled released this 26 Sep 20:50
cff3463

The bottleneck on a scanned page is the entropy coder, not the wavelet

§24 of the conformance baseline named ia-biodiversity as the population where a
typical page ties with poppler — median 1.004×, slower on 128 of 250. Profiling its
worst page put the wavelet nowhere near the top:

DecodeCodeBlock 1.03 s 37.1%
↳ significancePropagationPass 0.47 s 16.9%
↳ cleanupPass 0.36 s 13.0%

And the largest single line inside the significance pass is not the arithmetic
decoder — it is computing the context from eight neighbours (100 ms against the
decoder's 90 ms).

e.state is [][]uint8, so each e.state[y][x] costs two bounds checks and a
pointer load, and countSigNeighbors does eight per coefficient. The rows are now
bound once and indexed; the same reads happen in the same order.

Measured

Three rounds of min-of-4 per binary, all in one interleaved run:

document before after
jahresberichtder710meye.pdf 801.7 ms 773.5 ms −3.5%
bulletin43peab.pdf 663.9 ms 636.4 ms −4.1%
b31820797.pdf (wavelet-heavy) +0.5%, noise

That last row is the point: the change helps where the mechanism applies and
nowhere else.

Unchanged output

All 3 215 corpus documents render byte-identical to v0.7.0.

v0.7.0 — the movement was the cost, not the arithmetic

Choose a tag to compare

@tannevaled tannevaled released this 26 Sep 15:51
28520d1

Two passes folded away, bit for bit

Synthesize2D_97_WithDims is 48% of the heaviest page in the corpus, and the
arithmetic is the small part of it:

1.12 s flat in Synthesize2D_97_WithDims the gather and scatter
0.45 s liftstep97_c_f64_neon the lifting, NEON assembly
0.40 s BaseInterleave_fallback_Float64 the interleave, pure Go
0.30 s runtime.memmove the two copies
0.23 s scaleslice_c_f64_neon the scaling, NEON assembly

1.82 s of moving data against 0.68 s of computing it. Two passes carry no
arithmetic of their own and are now folded into the passes either side: the
scaling into the read-out of each subband (a copy then *= K is the same
single multiply as = data[i] * K), and the interleave, in the vertical pass,
into the scatter.

Measured

min-of-5, two interleaved rounds, on the three heaviest JPEG 2000 pages, against
a ±1% noise floor:

document before after
b31820797.pdf 786.3 ms 721.2 ms −7.7%
bulletinchicagov48nchicg.pdf 657.7 ms 636.7 ms −4.4%
irrigationrequir1340fort.pdf 404.7 ms 388.5 ms −3.4%

Unchanged output

All 3 215 corpus documents render byte-identical. TestFewerPassesIsBitIdentical
compares the new synthesis against the shape it had before in exact float64
bits, over twelve geometries including odd sizes and both parities — and it runs
on all six architectures, so the fold is exact on big-endian s390x and on 32-bit
too, where the lifting has no assembly path at all.

Two things measured and NOT done

  • A fused kernel in scalar Go (one call over six passes instead of seven calls
    over nine) is 1.5× to 1.8× slower. The library's lifting is NEON assembly
    with FMA and losing it costs more than the passes save. The same FMA makes that
    path unreproducible in Go: the identical expressions differ by 1 to 16 ulp.
  • BaseInterleave runs the pure-Go fallback on arm64 — the generated NEON
    variant exists and is never assigned. Benchmarked head to head, it makes no
    difference: both run at 25–28 GB/s, which is memory bandwidth. An interleave is
    pure movement; the answer is to remove the pass, not vectorise it.

Also takes go-highway v0.0.12, eight versions newer than what this package was
pinned to. Nothing was updating it.