Everything this package pins is in patches/manifest.json — one file, so the
inventory and the build script cannot drift apart. Check it any time with:
node scripts/upgrade.mjs # pins, artifact checksums, every patch and its marker
node scripts/upgrade.mjs --diff # regenerate patches/diffs/*.patch and prove each applies
node scripts/upgrade.mjs --install <fork> # copy every patch file to its target in a clone
node scripts/upgrade.mjs --refresh-upstream # re-extract patches/upstream/ from the core sources jar--diff fetches each patch's upstream source (or reads patches/upstream/ for core classes, which have
no fetchable source), writes a unified diff into patches/diffs/, and then applies that diff to the
upstream and checks the result equals our file. A diff that cannot be proven that way is reported as a
failure rather than written.
| Thing | Value | Where |
|---|---|---|
| Fork | Vsprocessing/teavm-javac@d1514ab… (TeaVM 0.14.1) |
patches/manifest.json |
| Build JDK | 25 (StdlibConverter reads the running JDK's class files, so the version is part of the build's identity) |
patches/Dockerfile |
| Binaryen | 133 (wasm-opt --enable-gc) |
patches/Dockerfile |
| Artifacts | 5 files, SHA-256 each | patches/manifest.json → verified by scripts/fetch-teavm.mjs on every build |
There are three kinds of change on top of upstream, and they upgrade differently:
| Kind | Count | How it is carried |
|---|---|---|
fork-file — the fork's own sources, patched |
3 | whole file in patches/, plus a diff against the fork commit |
core-shadow — a TeaVM core class the fork does not own |
5 | whole file in patches/, plus a diff against patches/upstream/ |
classlib-vendor — a TeaVM class-library class with our additions |
8 | whole file in patches/, plus a diff against TeaVM 0.14.1 |
new-source / new-classlib — classes that do not exist upstream |
2 | whole file only |
third-party — the bundled ThreeTen Duration |
1 | whole file only |
patches/manifest.json also lists forkEdits — changes inside a file the fork owns that we do not
replace (Compiler.java: registering the transformer and the two dependency listeners). Those are
applied by hand and quoted in patches/README.md.
Rare, but this is the "we found a new upstream build of the same commit" case:
# replace the file(s) in assets/
node scripts/upgrade.mjs # checksums will fail until you update the manifest
# update the checksum(s) in patches/manifest.json
npm run build && npm testThese are build inputs, not sources:
- Change the version in
patches/Dockerfile. - Rebuild the compiler archive (see §C below). Expect the artifacts to change —
StdlibConverterreads the running JDK, so a JDK bump changes the SDK archive's bytes even if nothing else moved. - Re-vendor and re-run the acceptance gate.
# 0. pick the new commit and record it
# (patches/manifest.json → teavm.commit, and .teaVMVersion if the fork moved TeaVM)
# 1. the fork clone: fetch and check out the new commit
cd <fork clone> # e.g. D:\DevWork\teavm-javac-build
git fetch && git checkout <new-commit>
# 2. re-apply the patches. There are two cases and they need different commands:
#
# a. SAME commit (fresh clone, or you just want the fork back in shape):
node scripts/upgrade.mjs --install <fork clone> --dry-run # see what goes where
node scripts/upgrade.mjs --install <fork clone> # copies all 19 files to their targets
#
# b. NEW commit — do NOT blind-copy: that reverts whatever upstream changed in those files.
# Apply our change to the new upstream instead, then resolve conflicts by hand:
cd <fork clone>
git apply -3 <repo>/patches/diffs/<File>.java.patch # one per patch; -3 leaves conflict markers
#
# Either way, two things are still manual because they are edits inside files the fork owns:
# * Compiler.java — the two hunks quoted verbatim in patches/README.md
# * compiler/build.gradle — prepareTeaVMClassLib: one include line per classlib class
# (manifest target paths give the entry name; TCharacter needs TCharacter*)
#
# c. Core shadows (compiler/src/patches/java/...): before applying, refresh the pristine base they
# were diffed against from the core sources jar your fork builds with:
node scripts/upgrade.mjs --refresh-upstream # finds teavm-core-<ver>-sources.jar in the
# Gradle cache; --core-sources <jar> overrides
# 3. rebuild everything, inside the container
docker build -f patches/Dockerfile -t teavm-javac-build .
docker run -d --name teavm-build -v teavm-gradle:/root/.gradle teavm-javac-build tail -f /dev/null
tar -czf ../src.tgz --exclude=./compiler/build --exclude=./javac/build \
--exclude=./protocol/build --exclude=./.gradle --exclude=./dist .
docker cp ../src.tgz teavm-build:/tmp/src.tgz
docker exec -d teavm-build bash -c "tar -xzf /tmp/src.tgz -C /work && cd /work && bash ./gradlew \
:compiler:generateClassLib :compiler:buildTeaVMClassLib :compiler:optimizeWasmGC \
--console=plain --no-daemon > /tmp/build.log 2>&1; echo done > /tmp/done"
# 4. vendor the five artifacts (see "Vendoring" below)
# 5. run the acceptance gatedocker cp teavm-build:/work/compiler/build/generated/teavm/wasm-gc-optimized/compiler.wasm assets/
docker cp teavm-build:/work/compiler/build/generated/teavm/wasm-gc-optimized/compiler.wasm-deobfuscator.wasm assets/
docker cp teavm-build:/work/compiler/build/generated/teavm/wasm-gc/compiler.wasm-runtime.js assets/
docker cp teavm-build:/work/compiler/build/classlib/compile-classlib-teavm.bin assets/
docker cp teavm-build:/work/compiler/build/classlib/runtime-classlib-teavm.bin assets/
# put the new sha256 values in patches/manifest.json, then:
npm run build && npm testCopy to assets/ before running npm run build. The build copies assets/ over dist/; a stale
assets/ silently replaces good binaries with old ones — that happened once (commit d871a87) and the
symptom was a freshly built feature that "did nothing".
A version bump is not done until all four pass:
node scripts/upgrade.mjs # inventory consistent (checksums + markers)
set TEAVM_JAVAC_DIST=<fork>/dist/teavm-javac
set JAVA_HOME=<JDK 25>
node scripts/sdk-check.mjs # 7/7
node scripts/compile-program.mjs --all # 145/145 compiled
node scripts/run-program.mjs stringformat --jdk # MATCH (engine sanity)
node scripts/run-program.mjs exceptions --jdk # MATCH
node scripts/run-program.mjs enum-map-set --jdk # MATCH
node scripts/run-program.mjs exceptions-finally --jdk # MATCH
node scripts/run-program.mjs file-io # ok — do NOT gate on its --jdk comparison: the
# engine models a POSIX filesystem, so File.isAbsolute
# answers differ from a Windows JDK by design. The
# corpus (against Doppio, a Linux JVM) is the authority.
node corpus/run.mjs teavm # 145/145, and diff the output against the committed baselineThe corpus is the real oracle: corpus/results.json is tracked, so git diff corpus/results.json
after a run shows exactly which programs changed behaviour. A version bump that keeps 145/145 and a
clean results.json diff is a safe upgrade; anything else needs triage before it ships.
Most of what we vendored is a gap that a newer TeaVM may close. --diff prints DROP? when the
upstream source already contains the marker we look for, and every classlib entry has a dropWhen
field saying what would make it unnecessary. To drop one:
- Delete the file from
compiler/src/sharedClasslibEmu/...and from theprepareTeaVMClassLibinclude list in the fork, and frompatches/+ the manifest here. - Rebuild. If the program that motivated it still passes, the gap really is closed.
Never drop a core shadow without checking that the upstream fixed it: those are fixes to TeaVM
itself (vtable implementors, class-name metadata, the statement emitter, the virtual filesystem), and
upstream 0.14.1 does not have them. The two new-* entries have no upstream counterpart at all.
- Core internals move between versions.
BaseWasmGenerationVisitordoes not exist in TeaVM 0.14.1's Git tag — the fork's core has it, upstream calls itWasmGCInstructionGenerationVisitor— so a "just fetch the upstream source" diff is impossible for core classes. That is why the pristine bases live inpatches/upstream/, taken fromteavm-core-<version>-sources.jar— the exact jar the fork compiles against, e.g.~/.gradle/caches/modules-2/files-2.1/org.teavm/teavm-core/0.14.1/*/teavm-core-0.14.1-sources.jar. On an upgrade runnode scripts/upgrade.mjs --refresh-upstreambefore trusting the diff. - A patch's assumption can rot. The concrete checks live in
patches/README.mdunder each patch; the ones with adropWhenare the first to look at. - The classlib diffs are against the 0.14.1 Git tag, not the jar. The exact base the class
library jar was built from is
teavm-classlib-0.14.1-sources.jar; the tag differs in at leastTString(it importsorg.teavm.interop.Intrinsified, which exists in no 0.14.1 jar). The tag is close enough to see your changes in a diff — which is what--diffis for — but never copy a file from the tag into the build. - Artifacts must be rebuilt, not carried over. The compiler embeds
DivisionCheckTransformer,CompilerResourceSupplierand the two dependency listeners, so a class-library-only rebuild leaves them out.patches/README.mdsays for each patch whether it needs:compiler:optimizeWasmGCor only the two archives (which is ~30 s instead of ~5 min). compiler.wasmis never byte-identical after a rebuild (wasm-optdiffers); the runtime glue is. Always run the corpus before committing a rebuilt compiler.
| Tool | What it is for |
|---|---|
node scripts/upgrade.mjs |
inventory + checksum + marker check; --diff regenerates and proves the diffs |
scripts/run-program.mjs |
run one program against the JDK, with --trace (named Java frames) and --emit (the module) |
scripts/generate-check.mjs |
generate + validate a program's module without running it |
wasm-dis from Binaryen 133 |
disassemble a generated module with Java names — the npm binaryen binding rejects TeaVM's output |
corpus/run.mjs |
the oracle: 145 programs, both engines, results.json diffable |
TEAVM_OPT_LEVEL |
compare Simple vs Advanced codegen when a failure looks like an optimizer bug |