Repository navigation
Replace libjpeg-turbo with a JPEG codec in safe Rust #47074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dylan-conway
wants to merge
147
commits into
main
Choose a base branch
from
claude/libjpeg-turbo-rust-17fiip
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
147 commits
Select commit
Hold shift + click to select a range
1814f1d
build: remove libjpeg-turbo
claude fba4e49
jpeg: add a decoder in safe Rust (bun_jpeg)
claude b3d495a
jpeg: add the encoder
claude 870a61b
image: decode and encode JPEG with bun_jpeg
claude 4701d02
tmp: the harness that compares bun_jpeg with libjpeg-turbo
claude d6b9aa7
jpeg: keep clear of the byte-search lint
claude 930a741
jpeg: include libjpeg-turbo's licenses and copyright notices
claude bf12679
[autofix.ci] apply automated fixes
autofix-ci[bot] 918d55a
jpeg: compute the encoder's divisors once
claude 1c9eafb
Keep prettier off libjpeg-turbo's license
claude fa813f1
test: JPEG files made with libjpeg-turbo's cjpeg and jpegtran
claude 1313a1a
wip: golden tests
claude 5799077
wip: golden tests, recorded
claude 9dc27e4
test: compare Bun.Image's JPEG results with those of the last build o…
claude c157571
jpeg: put the ICC profile together only when asked
claude 3308d2c
jpeg: convert YCbCr to RGB in 16 bits
claude e42cde5
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude 60472ed
jpeg: share constants and helpers between the modules
claude 0b4c282
jpeg: write the DCTs so that the compiler vectorizes them
claude d932889
jpeg: encode only the coefficients that are not zero
claude 468aab9
jpeg: do not zero the pixels before writing them, upsample in 16 bits
claude 8eb8f13
jpeg: one load for a Huffman code and its size
claude f97d523
jpeg: vectorize the inverse DCTs of the other sizes too
claude 378144f
jpeg: one body for the two ways to decode an MCU, codes ready to be o…
claude 6a7986c
jpeg: golden tests for CMYK and YCCK files, drop a field that was onl…
claude f1657b0
tmp: jpegdiff bench can profile the reference too
claude 1a63634
jpeg: a struct for what fill_bytewise returns
claude 535c0a9
jpeg: drop the record of warnings, which nothing in Bun reads
claude 16b9d18
jpeg: remove stores and branches that have no effect
claude 8ee24ed
jpeg: remove a field that is never read
claude 22f3a8b
jpeg: the workspace's allocation error, decode() takes the decoder, s…
claude 7bd46ad
jpeg: one helper for the low frequencies and for zigzag order, broken…
claude bf23ffc
jpeg: spell out names, comments in the present tense, golden tests re…
claude b175a40
jpeg: the encoder names its components and tables instead of numberin…
claude 2f7caba
jpeg: no made-up values for broken invariants in the decoder either
claude 53cf45f
jpeg: one description of an MCU's blocks, one way to derive a Huffman…
claude f91366f
jpeg: names in the arithmetic decoder and bits.rs, an internal error …
claude 706576a
jpeg: the length of a long Huffman code by comparisons instead of a loop
claude f02396b
jpeg: YCbCr to RGB in 16 bits and 16 pixels at a time, the progressiv…
claude 75c4701
jpeg: a lookup for whole short AC coefficients, a single stuffed FF w…
claude b960067
jpeg: no room for the whole image before the first scan has turned ou…
claude b0b3c5a
jpeg: the AC refinement pass goes through a band by bits instead of c…
claude 0f176ca
jpegdiff: a tool that times both decoders on every file of a directory
claude b2e0ffa
jpeg: running out of memory for the list of APP2 segments is an error
claude ae96de1
Bun.Image: refuse a JPEG with more than 500 scans
claude 825a666
jpeg: golden tests for 259 files that stand for bugs fixed in libjpeg…
claude b012cb3
jpeg: the whole-image array of a component is zeroed at its first sca…
claude 5093d08
jpeg: every allocation of the decoder can fail
claude 63485ca
jpeg: Highway kernels for color conversion, downsampling and upsampling
claude a1a8d9b
jpegdiff: build the kernels, choose their target, check them alone
claude 93f1ab6
Merge branch 'main' into claude/libjpeg-turbo-rust-17fiip
claude 8a69b45
[autofix.ci] apply automated fixes
autofix-ci[bot] fd0528b
test: the trimmed runtime has no texts of libjpeg-turbo to look for
claude 392c15d
Merge branch 'main' into claude/libjpeg-turbo-rust-17fiip
claude d6c7a5f
jpeg: stop encoding once the output cannot grow, refuse a symbol with…
claude ab05c6f
jpeg: say what scaled_size takes, update a comment on the codecs, a n…
claude 6f0f9b2
image: leave the encoded JPEG's buffer as it is, look for the JPEG co…
claude 56be61e
jpegdiff: fail on no input, on a failed job and on a failed variant, …
claude 0f31846
aot tests: do not pin what depends on the numbers of built-ins in one…
claude 492ecf9
jpeg: quote the whole attribution of every header in NOTICE.md, and t…
claude c41020a
jpeg: list the headers whose definitions src/jpeg carries in NOTICE.md
claude e253a9e
license: state that Bun is based in part on the IJG's work, link the …
claude 77ddcb1
Cargo.toml: give the real reason why bun_jpeg is optimized in debug b…
claude 7f44580
image: document ERR_OUT_OF_MEMORY, and the limit of 500 scans in the …
claude b06b9bb
ci: the Rust lint jobs no longer install nasm
claude d785df3
ci: look for nasm only in the build step for Windows x64
claude e2fbe68
source lints: a new Highway kernel may have a file of its own
claude b33f3cf
image: point from the README to src/jpeg/NOTICE.md
claude c69886f
jpeg: golden tests look at the ICC profile, 135 more files, a picture…
claude 9241ab8
image tests: the limits of the JPEG codec, profiles at the size of a …
claude 0dc1745
image tests: give the JPEG row kernels rows that AddressSanitizer wat…
claude 02b499f
simd-baseline: pin what the JPEG codec makes of the gradient, decode …
claude 6f2a879
image: tests and documentation for the refusal of redundant JPEG scans
claude d35d9f6
jpeg: remove checks that cannot fail, state that can be computed and …
claude dbd2eda
jpeg: find each run's table and row once per row of MCUs in lossless …
claude 0cfa507
jpeg: fail if the decoder did not produce every row
claude bc36ce3
jpeg kernels: refuse to compile without optimization
claude 6e92e98
jpeg: refuse a scan of what an earlier scan has coded
claude e6ed930
jpeg: notice before the transform that the headers did not fit in memory
claude b40f034
Merge branch 'main' into claude/libjpeg-turbo-rust-17fiip
claude fe6e98d
jpegdiff: quote two paths, run exactly --iters iterations, seeds with…
claude c0c8191
jpeg: the bytes that fill_bytewise reads may not be the bytes its cal…
claude 8f2951d
jpeg: golden tests expect a stream with a redundant scan to be refused
claude 7575750
image: search for the next marker in exif.rs without asserting that t…
claude 9055cc1
image tests: 17 more golden files for the Huffman decoders, DC predic…
claude 03deace
jpeg: no AVX-512 variant of the row kernels
claude 9f3a89a
jpegdiff: a redundant scan only excuses a stream that the reference d…
claude 3ed3bb5
jpeg: the fallible allocation helpers get a module of their own
claude 5e785f2
jpeg: the zigzag order and what goes by it get a module of their own
claude 8cce61e
jpeg: Output moves from the Huffman coder to the file with most of it…
claude 9dec26f
jpeg: #[rustfmt::skip] keeps the 8 by 8 tables in shape
claude 868f299
jpeg: one countdown to the restart marker for the four entropy decoders
claude 648084b
jpeg: both progressive decoders start a scan with the same function
claude 6f751ec
jpeg: one formula for fixed-point constants, and every constant writt…
claude 86becda
jpeg: names for a component's size in whole MCUs and for the side of …
claude 61dc148
jpeg: the trait for a source of bits moves next to the two sources
claude 1776642
jpeg: only the AC tables of sequential scans have the table of short …
claude ff8b314
jpeg: a Huffman table that several components share is derived once p…
claude 4a21b5d
jpeg: no room for correction bits in the scans that have none
claude 544413f
jpeg: the arithmetic decoder resets the statistics of a component once
claude fe7f1bd
jpeg: of the APP2 segments only the pieces of an ICC profile are kept
claude 9f1d268
jpeg: libjpeg-turbo's texts for three errors
claude c1233a5
jpeg: the multipliers of the inverse DCT belong to the coefficient co…
claude a645e59
jpeg: no bare booleans where rows are emitted and upsampled
claude e183a21
jpeg: the initial state of a bit reader and of the arithmetic decoder…
claude 9507d7a
jpeg: names for the numbers in the decoder
claude 3ebff55
jpeg: the encoder takes sizes from the component's sampling factor in…
claude 62d465e
jpeg: names for the numbers in the encoder
claude c1a0092
jpeg: the limit on Al is the standard's
claude e119174
jpeg: no bare booleans for the kind of frame and for the pass of the …
claude 7157302
jpeg: bit_width from the standard library, and no name for two things…
claude 3ea5cbb
jpeg: the output pass looks up a component's coefficients and multipl…
claude 5b9fc97
jpeg: idct2x2 writes its shifts as idct4x4 does, and idct1x1 stays in…
claude 31d7234
jpeg: one type for the decoder's rows of samples, differences and blocks
claude 5d56595
jpeg: the same name in Rust and in C++ for what makes a row twice as …
claude b8ffb41
highway: the JPEG wrappers leave short rows to the caller without a call
claude de56d5b
jpeg: the testing hook for the kernels has no bare boolean for the on…
claude a1e7209
jpeg: one line for each comment of the kernels, their wrappers and th…
claude 3660961
jpeg: one line for each comment of the decoder
claude 3f3f802
jpeg: one line for each comment of the encoder and of the shared modules
claude 3482297
image: one line for each comment that the change of the JPEG codec to…
claude 6f0962d
jpeg: a loop for each lossless predictor, and a run of samples withou…
claude fff61aa
jpeg: zero_if_empty says what it does, and a component of full size t…
claude b01c1cf
jpeg: comments and a message that said too little or the wrong thing
claude 4d06b29
jpeg: 14 more golden files, for changes to the decoder that the tests…
claude 2ac3067
jpeg: the comment on the kernels' test hook says that downsampling re…
claude 6c3fa24
jpeg: a test for a height that changes while the file is decoded
claude c1af7be
jpeg: 18 more golden files and 102 more pictures, for changes that th…
claude e80eec4
image tests: a race test that hangs leaves no child behind
claude 2b80e8b
jpeg: 2 more golden files, for two more changes that the tests let th…
claude eb50974
image tests: the wrappers of the JPEG row kernels, with rows of diffe…
claude 2ddcdc5
image tests: a type that `bun test --debug-aot` refuses
claude 3a47a5b
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude 350d410
test: send a request again when the server never saw it, in three AOT…
claude 8224fed
test: the same for the two other AOT node:http programs with that loop
claude 1cb18cc
test: one shape of build-scaling grows 2.58 times in memory
claude 788120f
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude 6f83dfd
image tests: the rule on redundant scans, where a wrong version of it…
claude 26b67b1
image tests: eleven more golden files, for changes to the decoder tha…
claude f68fbeb
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude 0e9fda7
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude 07da79c
image tests: two more golden files, for two marker codes that could b…
claude e4011bf
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude 43b9eae
image tests: two more golden files, for a refinement scan that takes …
claude 0d2debc
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude 7aab6fb
image tests: maxPixels in front of a JPEG decode, the order of the tw…
claude 1e3c2a3
Merge remote-tracking branch 'origin/main' into claude/libjpeg-turbo-…
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| target*/ | ||
| Cargo.lock | ||
| kernels | ||
| kernels-arm64 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [package] | ||
| name = "jpegdiff" | ||
| version = "0.0.0" | ||
| edition = "2024" | ||
|
|
||
| [workspace] | ||
|
|
||
| [dependencies] | ||
| bun_jpeg = { path = "../../src/jpeg" } | ||
|
|
||
| [profile.release] | ||
| debug = "line-tables-only" | ||
| overflow-checks = true | ||
| debug-assertions = true | ||
|
|
||
| [profile.fast] | ||
| inherits = "release" | ||
| overflow-checks = false | ||
| debug-assertions = false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # jpegdiff | ||
|
|
||
| Compares `src/jpeg` (`bun_jpeg`) with libjpeg-turbo 3.2.0 as Bun linked it. Temporary: it is here while the replacement | ||
| is under review, so that whoever works on it has the same tools, and goes away before the merge. | ||
|
|
||
| Set-up, on x86-64 Linux with clang, nasm and llvm-ar: | ||
|
|
||
| 1. `git clone https://github.com/libjpeg-turbo/libjpeg-turbo /root/ref/libjpeg-turbo` and check out | ||
| `c85e6b905bf237038faa936dab160ebfc5da0344`. | ||
| 2. Copy it to `/root/ref/libjpeg-turbo-patched` and apply what Bun applied, from the commit before the removal: | ||
| `git show 92e819d8:patches/libjpeg-turbo/<file>` for each file of `git ls-tree 92e819d8 patches/libjpeg-turbo/` | ||
| (`scripts/build/deps/libjpeg-turbo.ts` at that commit says how: two patches and the `jbun_stubs.c` overlay). | ||
| 3. `mkdir -p /root/ref/build && cp -r include /root/ref/build/` (the headers that the build generated), then | ||
| `./build-ref.sh`, which writes `/root/ref/build/libtjref.a`. | ||
| 4. `vendor/highway` has to be there, which it is after any build of Bun (`scripts/build/deps/highway.ts` has the commit and | ||
| two patches): `build.rs` compiles `src/jsc/bindings/highway_jpeg.cpp`, the kernels that `bun_jpeg` calls, with `clang++-23`. | ||
| 5. `cargo build --release` here. That profile keeps overflow checks and debug assertions; `--profile fast` is for timing. | ||
|
|
||
| Programs: | ||
|
|
||
| - `jpegdiff <file or dir>...`: decodes at every scale from 8/8 to 1/8 with both and compares pixels, size, ICC profile, | ||
| whether it fails, and at which stage. A stream that `bun_jpeg` refuses for a redundant scan, which libjpeg-turbo | ||
| decodes, is counted as `redundant-scan` and not compared. | ||
| - `mutate <dir>... --iters N --seed S --threads T`: the same for random mutations of the files. Failures are saved to | ||
| `/root/ref/corpus/fail`. | ||
| - `encfuzz --iters N --seed S --threads T --max-dim D`: encodes generated images with both and compares the bytes. | ||
| - `encspecial`: encoder cases that random images do not reach. | ||
| - `bench [file.jpg]...`: times both. | ||
| - `tools/kernels.sh`: checks the kernels alone, for every target of this machine and under qemu for those of arm64. | ||
|
|
||
| `HWY_TARGET=SSSE3` (or `AVX2`) in the environment makes any of the programs use the kernels for that target and not | ||
| for the best one that the CPU has. | ||
|
|
||
| `src/reference.rs` replays what `src/runtime/image/codec_jpeg.rs` did at 92e819d8. `tools/gen_corpus.sh` makes the seed | ||
| corpus with libjpeg-turbo's `cjpeg` and `jpegtran`. `regress/` holds inputs that once differed. The reference takes | ||
| `JSIMD_FORCENONE=1` and `JSIMD_FORCESSE2=1` in the environment. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| #!/bin/bash | ||
| # Builds /root/ref/build/libtjref.a from the patched libjpeg-turbo, the way scripts/build/deps/libjpeg-turbo.ts did (x64, release flags). | ||
| set -euo pipefail | ||
| SRC=/root/ref/libjpeg-turbo-patched | ||
| OUT=/root/ref/build | ||
| CC=${CC:-clang-23} | ||
| EXTRA=${EXTRA:-} | ||
| OBJ=$OUT/obj${SUFFIX:-} | ||
| rm -rf "$OBJ" | ||
| mkdir -p $OBJ | ||
| JPEG8="jcapimin jcapistd jccoefct jccolor jcdctmgr jcdiffct jchuff jcicc jcinit jclhuff jclossls jcmainct jcmarker jcmaster jcomapi jcparam jcphuff jcprepct jcsample jctrans jdapimin jdapistd jdatadst jdatasrc jdcoefct jdcolor jddctmgr jddiffct jdhuff jdicc jdinput jdlhuff jdlossls jdmainct jdmarker jdmaster jdmerge jdphuff jdpostct jdsample jdtrans jfdctflt jfdctfst jfdctint jidctflt jidctfst jidctint jidctred jaricom jcarith jdarith jerror jmemmgr jmemnobs jquant1 jquant2 jutils jpeg_nbits" | ||
| TJ="turbojpeg transupp jdatadst-tj jdatasrc-tj" | ||
| SIMD="jsimdcpu jfdctflt-sse jccolor-sse2 jcgray-sse2 jchuff-sse2 jcphuff-sse2 jcsample-sse2 jdcolor-sse2 jdmerge-sse2 jdsample-sse2 jfdctfst-sse2 jfdctint-sse2 jidctflt-sse2 jidctfst-sse2 jidctint-sse2 jidctred-sse2 jquantf-sse2 jquanti-sse2 jccolor-avx2 jcgray-avx2 jcsample-avx2 jdcolor-avx2 jdmerge-avx2 jdsample-avx2 jfdctint-avx2 jidctint-avx2 jquanti-avx2" | ||
| CFLAGS="-march=nehalem -O2 -g -fPIC -fno-omit-frame-pointer -I$SRC/src -I$OUT/include -DBUN_8BIT_ONLY -DUSE_CLZ_INTRINSIC $EXTRA" | ||
| for f in $JPEG8 $TJ; do $CC $CFLAGS -c $SRC/src/$f.c -o $OBJ/$f.o & done | ||
| $CC $CFLAGS -c $SRC/jbun_stubs.c -o $OBJ/jbun_stubs.o & | ||
| $CC $CFLAGS -c $SRC/simd/jsimd.c -o $OBJ/jsimd.o & | ||
| for f in $SIMD; do nasm -felf64 -DELF -D__x86_64__ -DPIC -I$SRC/simd/nasm/ -I$SRC/simd/x86_64/ -o $OBJ/$f.o $SRC/simd/x86_64/$f.asm & done | ||
| failed=0 | ||
| for job in $(jobs -p); do wait "$job" || failed=1; done | ||
| [ $failed = 0 ] | ||
| rm -f $OUT/libtjref${SUFFIX:-}.a | ||
| llvm-ar-23 rcs $OUT/libtjref${SUFFIX:-}.a $OBJ/*.o | ||
| ls -la $OUT/libtjref${SUFFIX:-}.a | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.