CI: one workflow, planned lanes, toolchain images, and the JavaScriptCore tests - #645
Conversation
Nothing in CI ran a test. Add a `test` job that downloads the jsc shell shipped
by the bun-webkit-linux-{amd64,arm64}-asan lanes and runs
run-javascriptcore-tests (JSTests, LayoutTests/js, PerformanceTests) and testFFI
with it. The job does not gate the release and is continue-on-error until the
known failures on this fork are worked through.
Drop continue-on-error from the test job so a test failure turns the run red. The release still does not wait for the tests. The preview-build comment is now posted whenever the release was published rather than only when the whole build workflow succeeded, so it keeps appearing while tests are failing.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe change replaces platform-specific build scripts and workflows with centralized lane planning, Docker toolchain builds, unified CI testing, and release handling. It also updates cross-compilation Dockerfiles, centralizes ICU metadata, and updates JavaScriptCore test execution and diagnostics. ChangesUnified CI workflow
Suggested reviewers: Priority: ➖ Normal Merge Risk: 🟡 Moderate · up to Some CI runs can select the wrong test configuration, skip JavaScriptCore diagnostics after a testFFI failure, or build the Bun-specific code without the required feature guard. These issues should be resolved before merge. 🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
Warning Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use Comment |
The 42 lanes were spread over seven near-identical jobs plus a hand-kept list of expected assets in the release job. They are now defined once, in .github/scripts/plan.mjs; a `plan` job turns that table into the build, test and expected-asset matrices. Every lane gets the same runner, release script and settings as before. The lanes that are tested build in their own job (same steps, via an anchor) so that the tests start when those lanes are done instead of waiting for all 42. Also drops the unused llvm_version input, and only triggers CI on pushes to main rather than starting a skipped run for every branch.
…usable.yml build.yml (pushes to main) and build-preview.yml (pull requests) were thin wrappers that called build-reusable.yml with a different commit, release tag and prerelease flag. They are now one workflow, ci.yml, with all three triggers: the plan job works out what is being built, and the pull request comment is a final job that only runs for pull requests. Tags, the write-access check for pull requests, manual dispatch with pr_number and the per-pull-request concurrency group are unchanged.
What differs between a build of main and a build of a pull request is now nine lines: the commit (env.REF), the release tag, the prerelease flag, the concurrency group and a one-line `gh pr comment` after publishing. The separate target-resolution step and the 60-line comment job are gone. Dropped: the explicit write-access check (a fork's token is read-only, so such a build already fails when it creates the release) and manual dispatch by pr_number. Manual dispatch now builds the head of the branch it is run on.
…r lane It was the one lane that still built on a Windows runner, because /MTd needs the static debug CRT (libcmtd.lib, libcpmtd.lib, libvcruntimed.lib) and an xwin splat has none for ARM64. The libs exist: for x64 they are in the same Visual Studio package as the release CRT, for ARM64 Microsoft ships them in a separate package, CRT.ARM64.Desktop.debug.base, that xwin does not select. Dockerfile.windows now fetches that package (pinned by sha256) and unpacks its libs next to the others. The lane takes the same settings as bun-webkit-windows-amd64-debug with WIN_ARCH=arm64. With it gone, the LLVM install and PowerShell steps are gone from the workflow: every lane is checkout, buildx, build, upload.
…e per lane Every one of the 42 lanes rebuilt its toolchain from scratch on every run: apt, the LLVM and GCC debs, the xwin download, the macOS SDK, the NDK. The Dockerfiles' `base` stages are now pure toolchain stages (no lane settings; the glibc and musl Dockerfiles get a `lane` stage on top for those), and CI keeps them as images in ghcr.io/oven-sh/bun-webkit-build-env, tagged with a hash of what goes into them. A new `image` job, one leg per distinct toolchain (10), builds and pushes an image only when its tag is missing, i.e. after a change to a `base` stage. Lanes start from their image (--build-context base=docker-image://...). If an image is not there a lane builds the toolchain itself, which is also what running a release script by hand still does. The ICU and WebKit stages are unchanged. Modelled on the bun-toolchain workflow in oven-sh/rust.
The image job warned and carried on when a push failed, and lanes fell back to building the toolchain themselves when their image was missing, so a broken registry setup would only show up as every lane being slow. Now the image leg fails, no lane starts, and a lane always builds from its image.
release.sh, musl-release.sh, macos-cross-release.sh, windows-cross-release.sh, freebsd-release.sh and android-release.sh were six copies of the same wrapper around `docker buildx build`, each also deriving a few settings that were visible nowhere else (MARCH_FLAG per architecture, WIN_TRIPLE_ARCH, ICU_MARCH_FLAG, ENABLE_MALLOC_HEAP_BREAKDOWN=ON for macOS Debug, FREEBSD_VERSION, ANDROID_API). What a lane was built with was spread over the plan, a script and the Dockerfile's defaults. .github/scripts/lanes.mjs (was plan.mjs) now holds every build argument of every lane and is the only way one is built: lanes.mjs build <label> --output <dir> [--base-image <ref>] lanes.mjs image <name> --push CI runs exactly that, so a lane can be reproduced by running the same command. For all 42 lanes the docker command is the same as the one the scripts on main produce: same Dockerfile, platform and build arguments.
There was a problem hiding this comment.
Findings marked 🟡 are optional suggestions and need no follow-up push.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🔴
.github/workflows/build-preview.yml—notifygates onneeds.build.result == 'success', and the reusable workflow's result now includes the newtestjob: whenever a JavaScriptCore test fails the called workflow's aggregate result isfailure, so the PR "Preview Builds" comment is never posted/updated even thoughreleasestill publishes the tarballs. On base every published preview got a comment; after this change any test flake hides an otherwise-good release from the PR. Fix: gatenotifyon the release having been published rather than the whole workflow succeeding, e.g.if: always() && needs.build.outputs.release_tag != ''(the reusable workflow already exports that output only on publish).Extended reasoning...
build-preview.yml calls build-reusable.yml as job
build. GitHub setsneeds.build.resultto the aggregate of every job inside the called workflow. This PR adds atestjob (build-reusable.yml:226-337) that runsrun-javascriptcore-tests --no-fail-fastand exits non-zero on any failing test. When that happens:release(which does not needtest) still runs, sees all builds succeeded, and publishes the draft (build-reusable.yml:381-385); but the reusable workflow's overall conclusion isfailurebecausetestfailed. Back in build-preview.yml,notifyhasneeds: [trigger, build]with noalways(), so it is skipped, and itsif: needs.build.result == 'success'would fail anyway. The preview release exists on GitHub but the PR comment linking to it is never created or updated. On the base branch there was notestjob, sonotifyfired for every published preview. The PR description says test failures are meant not to gate the release; this side-effect on the PR-comment path is not called out and leaves authors without the release link the workflow is meant to post.Verification: normal — The scenario is real and reachable.
.github/workflows/build-preview.yml:88still gatesnotifyonif: needs.build.result == 'success', wherebuildis the reusable-workflow call (uses: ./.github/workflows/build-reusable.yml). GitHub sets a caller job'sresultto the called workflow's overall conclusion, which isfailureif any job inside it fails. This PR addstestto the…
- A tested lane that fails to build no longer skips the tests of the ones that did build: `test` runs whenever `build-tested` ran, and the leg of the lane that did not build fails at its download. - testFFI and run-javascriptcore-tests each run whatever the other did; either failing fails the job. Before, a testFFI failure skipped the JavaScriptCore tests, leaving no results or log. Also says what `native` means in lanes.mjs.
|
Preview build of a5ca21e: |
JSTests/wasm.yaml runs LayoutTests/imported/w3c/web-platform-tests/wasm/{core/js,jsapi}
with the harness in web-platform-tests/resources; the test job's sparse checkout
left them out and run-jsc-stress-tests died in `realpath` before running a test.
The log filter that drops "Skipping <test>" lines also dropped that error,
which was printed onto the end of one: it now only drops lines that are nothing
but a skip.
The glibc and musl Dockerfiles built for whatever architecture the container was, so the arm64 lanes needed arm64 runners and their own toolchain images. They now build in the same linux/amd64 container as the x86_64 lanes, with clang --target and a sysroot: - glibc: an ubuntu 20.04 arm64 sysroot (the arm64 ubuntu:20.04 image, focal's libc6/libc6-dev unpacked over it, and the arm64 half of the gcc-13 mirror), plus the aarch64 sanitizer runtimes from the arm64 half of the LLVM mirror. - musl: an alpine aarch64 sysroot populated by apk from the same repository. - ICU is a two-stage cross build for aarch64 (the container's own ICU tools live in the toolchain image), as in the FreeBSD and Android Dockerfiles. The x86_64 lanes run the same commands as before. The libc versions do not change, and that is now checked: the image build fails unless the sysroot's glibc is 2.31 like the container's (for musl: the same package version as the container's), and every glibc jsc that is linked must not need a symbol version newer than GLIBC_2.31. Every lane now builds on linux-x64-gh; arm64 runners only run tests. 8 toolchain images instead of 10. The musl stages no longer install packages or build zstd per lane: that moved into the toolchain image.
Every run queued one `image` leg per toolchain on the large runners only to find the image already there. `plan` (a standard runner, and it already knows the tags) now asks the registry itself and hands `image` only the missing ones; when there are none, `image` is skipped and the lanes start right after `plan`. The legs that do run, after a change to a Dockerfile's `base` stage, use a standard runner too: every image is linux/amd64 and building one is mostly downloading and unpacking. They free the usual disk space first. The registry token is passed to `docker login` through the environment rather than written into the script.
The toolchain images took the package installs, SDKs and sysroots out of the lanes, but every lane still fetched a few things and redid some lane-independent work: ICU's source tarball (all but macOS), its host tools (Windows, FreeBSD, Android), zstd, node and `patch` (Windows), and bootstrap_cmds to build `mig` (macOS). Those are now in each Dockerfile's `base` stage, so in the image. The stages on top only apply the lane's settings. On Windows the host ICU tools are therefore built from the sources before the udata.cpp hook patch is applied rather than after; the hook is null in ICU's own tools either way. The macOS image now depends on all of macos-cross/, not just xmac.mjs.
Each image leg used to delete the older versions of its own image as soon as it pushed a new one, on main only. That missed images nothing uses any more (a renamed toolchain, a pull request that never landed) and had no notion of age. A build of main now runs `prune`: every version of the package that main's lanes do not use and that was pushed more than 14 days ago is deleted, untagged leftovers included. An image a pull request pushed recently stays, so a toolchain change can be iterated on before it is merged; if an image a branch still wants has gone, `plan` finds it missing on that branch's next run and `image` rebuilds it.
There was a problem hiding this comment.
Findings marked 🟡 are optional suggestions and need no follow-up push.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟡
Dockerfile.macos— nit: this comment still points readers atbuild-reusable.yml("Matches the native macOS lane's CMAKE_C_FLAGS (build-reusable.yml)"), which this PR deletes; after merge the reference is dangling and there is no native macOS lane in any workflow. Fix: update the comment to point at.github/scripts/lanes.mjs(ormac-release.bash) so future readers can find where these flags are kept in sync — this is the one remaining reference to the deleted workflow files across the repo.Extended reasoning...
A repo-wide grep for the deleted filenames (
build-reusable,release.sh,musl-release.sh,macos-cross-release.sh, etc.) turns up exactly one leftover: Dockerfile.macos:35 referencesbuild-reusable.yml. On the base branch that pointed at the CMAKE_C_FLAGS matrix in .github/workflows/build-reusable.yml; after this PR that file no longer exists and the flags live in .github/scripts/lanes.mjs (macos platformargs()) and mac-release.bash. Purely a documentation drift — no build behaviour is affected — but it is the sort of stale pointer that misleads the next person bumping DEFAULT_CFLAGS.Verification: nit — Dockerfile.macos:35 reads
# Matches the native macOS lane's CMAKE_C_FLAGS (build-reusable.yml) plus -g, and this PR deletes.github/workflows/build-reusable.yml(git diff --name-status showsD .github/workflows/build-reusable.yml; onlyci.ymlandmirror-llvm-debs.ymlremain in.github/workflows/). A repo-wide grep forbuild-reusablereturns only this one line, so the… | nit —…
…guments - A pull request from a fork gets a read-only token, so it could never publish, but nothing stopped its 42 lanes from occupying the large runners for an hour before failing at the upload (the old workflow refused such runs up front). `plan` is now skipped for them, and everything else needs `plan`. - A toolchain image's tag hashed its Dockerfile's `base` stage and the files it copies, but not the build arguments that stage takes from lanes.mjs (FREEBSD_VERSION, MACOS_DEPLOYMENT_TARGET, ANDROID_API, ...): bumping one left the tag unchanged and the lanes on the old toolchain. The ARGs a `base` stage declares that a lane sets are now part of the hash. - Dockerfile.macos: a comment still named the deleted build-reusable.yml.
…e builds - Creating the draft release was a job of its own next to `plan`. It is now the last step of `plan`, which already names the release. Every lane needs `plan`, so the draft exists before any lane starts, and the loop in which each lane waited up to ten minutes for it to appear is gone. - linux image: there are no *.list files in /etc/apt/sources.list.d in this container, so the glob handed to sed stayed literal and sed failed. - linux-musl image: /usr/share/apk/keys/aarch64/* are symlinks to ../<key>.pub, and cp -r copied them as dangling links, so apk trusted no key for the aarch64 index and found no packages. They are dereferenced now.
Builds: - aarch64 glibc sysroot: libc6-dev's libm.so, libpthread.so, libdl.so, ... are absolute symlinks into /lib/aarch64-linux-gnu, which dangle when the package is unpacked into a sysroot, so the linker quietly took the static archives. They are re-pointed into the sysroot, their presence is checked, and every glibc jsc must list libm.so.6 as NEEDED. - The glibc and musl version guards did nothing: under `set -e` a failing test that is not last in an `&&` list does not abort. They are separate statements. - The aarch64 sanitizer runtimes must be the version of the clang installed. Workflow: - prune: only on the first attempt of a push to main (a re-run of an old run knew an old set of images), and the two newest versions of each toolchain main has always stay, so the image main used until a moment ago is not pulled from under runs still in flight. - plan: a branch that predates lanes.mjs gets told so; a registry that cannot be asked is an error, not eight missing images; two images may not share a name; at least one lane must be tested and one not (no empty matrices). - test takes the jsc shell from an artifact of build-tested instead of the draft release, which `release` deletes the moment any lane fails. - A lane checks that there is a release to upload to before it builds, not after. - The image tag no longer changes with comments, lane-only ARG defaults or macos-cross/README.md. - Build and test checkouts skip about 2 GB nothing reads; dead outputs, ids and an API call left over from the reusable workflow are gone.
The `base` stage's LDFLAGS carry -L/usr/lib/x86_64-linux-gnu, where the distribution's ICU 66 lives (libxml2-dev brings libicu-dev), and that came ahead of the freshly built libicuuc.a: makeconv failed to link with undefined uprv_stricmp_78 and friends. The lanes' ICU step already replaces LDFLAGS for the same reason; the host tools step does now too.
`linux` sat next to `linux-musl` and `android`, which are Linux too; the job was called "image linux" though only the ten glibc lanes use it.
Dockerfile: TARGETARCH is always amd64, so the gcc-13 and LLVM bundles and their checksums are named directly, the library-path step is unconditional, and its `uname -m` twin (which did the same thing a second time) is gone. The LLVM symlink step ran twice, before and after the last apt-get install; the second run, whose result is the one that stands, stays. Dockerfile and Dockerfile.musl: CPU was an ARG and an ENV that no lane passes and nothing reads. musl never used LLVM_VERSION (clang-21 is spelled out). Dockerfile.freebsd, .android, .windows: the ICU_* ARGs in build_icu were only for the ADD that moved to `base`. Dockerfile.macos: two comments still spoke of native macOS lanes. No build argument, environment variable that is read, or command that affects an artifact changes.
Nothing runs them. CI builds every lane, macOS and Windows included, through lanes.mjs and the Dockerfiles; Bun builds WebKit from source with its own scripts/build.ts. Each of the three carried its own copy of the compiler flags and cmake options, already out of step with what ships (build.ts still looked for ICU under vcpkg_installed/), and a stale script that looks authoritative is how a wrong flag gets copied somewhere it matters. build-icu.ps1 stays: Bun runs it when it builds WebKit from source on Windows. It now says so.
The previous change read PROCESSOR_ARCHITEW6432 and PROCESSOR_ARCHITECTURE. Strawberry Perl is x64, and an x64 process on Windows on ARM runs emulated: it has AMD64 in %PROCESSOR_ARCHITECTURE%, and PROCESSOR_ARCHITEW6432 is only set for 32-bit processes, so that still said x86_64 on windows-11-arm. HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment's PROCESSOR_ARCHITECTURE is the machine's, whatever the process is. oven-sh/bun's scripts/bootstrap.ps1 detects ARM64 the same way, for the same reason. Not exercised: the Windows lanes are not tested at the moment. The query's output format is as the pattern expects on a Windows x64 machine (AMD64).
…modes that collect continuously It segfaulted (exit 139, no output) in the ftl-eager mode on linux-arm64-lto, once, and with publishing now dependent on the tests that held back the release. On x64 with the ftl-eager options it crashes in JIT code, called from llint_op_call at the top level of the script, 13 times in 320 on a loaded machine (ASan build; 1 in 320 without ASan). It needs eager tiering and --collectContinuously=true together: 0 of 320 with either alone. Upstream's own jsc at ccdcb8a, built the same way, does the same, 19 of 640, so this is not the fork's; upstream's bots show 1 failure in about 48,000 runs of these modes. The cause is not known. dfg-eager, dfg-eager-no-cjit-validate, ftl-eager and ftl-eager-no-cjit are the default modes that have both ingredients.
…e stack For a DirectTailCall to a known host function, DFG and FTL shuffle the frame for the tail call and then call the host function inline, from their own code (emitCallTarget(): prologue, null CodeBlock slot, call, exception check, epilogue, ret). After the shuffle no frame names this CodeBlock: the frame is the host function's, with a null CodeBlock slot. The only thing left that points into this code is the return address, which the conservative scan does not map to a CodeBlock. If the host function triggers a collection and nothing else keeps the caller alive, the CodeBlock dies, its executable memory is freed (and reused), and the host function returns into it. stress/shadow-realm-remote-function-copy-length-and-name.js hit it: the ShadowRealm builtins tail-call the host function createRemoteFunction, which allocates. With eager tiering and --collectContinuously=true --useGenerationalGC=false together (the dfg-eager and ftl-eager modes) it segfaulted in 11 to 16 of 640 runs on a loaded x86_64 machine, once on linux-arm64-lto in CI; upstream's jsc at ccdcb8a does the same, 19 of 640. The freed DFG code, saved before it was freed, has the inline call, and the fault is at its return address. The inline call stays. Before it, the CodeBlock's pointer is stored in a stack slot below the host function's frame, where the conservative scan finds it and keeps the CodeBlock alive (and in CodeBlockSet's currently-executing set); the epilogue that was already there drops the slot. 0 of 1,280 runs with the minimal options and 0 of 1,280 with the ftl-eager mode's. A native tail call from optimized code costs the same, 18.0 ns against 18.0 ns. The test runs in every mode again.
`release` needs the builds only, as before b7bc14f. The test job still runs on the tested lanes and goes red when a test fails; nothing waits for it. One intermittent failure in about 120,000 test runs (an upstream JIT bug, since fixed here) was enough to stop a preview from being published.
main has the same bug fixed another way (#649): a direct tail call to a host function no longer takes the inline call path, and links through the executable's host call thunk, which lives as long as the VM and returns to the caller's caller. It comes with a regression test that frees the caller's code deterministically. This branch kept the inline call and parked the CodeBlock's pointer in a stack slot across it (f66e3bc). Both fix the crash. The inline tail call is about 0.7 ns faster per call (17.6 ns against 18.4 ns); main's version leaves no JIT code running without a frame that names its CodeBlock, which is the assumption CodeBlockSet's currently-executing set states. DFGSpeculativeJIT64.cpp and FTLLowerDFGToB3.cpp are main's.
The preview release autobuild-preview-pr-645-b6d2430a: #645 merged with oven-sh/WebKit main, which brings the fix for a direct tail call to a host function returning into freed JIT code (oven-sh/WebKit#649). Temporary: once #645 is merged this becomes the sha of the autobuild from main.
Reverts 46650cc ("The asan lanes are built with USE_SYSTEM_MALLOC") and 1bd0367 (its Windows exception): the asan and debug-asan variants pass no USE_SYSTEM_MALLOC, and the Dockerfiles no longer take it. They build as they do on main. No other lane ever set the argument. With the system allocator, every fastMalloc in WTF, JavaScriptCore and Bun's C++ is an ASan allocation. In Bun's asan test lane that made LeakSanitizer see allocations it could not see in libpas, freed JavaScriptCore memory count as RSS through ASan's 256 MB quarantine, and max_allocation_size_mb cap JavaScriptCore's own allocations. One build of that lane: 81 failing test files against 0, 559 minutes of shard time against 108, 4 of 20 shards past the job's time limit. Of those, 15 files compare RSS with exact limits that are not to be retuned.
The preview release autobuild-preview-pr-645-7022a119. Its asan lanes are built with bmalloc/libpas again, as on oven-sh/WebKit main: with the system allocator the asan test lane had 81 failing files and took five times as long. Temporary: once #645 is merged this becomes the sha of the autobuild from main.
Alpine's clang defines _FORTIFY_SOURCE=2 by itself and looks for the checked libc wrappers in <sysroot>/usr/include/fortify. In the container they come with clang (a dependency); the aarch64 sysroot has no clang and never got them, so since the arm64 lanes became a cross build they were compiled with the define and nothing behind it (musl has no fortify of its own), while the x86_64 lanes kept the checks. Against main's build of the same sources, in bun-webkit-linux-arm64-musl: libWTF.a's code is 15% smaller (StringImpl.cpp.o 140,544 -> 60,548 bytes, WTF::equal(StringImpl&, StringImpl&) 6,108 -> 1,612, 38 brk traps -> 0), libJavaScriptCore.a 0.7%, ICU 1.4 to 3.5%; the bun that links the -lto lane lost 472 KB of .text. The arm64 glibc lanes are unaffected (every object of libWTF, libbmalloc and libicuuc has the same code size as main's). The sysroot's fortify-headers must be the container's version, and the toolchain check compiles a memcpy for both and requires the trap in each. This changes `base`, so the musl toolchain image is rebuilt.
… does Alpine gives clang its default flags in /etc/clang21/<triple>.cfg, read for the triple being compiled for. The clang package installs the container's (x86_64-alpine-linux-musl.cfg: -fstack-clash-protection); Alpine's aarch64 clang package installs the same file as aarch64-alpine-linux-musl.cfg, and in this x86_64 container nothing did, so the cross-built arm64 lanes were compiled without it. With fortify-headers back, that was what was left of the difference from main's native build of bun-webkit-linux-arm64-musl: 11 of libWTF.a's 175 objects 144 bytes smaller in all, 55 of libJavaScriptCore.a's 196. The file is copied, so the aarch64 lanes get whatever Alpine puts there, and the toolchain check requires clang to pass itself the same protection flags for both targets.
The preview release autobuild-preview-pr-645-4d198c60. Its cross-built linux-arm64-musl lanes have _FORTIFY_SOURCE's checked libc wrappers and -fstack-clash-protection again, as the x86_64-musl lanes and main's native arm64-musl build do: every object of libWTF, libbmalloc and ICU has the code size of main's, and libJavaScriptCore differs only in the two objects #645 changes. Temporary: once #645 is merged this becomes the sha of the autobuild from main.
…d why JSTests/BUN-TEST-DIFFERENCES.md. All 120 changed test files and the three runner scripts, grouped by reason, for whoever has a failing test after an upstream sync and needs to know whether the test is the thing to change: - nine behaviors the fork has on purpose (ReferenceError text, one-line native toString, module link error wording, import attributes, cyclic array join, /u matches and surrogate pairs, when a time-zone change is seen, builtin frames' URL, bytecode-cache strings), each with the fork code that causes it and the files whose expectations follow it; - tests narrowed to the modes or builds they can pass in, with the reason; - the seven skipped with TODO(bun), by the gap each waits on; - the rules an edit to an upstream test follows, and the steps for sorting a new failure.
Under Testing: what the file is, that it is read before a test's expectation is changed or a test is skipped, and that a changed test is added to it. The CI/CD paragraph mentions it where the failing tests' summary is described. JSTests/CLAUDE.md and README.md are upstream's and stay as they are.
| - Files (6): `LayoutTests/js/` `array-string-recursion`, `array-tostring-and-join`, `toString-recursion` (each: the | ||
| script under `script-tests/` and the `-expected.txt`). `array-string-recursion` is this fork's previous version of | ||
| the test (upstream rewrote it to expect the `RangeError`). | ||
|
|
There was a problem hiding this comment.
🟡 nit (optional): A5 cites ArrayPrototype.cpp:329 as the toString recursion guard, but line 329 is inside arrayProtoFuncToLocaleString; Array.prototype.toString is guarded in JSArray::fastToString (JSArray.cpp) instead, which the entry never names. A maintainer following section E ("check that the fork code named there is still in the tree") after a sync that dropped the fastToString guard would still find a StringRecursionChecker at :329 and wrongly conclude the behavior is intact. Fix: label :329 as toLocaleString and add the JSArray::fastToString site so the entry names every guard [a,a].toString() actually depends on.
Extended reasoning...
arrayProtoFuncToString (ArrayPrototype.cpp:259) has no StringRecursionChecker; it delegates to asArray(thisValue)->fastToString() (line 286) or to join. StringRecursionChecker at ArrayPrototype.cpp:329 sits inside arrayProtoFuncToLocaleString (defined at line 315), and the toString path's guard is the #if USE(BUN_JSC_ADDITIONS) block in JSArray::fastToString (JSArray.cpp:1048–1060). The doc's stated workflow (section E step 1: "Check that the fork code named there is still in the tree, then update the expectation") relies on these citations being the code that produces the behavior; here the cited line is a different function's guard, so it can survive a sync that removes toString's. Doc-only; nothing changes at runtime.
Verification: nit — the doc's citation is wrong as described. BUN-TEST-DIFFERENCES.md:84 reads: "StringRecursionChecker in runtime/ArrayPrototype.cpp:329 (toString) and :470 (join)". But in /home/claude/webkit/Source/JavaScriptCore/runtime/ArrayPrototype.cpp: - Line 329 (StringRecursionChecker checker(globalObject, thisObject);) is inside arrayProtoFuncToLocaleString, defined at line 315. -…
… the build graph (#42556) ### What does this PR do? Re-lands the parts of #41330 that do not depend on compiling JavaScriptCore and ICU inside bun's own build graph. bun keeps linking the prebuilt `bun-webkit-*` tarballs; nothing here changes how WebKit or ICU are obtained. One commit per topic, so any of them can be reverted alone. **Build graph** - Generated headers were one build late: a header that a depfile names by absolute path did not match its relative ninja output, so includers were recompiled on the *next* run. Every build-dir output is also declared under its absolute path. - Every build after a build-script edit configured twice (`[0/1] reconfigure`). Configure stamps the manifest and `ninja -t restat`s it. - `bun run build` never reached a no-op with an isolated-layout `node_modules`: the install edge's outputs made ninja pre-create `node_modules/<dep>/`, and bun's isolated linker then skipped the symlink. - The link is an ordinary edge instead of holding ninja's console pool. **Link-time checks** (`scripts/build/verify-binary.ts`, expectations per target in `binary-expectations.ts`), as ninja validations of bun's link in every mode that links: exported symbols against the lists in `src/`; the exact NEEDED / dylib / import-DLL set and symbol-version ceilings; forbidden imports; static initializers; nx-stack / no-RWX / PIE / DllCharacteristics; no symbol strongly defined by two link inputs. A check whose tool is missing is not emitted. `ninja_required_version` is 1.11 (validations); `rust-toolchain.toml` gains `llvm-tools`. - `bun.exe` links the prebuilt WebKit, whose bmalloc is compiled with `BEXPORT=__declspec(dllexport)`, so it exports 22 bmalloc symbols and `g_config`. They are a named temporary allowance in the PE export expectations, to be removed once oven-sh/WebKit builds bmalloc with `BEXPORT` empty. **Compiler policy, stated instead of inherited from the host compiler** - `-fno-stack-protector` on Linux, FreeBSD and Android (it was off with CI's clang, `-fstack-protector-strong` with Arch/Alpine/Fedora's). macOS states `-fstack-protector`, which every clang turns on for a Darwin target, so the macOS binaries keep their canaries. - Vendored deps follow bun's PIC policy instead of the building clang's default. - windows-arm64 without `-mtune=ampere1`. **Windows** - The exe's import library no longer overwrites the object archive (`obj/<exe>.import.lib`). - `bun.exe`'s manifest declares `supportedOS` (Windows 7 through 10/11) and `asInvoker`, so Windows stops running bun in the Vista compatibility context. Visible effect: `process.report`'s `header.osRelease` reads `10.0` instead of `6.1` on Windows 10/11. **Runtime symbols** - `napi_internal_*` → `Bun__napi_*`: the `napi*` glob in the version scripts exported them on Linux and FreeBSD. - `offsetOfWrapped()` in the generated classes is `constexpr`: 93 fewer static initializers in `-O0` builds. **CI** - Linux images are baked by a Buildkite job on a fresh machine of the target distro plus a `wait-for-image` step. Only `[build images]` / `[publish images]` runs change; ordinary runs generate the same pipeline. - `verify-baseline` and `trace-order` are drawn beside each target's build group; step keys and dependencies are unchanged. - verify-baseline-static: decoder resync. **Release is LTO by default**, locally as in CI; `--lto=off` for fast relinks. The `btg` and `ci-release` profiles are gone and fail with what to use instead. Not included from #41330: JavaScriptCore/ICU in the build graph, the ccache environment change, any change to `bootstrap.sh` / `bootstrap.ps1`. **WebKit** is bumped from `cf1b36ec8703` to `9b02218df662`, oven-sh/WebKit `main` with oven-sh/WebKit#645 merged: #636, #634, #632, #652, #646, #649 (a direct tail call to a host function could return into freed JIT code) and #645 (a parser `SyntaxError` with no JS frame on the stack has its `line`/`sourceURL` again; private builtins' source text stays out of error messages; a wasm-to-JS frame index fix). `test/js/bun/jsc/webkit-upgrade-9b02218df6.test.ts` covers what Bun can see of #645. Against main's build of the same sources, every prebuilt lane differs only in the two JavaScriptCore objects #645 changes. ### How did you verify your code works? - `test/internal` build tests pass (212/212), including new cases for the validations and for a missing tool; `scripts/build` typechecks apart from two files that already fail on main. - `--configure-only` for debug, release and each `--mode`: the link edge carries `|@ … binary-verified … duplicate-symbols-checked`; release resolves the `-lto` WebKit tarball, `--lto=off` the plain one, `--profile=btg` fails with the retired-profile message. - The verifier run by hand on existing binaries: passes on prebuilt-linked release and debug-asan ELF, and on the released Windows x64 `bun.exe` with the bmalloc allowance; the duplicate-symbol scan finds none over 1221 inputs including the prebuilt archives. - The Buildkite pipeline generated offline for seven scenarios: identical step keys, `depends_on` and dependency closure on ordinary runs; image runs match what #41330 generated. - The rest is for CI on this PR.
…/ICU in the build graph (oven-sh#42556) ### What does this PR do? Re-lands the parts of oven-sh#41330 that do not depend on compiling JavaScriptCore and ICU inside bun's own build graph. bun keeps linking the prebuilt `bun-webkit-*` tarballs; nothing here changes how WebKit or ICU are obtained. One commit per topic, so any of them can be reverted alone. **Build graph** - Generated headers were one build late: a header that a depfile names by absolute path did not match its relative ninja output, so includers were recompiled on the *next* run. Every build-dir output is also declared under its absolute path. - Every build after a build-script edit configured twice (`[0/1] reconfigure`). Configure stamps the manifest and `ninja -t restat`s it. - `bun run build` never reached a no-op with an isolated-layout `node_modules`: the install edge's outputs made ninja pre-create `node_modules/<dep>/`, and bun's isolated linker then skipped the symlink. - The link is an ordinary edge instead of holding ninja's console pool. **Link-time checks** (`scripts/build/verify-binary.ts`, expectations per target in `binary-expectations.ts`), as ninja validations of bun's link in every mode that links: exported symbols against the lists in `src/`; the exact NEEDED / dylib / import-DLL set and symbol-version ceilings; forbidden imports; static initializers; nx-stack / no-RWX / PIE / DllCharacteristics; no symbol strongly defined by two link inputs. A check whose tool is missing is not emitted. `ninja_required_version` is 1.11 (validations); `rust-toolchain.toml` gains `llvm-tools`. - `bun.exe` links the prebuilt WebKit, whose bmalloc is compiled with `BEXPORT=__declspec(dllexport)`, so it exports 22 bmalloc symbols and `g_config`. They are a named temporary allowance in the PE export expectations, to be removed once oven-sh/WebKit builds bmalloc with `BEXPORT` empty. **Compiler policy, stated instead of inherited from the host compiler** - `-fno-stack-protector` on Linux, FreeBSD and Android (it was off with CI's clang, `-fstack-protector-strong` with Arch/Alpine/Fedora's). macOS states `-fstack-protector`, which every clang turns on for a Darwin target, so the macOS binaries keep their canaries. - Vendored deps follow bun's PIC policy instead of the building clang's default. - windows-arm64 without `-mtune=ampere1`. **Windows** - The exe's import library no longer overwrites the object archive (`obj/<exe>.import.lib`). - `bun.exe`'s manifest declares `supportedOS` (Windows 7 through 10/11) and `asInvoker`, so Windows stops running bun in the Vista compatibility context. Visible effect: `process.report`'s `header.osRelease` reads `10.0` instead of `6.1` on Windows 10/11. **Runtime symbols** - `napi_internal_*` → `Bun__napi_*`: the `napi*` glob in the version scripts exported them on Linux and FreeBSD. - `offsetOfWrapped()` in the generated classes is `constexpr`: 93 fewer static initializers in `-O0` builds. **CI** - Linux images are baked by a Buildkite job on a fresh machine of the target distro plus a `wait-for-image` step. Only `[build images]` / `[publish images]` runs change; ordinary runs generate the same pipeline. - `verify-baseline` and `trace-order` are drawn beside each target's build group; step keys and dependencies are unchanged. - verify-baseline-static: decoder resync. **Release is LTO by default**, locally as in CI; `--lto=off` for fast relinks. The `btg` and `ci-release` profiles are gone and fail with what to use instead. Not included from oven-sh#41330: JavaScriptCore/ICU in the build graph, the ccache environment change, any change to `bootstrap.sh` / `bootstrap.ps1`. **WebKit** is bumped from `cf1b36ec8703` to `9b02218df662`, oven-sh/WebKit `main` with oven-sh/WebKit#645 merged: oven-sh#636, oven-sh#634, oven-sh#632, oven-sh#652, oven-sh#646, oven-sh#649 (a direct tail call to a host function could return into freed JIT code) and oven-sh#645 (a parser `SyntaxError` with no JS frame on the stack has its `line`/`sourceURL` again; private builtins' source text stays out of error messages; a wasm-to-JS frame index fix). `test/js/bun/jsc/webkit-upgrade-9b02218df6.test.ts` covers what Bun can see of oven-sh#645. Against main's build of the same sources, every prebuilt lane differs only in the two JavaScriptCore objects oven-sh#645 changes. ### How did you verify your code works? - `test/internal` build tests pass (212/212), including new cases for the validations and for a missing tool; `scripts/build` typechecks apart from two files that already fail on main. - `--configure-only` for debug, release and each `--mode`: the link edge carries `|@ … binary-verified … duplicate-symbols-checked`; release resolves the `-lto` WebKit tarball, `--lto=off` the plain one, `--profile=btg` fails with the retired-profile message. - The verifier run by hand on existing binaries: passes on prebuilt-linked release and debug-asan ELF, and on the released Windows x64 `bun.exe` with the bmalloc allowance; the duplicate-symbol scan finds none over 1221 inputs including the prebuilt archives. - The Buildkite pipeline generated offline for seven scenarios: identical step keys, `depends_on` and dependency closure on ordinary runs; image runs match what oven-sh#41330 generated. - The rest is for CI on this PR.
…t that pins the text The lanes run the JavaScriptCore tests (#645), and JSTests/BUN-TEST-DIFFERENCES.md says how to record a difference that is on purpose. - 34 trap expectations in 18 JSTests/wasm files lose " (evaluating '...')". Each change has a `// Bun:` comment with the suffix upstream expects. assert.js's `throws` is a substring check, so they also pass on upstream. - BUN-TEST-DIFFERENCES.md gets section A10 with the fork code, how to recognize the difference, and the file list. - wasm/stress/trap-message-has-no-source-text.js compares `e.message` exactly for a trap (tail call, statement call, direct reference, unreachable) and for a SuspendError. It fails without the engine change. It builds its module from bytes, so it needs no wabt.
Summary
Nothing in CI runs a test today: every job builds and uploads. This adds a
testjob that runs JavaScriptCore's own suites with thejscshell the Linux asan lanes already ship, and restructures the build workflow around aplanjob so lanes are defined in one place.One workflow.
build.yml(main) andbuild-preview.yml(pull requests) were wrappers aroundbuild-reusable.yml. They are now a singleci.yml, and main and pull requests run exactly the same jobs. What differs is nine lines: the commit (env.REF, a pull request's head), the release tag (autobuild-<sha>vsautobuild-preview-pr-<n>-<sha8>, same names as before), the prerelease flag, the concurrency group, and a one-linegh pr comment --edit-lastafter publishing that replaces the 60-line comment script. Dropped: the explicit write-access check (a fork's token is read-only, so such a build already fails when it creates the release) and dispatch bypr_number; manual dispatch now builds the head of the branch it is run on, as a prerelease.Lanes from one table, built by one tool. The 42 lanes were seven near-identical jobs, a hand-kept list of expected assets in
release, and six*-release.shwrappers arounddocker buildx buildthat each derived a few settings visible nowhere else (MARCH_FLAGper arch,WIN_TRIPLE_ARCH,ENABLE_MALLOC_HEAP_BREAKDOWN=ONfor macOS Debug, ...)..github/scripts/lanes.mjsnow holds every lane: label, runner, Dockerfile, every build argument, tested or not.lanes.mjs planemits the matrices and the asset list;lanes.mjs build <label> --output <dir>builds a lane and is exactly what CI runs, so a lane can be reproduced by hand. The six scripts are deleted. Three workflow files totalling 973 lines become one of 421, test and image jobs included.Toolchain images. Every lane rebuilt its toolchain (apt, LLVM/GCC debs, xwin, macOS SDK, NDK) on every run. Each Dockerfile's
basestage is now a pure toolchain stage, kept as an image inghcr.io/oven-sh/bun-webkit-build-envtagged with a hash of what goes into it. A newimagejob (10 legs) builds and pushes an image only when its tag is missing; lanes start from theirs via--build-context base=docker-image://...and fall back to building the toolchain themselves if it is not there. ICU and WebKit stages are unchanged. Modelled onbun-toolchain.ymlin oven-sh/rust.Windows arm64 debug on Linux. The last lane that built on a Windows runner.
/MTdneeds the static debug CRT, which Microsoft ships for ARM64 in a separate package (CRT.ARM64.Desktop.debug.base) that xwin does not select;Dockerfile.windowsnow fetches it, pinned by sha256. Every lane is now the same four steps.Tests.
testruns onbun-webkit-linux-{amd64,arm64}-asan(Release + asserts + ASAN/UBSAN; plain Release compiles out$vmand the JIT disassembler). It downloads the tarball from the draft release, extractsbin/, runstestFFIandrun-javascriptcore-tests --no-fail-fast(JSTests, LayoutTests/js, PerformanceTests), lists failing files in the job summary and uploads the log and results JSON. The tested lanes build in their own job (build-tested, same steps via an anchor) becauseneeds:cannot name one matrix leg; that way tests start when those two lanes are done, not all 42.releasedoes not wait fortest.Also: drops the unused
llvm_versioninput; pushes to branches other thanmainno longer start a skipped run. If branch protection names any of the old job names as required checks, those need updating.Not included: the C++ test binaries (
testapi,testmasm,testb3,testair,testdfg), which the release Dockerfiles do not build, and test262.Test plan
Lane settings: ran the release scripts from
mainfor every lane, with the env the old workflow passes anddockerreplaced by a stub that records its arguments, and diffed that againstlanes.mjs build --dry-run: same Dockerfile, platform and build arguments for all 42 (windows-arm64-debugcompared against the cross script). Label set equals the old asset list. Dry-ran the tag and comment lines for main and for a pull request. Ran the build step's env export against a stub script for lanes with spaces, slashes and empty values. Test job: ran the same command with--quicklocally againstbun-webkit-linux-amd64-asan.tar.gzfromautobuild-28f58fb0. The workflow itself is first exercised by this PR's preview build.