From 18dd8de4e9b2726f117b79a0dcc5f8abb839331e Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Thu, 13 Aug 2026 09:31:12 +0200 Subject: [PATCH 1/4] fix(windows): bundle the LLVM runtime with Perry --- .github/workflows/gc-native-roots.yml | 23 ++++++------ .github/workflows/release-packages.yml | 28 ++++++++++++++- .github/workflows/test.yml | 3 ++ scripts/stage-npm.sh | 9 +++++ tests/test_stage_npm_windows_llvm.sh | 50 ++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 13 deletions(-) create mode 100755 tests/test_stage_npm_windows_llvm.sh diff --git a/.github/workflows/gc-native-roots.yml b/.github/workflows/gc-native-roots.yml index cc8c652810..e515aa2101 100644 --- a/.github/workflows/gc-native-roots.yml +++ b/.github/workflows/gc-native-roots.yml @@ -138,19 +138,18 @@ # prologue words, which is enough to say which walker is wrong # without a second run. # -# windows-latest REAL DEFECT, filed as #7985 (`perry.exe` cannot link -# against the official LLVM 22 release: /MT-vs-/MD CRT mismatch, -# bundled rpmalloc redefining malloc, and inkwell referencing -# target backends the release does not build). A SECOND, -# separate failure in the same arm — Git-bash `tar` reading -# `D:\a\_temp` as a remote host — was a workflow bug and is -# fixed here with `--force-local`. +# windows-latest FIXED. #8017 stopped linking the official archive's +# incompatible /MT + rpmalloc static objects, restricted +# inkwell to Perry's x86/AArch64 targets, and linked the +# archive's LLVM-C.dll instead. #7985's release follow-up +# keeps that DLL beside perry.exe in the Windows zip/npm +# package. A SECOND, separate failure in the same arm — +# Git-bash `tar` reading `D:\a\_temp` as a remote host — was a +# workflow bug fixed here with `--force-local`. # -# So: after #7970 and #7997 three of the four arms should pass and only -# windows-latest should remain red, on #7985. This workflow is therefore still -# NOT a promotion candidate — promoting it while #7985 is open would block every -# PR. Promote only once all four arms are green, and per CLAUDE.md, run it green -# once BEFORE adding it to branch protection. +# So: after #7970, #7997 and #8017 all four arms should pass. Per CLAUDE.md this +# becomes a promotion candidate only AFTER one complete green run demonstrates +# that claim; do not add it to branch protection based on a comment. name: gc-native-roots on: # Must run where it can actually gate something. Branch-scoped triggers were diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 6bb9b5dcf2..f0500b3a55 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -799,8 +799,34 @@ jobs: if: runner.os == 'Windows' shell: pwsh run: | + $ErrorActionPreference = 'Stop' + $binary = "target/${{ matrix.target }}/dist/perry.exe" + $llvmBin = Join-Path $env:LLVM_SYS_221_PREFIX 'bin' + $llvmReadObj = Join-Path $llvmBin 'llvm-readobj.exe' + $llvmRuntime = Join-Path $llvmBin 'LLVM-C.dll' + + foreach ($required in @($binary, $llvmReadObj, $llvmRuntime)) { + if (-not (Test-Path -LiteralPath $required -PathType Leaf)) { + throw "Windows release prerequisite is missing: $required" + } + } + + # #7985: the official LLVM archive's static libraries use /MT and + # bundle rpmalloc, so Perry links its LLVM C surface dynamically on + # Windows. Prove the release binary has the expected import before + # copying the matching DLL; otherwise this step could ship either a + # loader-broken executable or a stale, unused 70 MB runtime. + $imports = (& $llvmReadObj --coff-imports $binary | Out-String) + if ($LASTEXITCODE -ne 0) { + throw "llvm-readobj failed while inspecting $binary" + } + if ($imports -notmatch '(?m)^\s*Name:\s+LLVM-C\.dll\s*$') { + throw "$binary does not import LLVM-C.dll; refusing to package an unverified LLVM runtime" + } + New-Item -ItemType Directory -Force -Path staging - Copy-Item "target/${{ matrix.target }}/dist/perry.exe" staging/ + Copy-Item $binary staging/ + Copy-Item $llvmRuntime staging/ Copy-Item "xwin-install/bin/xwin.exe" staging/ # Include static libraries for linking (runtime, stdlib, UI) $libs = @("perry_runtime.lib", "perry_stdlib.lib", "perry_ui_windows.lib") diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9294acdce1..74381f9d54 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,6 +116,9 @@ jobs: benchmarks/honest_bench/harness/run_http_bench.sh \ tests/test_benchmark_peer_fallback.sh + - name: Validate Windows LLVM runtime npm staging + run: ./tests/test_stage_npm_windows_llvm.sh + - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: diff --git a/scripts/stage-npm.sh b/scripts/stage-npm.sh index 0c8ece87d5..9a8614ec99 100755 --- a/scripts/stage-npm.sh +++ b/scripts/stage-npm.sh @@ -239,7 +239,16 @@ for entry in "${PLATFORMS[@]}"; do mkdir -p "$pkg_dir/bin" "$pkg_dir/lib" if [ "$kind" = "win" ]; then + # #7985: perry.exe imports the official LLVM archive's LLVM-C.dll to avoid + # mixing its /MT + rpmalloc static objects with Rust's /MD runtime. The DLL + # must stay beside the executable: Windows searches that directory first, + # and npm installs do not inherit CI's C:\llvm\bin PATH entry. + if [ ! -f "$src_dir/LLVM-C.dll" ]; then + echo " error: $artifact is missing LLVM-C.dll required by perry.exe (#7985)" >&2 + exit 1 + fi cp "$src_dir/perry.exe" "$pkg_dir/bin/perry.exe" + cp "$src_dir/LLVM-C.dll" "$pkg_dir/bin/LLVM-C.dll" for lib in "${WIN_CORE_LIBS[@]}" "$ui_lib"; do [ -f "$src_dir/$lib" ] && cp "$src_dir/$lib" "$pkg_dir/lib/" done diff --git a/tests/test_stage_npm_windows_llvm.sh b/tests/test_stage_npm_windows_llvm.sh new file mode 100755 index 0000000000..8440a67474 --- /dev/null +++ b/tests/test_stage_npm_windows_llvm.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Regression for #7985: the Windows release and npm artifacts must keep the +# dynamically linked LLVM-C.dll beside perry.exe. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +FIXTURE_REPO="$WORK/repo" +ARTIFACTS="$WORK/artifacts" +WIN_ARTIFACT="$ARTIFACTS/perry-windows-x86_64" + +mkdir -p \ + "$FIXTURE_REPO/scripts" \ + "$FIXTURE_REPO/npm/perry/bin" \ + "$FIXTURE_REPO/npm/perry-win32-x64" \ + "$WIN_ARTIFACT" + +cp "$REPO_ROOT/scripts/stage-npm.sh" "$FIXTURE_REPO/scripts/stage-npm.sh" +cp "$REPO_ROOT/npm/perry/package.json.tmpl" "$FIXTURE_REPO/npm/perry/package.json.tmpl" +cp "$REPO_ROOT/npm/perry/bin/perry.js" "$FIXTURE_REPO/npm/perry/bin/perry.js" +cp "$REPO_ROOT/npm/perry-win32-x64/package.json.tmpl" \ + "$FIXTURE_REPO/npm/perry-win32-x64/package.json.tmpl" + +printf '[workspace.package]\nversion = "0.0.0-test"\n' > "$FIXTURE_REPO/Cargo.toml" +printf 'fixture executable\n' > "$WIN_ARTIFACT/perry.exe" +printf 'fixture LLVM runtime\n' > "$WIN_ARTIFACT/LLVM-C.dll" + +SKIP_MISSING=1 PERRY_NPM_NO_COMPRESS=1 \ + "$FIXTURE_REPO/scripts/stage-npm.sh" "$ARTIFACTS" > "$WORK/stage.out" + +cmp "$WIN_ARTIFACT/perry.exe" \ + "$FIXTURE_REPO/npm/perry-win32-x64/bin/perry.exe" +cmp "$WIN_ARTIFACT/LLVM-C.dll" \ + "$FIXTURE_REPO/npm/perry-win32-x64/bin/LLVM-C.dll" + +# The negative direction is load-bearing: a missing DLL must stop publishing, +# not silently produce an npm package whose perry.exe dies with 0xC0000135. +rm "$WIN_ARTIFACT/LLVM-C.dll" +if SKIP_MISSING=1 PERRY_NPM_NO_COMPRESS=1 \ + "$FIXTURE_REPO/scripts/stage-npm.sh" "$ARTIFACTS" \ + > "$WORK/missing.out" 2> "$WORK/missing.err"; then + echo "stage-npm accepted a Windows artifact without LLVM-C.dll" >&2 + exit 1 +fi +grep -F 'missing LLVM-C.dll required by perry.exe (#7985)' "$WORK/missing.err" >/dev/null + +echo "Windows LLVM runtime npm staging: OK (copy + missing-DLL refusal)" From 17b3726d86332640f2a71322c70479482c7e7724 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Thu, 13 Aug 2026 09:32:22 +0200 Subject: [PATCH 2/4] docs(changelog): record Windows LLVM runtime packaging --- changelog.d/8018-windows-llvm-runtime.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog.d/8018-windows-llvm-runtime.md diff --git a/changelog.d/8018-windows-llvm-runtime.md b/changelog.d/8018-windows-llvm-runtime.md new file mode 100644 index 0000000000..4babbb7376 --- /dev/null +++ b/changelog.d/8018-windows-llvm-runtime.md @@ -0,0 +1,5 @@ +- Fixed the Windows release and npm packages after the LLVM 22 dynamic-link + change: `LLVM-C.dll` is now shipped beside `perry.exe`, and both packaging + paths fail closed if the executable's required LLVM runtime is absent. This + prevents extracted or npm-installed Windows builds from exiting with loader + error `0xC0000135` on machines without a separate LLVM installation. From d0829e5034c526c4a14f6f0f398f5ea1c16e9578 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Thu, 13 Aug 2026 12:30:23 +0200 Subject: [PATCH 3/4] docs(ci): correct Windows native-roots status --- .github/workflows/gc-native-roots.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gc-native-roots.yml b/.github/workflows/gc-native-roots.yml index e515aa2101..7beadd7267 100644 --- a/.github/workflows/gc-native-roots.yml +++ b/.github/workflows/gc-native-roots.yml @@ -138,18 +138,22 @@ # prologue words, which is enough to say which walker is wrong # without a second run. # -# windows-latest FIXED. #8017 stopped linking the official archive's +# windows-latest PARTIALLY FIXED. #8017 stopped linking the official archive's # incompatible /MT + rpmalloc static objects, restricted # inkwell to Perry's x86/AArch64 targets, and linked the # archive's LLVM-C.dll instead. #7985's release follow-up # keeps that DLL beside perry.exe in the Windows zip/npm # package. A SECOND, separate failure in the same arm — # Git-bash `tar` reading `D:\a\_temp` as a remote host — was a -# workflow bug fixed here with `--force-local`. +# workflow bug. `--force-local` is ineffective with the +# runner's bsdtar; #8028 tracks normalizing the archive and +# extraction paths with `cygpath`. Do not call this arm green +# until that fix is merged and a complete run measures it. # -# So: after #7970, #7997 and #8017 all four arms should pass. Per CLAUDE.md this -# becomes a promotion candidate only AFTER one complete green run demonstrates -# that claim; do not add it to branch protection based on a comment. +# So: after #7970, #7997 and #8017 the three non-Windows arms should pass; +# windows-latest remains unproven pending #8028. Per CLAUDE.md this becomes a +# promotion candidate only AFTER one complete green run demonstrates all four +# arms; do not add it to branch protection based on a comment. name: gc-native-roots on: # Must run where it can actually gate something. Branch-scoped triggers were From 9dedfab59207df6194833c40caed77403e7203bb Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Thu, 13 Aug 2026 15:27:11 +0200 Subject: [PATCH 4/4] docs(ci): reflect landed Windows path fix --- .github/workflows/gc-native-roots.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gc-native-roots.yml b/.github/workflows/gc-native-roots.yml index 7beadd7267..eda72903bc 100644 --- a/.github/workflows/gc-native-roots.yml +++ b/.github/workflows/gc-native-roots.yml @@ -138,22 +138,22 @@ # prologue words, which is enough to say which walker is wrong # without a second run. # -# windows-latest PARTIALLY FIXED. #8017 stopped linking the official archive's +# windows-latest FIXES LANDED; GREEN RUN PENDING. #8017 stopped linking the official archive's # incompatible /MT + rpmalloc static objects, restricted # inkwell to Perry's x86/AArch64 targets, and linked the # archive's LLVM-C.dll instead. #7985's release follow-up # keeps that DLL beside perry.exe in the Windows zip/npm # package. A SECOND, separate failure in the same arm — -# Git-bash `tar` reading `D:\a\_temp` as a remote host — was a -# workflow bug. `--force-local` is ineffective with the -# runner's bsdtar; #8028 tracks normalizing the archive and -# extraction paths with `cygpath`. Do not call this arm green -# until that fix is merged and a complete run measures it. +# Git-bash `tar` reading `D:\a\_temp` as a remote host — was +# fixed by #8028, which normalizes shell paths with `cygpath` +# and exports native Windows paths for Perry. `--force-local` +# was ineffective with the runner's bsdtar. # -# So: after #7970, #7997 and #8017 the three non-Windows arms should pass; -# windows-latest remains unproven pending #8028. Per CLAUDE.md this becomes a -# promotion candidate only AFTER one complete green run demonstrates all four -# arms; do not add it to branch protection based on a comment. +# So: all known defects now have fixes in the intended combined state, but the +# windows-latest arm remains unproven until a complete run measures it. Per +# CLAUDE.md this becomes a promotion candidate only AFTER one complete green +# run demonstrates all four arms; do not add it to branch protection based on +# a comment. name: gc-native-roots on: # Must run where it can actually gate something. Branch-scoped triggers were