Skip to content

Add Windows ARM64 build and release support#6486

Draft
threepointone wants to merge 9 commits into
mainfrom
add-windows-arm64-support
Draft

Add Windows ARM64 build and release support#6486
threepointone wants to merge 9 commits into
mainfrom
add-windows-arm64-support

Conversation

@threepointone

@threepointone threepointone commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds end-to-end support for building, testing, and releasing workerd on Windows ARM64 (aarch64-pc-windows-msvc). This enables npm install workerd to work natively on Windows ARM64 devices (e.g. Snapdragon laptops) via the new @cloudflare/workerd-windows-arm64 package.

Customer request: https://chat.google.com/room/AAAAAzbdxGI/mmu50RiTETE/mmu50RiTETE?cls=10

Builds on prior work in #4062 by @fhanau. Several upstream blockers from that attempt have since been resolved (rules_nodejs, bazel_lib, yq all gained ARM64 Windows support), making this possible now.

What changed

Build system scaffolding

  • New arm64_windows-clang-cl Bazel platform, build:windows_arm64 / release_windows_arm64 / ci-windows-arm64 configs
  • Rust toolchain: aarch64-pc-windows-msvc added to RUST_TARGET_TRIPLES, supported_platform_triples, and vendored crate platform mappings
  • Host tools (wasm-tools, clang-tidy): windows_arm64 select entries using x64 binaries under emulation

Upstream dependency patches

Three Bazel rulesets needed patching for ARM64 Windows — the upstream binaries exist but the Bazel rules don't register the platform:

Dependency Issue Patch
yq.bzl 0.3.5 aspect_rules_js pulls 0.3.2 transitively (no windows_arm64) archive_override to force 0.3.5
aspect_rules_js 3.0.3 use_repo list missing yq_windows_arm64 Patch to add it (upstream PR #2276 was closed)
aspect_rules_esbuild 0.25.1 _PLATFORMS missing win32-arm64 Patch to add platform + integrity hash

Runner/toolchain fixes

  • Bazel 9.0.2rc1: Bazel 9.0.1 has a broken ARM64 Windows build (bazelbuild/bazel#28941). Using USE_BAZEL_VERSION=9.0.2rc1 on ARM64 Windows only until .bazelversion is bumped globally.
  • compiler-rt builtins: clang-cl on ARM64 doesn't auto-link clang_rt.builtins-aarch64.lib, causing __udivti3 undefined symbol in V8. Added explicit --linkopt.
  • workspace-status.cmd: Git is at \bin\git.exe on ARM64 runners (not \cmd\git.exe). Fixed bash path derivation.
  • Test timeouts: The windows-11-arm runner has 4 vCPUs (vs 16 on x64). Increased test timeouts accordingly.

NPM distribution

  • New @cloudflare/workerd-windows-arm64 package (os: win32, cpu: arm64)
  • Platform resolver, meta-package optionalDependencies, and build script all updated

CI/CD

  • Build + upload matrix entries in release.yml using windows-11-arm GitHub Actions runner
  • Test matrix entry in test.yml (debug builds excluded)
  • .exe extension handling generalized from == 'windows-64' to contains(matrix.arch, 'windows')

Pre-existing fix

  • OCI container targets (images/) now have target_compatible_with = ["@platforms//os:linux"] — they were missing this and would fail on any Windows platform, not just ARM64.

Before merging

  • Bazel version: Update USE_BAZEL_VERSION from 9.0.2rc1 to 9.0.2 final once released (or bump .bazelversion globally and remove the override). Tracked at bazelbuild/bazel#28967.
  • Rust crate re-pin: The vendored crate platform mappings in deps/rust/crates/ were edited manually. Run bazel run //deps/rust:crates_vendor -- --repin to validate/regenerate them properly.
  • Consider squashing: The 9 commits reflect the iterative debugging process. Consider squashing into fewer logical commits for a cleaner history.
  • Upstream contributions: The patches for aspect_rules_js, aspect_rules_esbuild, and yq.bzl could be contributed upstream to reduce our maintenance burden.

Test plan

  • CI passes on windows-arm64 test matrix entry (windows-11-arm runner)
  • bazel build //... succeeds (2545/2545 targets)
  • bazel test //... passes (with adjusted timeouts)
  • All other platforms (Linux x64/ARM64, macOS ARM64, Windows x64) remain green
  • Verify workerd --version runs on an actual Windows ARM64 device
  • Verify @cloudflare/workerd-windows-arm64 npm package installs correctly

@threepointone threepointone requested review from a team as code owners April 2, 2026 19:09
@threepointone threepointone requested a review from vicb April 2, 2026 19:09
@ask-bonk

ask-bonk Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

I'm Bonk, and I've done a quick review of your PR.

This PR adds Windows ARM64 build, test, and release support across the Bazel build system, Rust toolchain, NPM distribution, and CI workflows. The Bazel, Rust, npm, and test workflow changes all look correct.

Issues found (ranked by severity):

1. [HIGH] release.yml upload-artifacts: .exe extension logic will break for windows-arm64

The upload-artifacts job has four places where .exe handling is gated on matrix.arch == 'windows-64'. Since the new matrix entry has arch: windows-arm64, all of these evaluate to false, which will cause the release pipeline to fail for Windows ARM64:

  • Line 166if: matrix.arch != 'windows-64'chmod +x /tmp/workerd will run for windows-arm64, but the file is named workerd.exe
  • Line 173gzip -9N -k /tmp/workerd → should be workerd.exe, so gzip will fail (file not found)
  • Line 174mv /tmp/workerd.gz → same issue, cascading failure
  • Line 199cp /tmp/workerd ... bin/workerd → will copy without .exe extension, producing a broken npm package

Fix: Replace matrix.arch == 'windows-64' with contains(matrix.arch, 'windows') in all four locations:

# Line 166:
- if: ${{ !contains(matrix.arch, 'windows') }}
  run: chmod +x /tmp/workerd

# Line 173:
gzip -9N -k /tmp/workerd${{ contains(matrix.arch, 'windows') && '.exe' || '' }}

# Line 174:
- run: mv /tmp/workerd${{ contains(matrix.arch, 'windows') && '.exe' || '' }}.gz /tmp/workerd-${{ matrix.arch }}.gz

# Line 199:
- run: cp /tmp/workerd${{ contains(matrix.arch, 'windows') && '.exe' || '' }} npm/workerd-${{ matrix.arch }}/bin/workerd${{ contains(matrix.arch, 'windows') && '.exe' || '' }}

This review was generated by an AI assistant and may contain inaccuracies.

@ask-bonk

ask-bonk Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

I've posted a review comment on PR #6486. Here's a summary:

The PR adds Windows ARM64 support across the build system, Rust toolchain, npm packages, and CI workflows. The Bazel, Rust, npm, and test workflow changes are all correct.

One high-severity issue found: The upload-artifacts job in release.yml has four places where .exe extension handling is gated on matrix.arch == 'windows-64'. Since the new matrix entry uses arch: windows-arm64, these conditions all evaluate incorrectly, which will cause the release pipeline to fail for Windows ARM64 — gzip will try to compress a non-existent file, and the npm package will get a binary without the .exe extension.

The fix is to replace matrix.arch == 'windows-64' with contains(matrix.arch, 'windows') in all four locations (lines 166, 173, 174, 199 of release.yml).

github run

@fhanau

fhanau commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

My understanding is that lots of effort would be needed to properly support an arm64 Windows build – see #4062 for some prior work. Would need to be developed on an actual arm64 machine, not something an LLM can do alone. A good alternative to explore would be to cross-compiling from x64 Windows (still won't be trivial).

@threepointone threepointone force-pushed the add-windows-arm64-support branch from db49ad2 to 61c282d Compare April 2, 2026 19:14
@codspeed-hq

codspeed-hq Bot commented Apr 2, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 14.58%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 69 untouched benchmarks
⏩ 129 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
Encode_ASCII_32[TextEncoder][0/0/32] 3.1 ms 2.7 ms +14.58%

Comparing add-windows-arm64-support (193ca0e) with main (1f30941)

Open in CodSpeed

Footnotes

  1. 129 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@threepointone

Copy link
Copy Markdown
Contributor Author

Ah I see ,thanks for the info @fhanau (tagged you in the internal thread as well). lemme do a couple of iterations here and see how far I can get before I complete give up (inevitably)

@threepointone

Copy link
Copy Markdown
Contributor Author

Status update

We've been iterating through blockers on the windows-arm64 CI job. Here's what we've hit and resolved so far:

# Blocker Fix Commit
1 release.yml .exe logic hardcoded to windows-64 Changed to contains(matrix.arch, 'windows') 61c282d
2 yq.bzl 0.3.2 (transitive) missing windows_arm64 yq binary Added bazel_dep(name = "yq.bzl", version = "0.3.5") override in MODULE.bazel 5b46e41
3 Bazel 9.0.1 ARM64 Windows JNI bug — ships x64 DLLs in ARM64 package (bazelbuild/bazel#28941) Force x64 Bazel via BAZELISK_FORMAT_URL in setup-runner 40171a0
4 workspace-status.cmd can't find bash on ARM64 runner (Git installed at \bin\git.exe not \cmd\git.exe) Added \bin\git.exe path pattern + fixed for loop f85113d

Current blocker: Bazel 9.0.1 toolchain detection

Because of blocker #3, we're currently running the x64 Bazel binary under emulation. This works for Bazel itself, but local_config_cc auto-detects the x64 MSVC toolchain instead of ARM64 clang-cl. The build then fails because:

  • V8's v8_heap_base_files select() has no matching condition (the platform is detected as x64, not ARM64)
  • The compiler is x64 cl.exe instead of ARM64 clang-cl

This is a fundamental limitation of using x64 Bazel on an ARM64 host — the toolchain auto-detection picks up the wrong architecture.

Path forward

Bazel 9.0.2 (bazelbuild/bazel#28967) fixes the ARM64 Windows JNI bug. RC1 is expected April 6 and the release on April 8. Once we bump .bazelversion to 9.0.2:

  1. The BAZELISK_FORMAT_URL workaround can be removed
  2. Native ARM64 Bazel will correctly auto-detect the ARM64 clang-cl toolchain
  3. V8/zlib/BoringSSL should then build against the correct target

The prior attempt at this (PR #4062 by @fhanau) hit several of these same blockers when the upstream ecosystem wasn't ready. Since then, rules_nodejs 6.7.3, bazel_lib 3.2.2, and yq all gained ARM64 Windows support — the only remaining gap is Bazel itself.

This PR is ready to pick back up once Bazel 9.0.2 drops.

@anonrig anonrig requested a review from fhanau April 2, 2026 21:05
@threepointone threepointone marked this pull request as draft April 3, 2026 09:36
@threepointone

Copy link
Copy Markdown
Contributor Author

converting to draft so we can revisit this next week after bazel 9.0.2 is out

@github-actions

github-actions Bot commented Apr 7, 2026

Copy link
Copy Markdown

The generated output of @cloudflare/workers-types matches the snapshot in types/generated-snapshot 🎉

@threepointone threepointone force-pushed the add-windows-arm64-support branch 4 times, most recently from 31dffa2 to 6bd6bf6 Compare April 8, 2026 11:31
Add end-to-end support for building, testing, and releasing workerd on
Windows ARM64 (aarch64-pc-windows-msvc). This enables `npm install workerd`
to work natively on Windows ARM64 devices (e.g. Snapdragon laptops) via
the new @cloudflare/workerd-windows-arm64 package.

Bazel build system:
- Define arm64_windows-clang-cl platform in BUILD.bazel
- Add build:windows_arm64 config overriding the toolchain and execution
  platform from x64 to ARM64
- Add build:release_windows_arm64 composing release_windows + windows_arm64
- Add build:ci-windows-arm64 for CI release builds

Rust toolchain:
- Register aarch64-pc-windows-msvc in RUST_TARGET_TRIPLES so rules_rust
  downloads and configures a Rust toolchain for the new target
- Add the triple to supported_platform_triples in crates_vendor so the
  Cargo dependency graph resolves for Windows ARM64
- Add win_arm64 config_setting_group and wire cargo-bazel to use the x64
  binary under WoW64 emulation (no native ARM64 build available)

Host tools:
- Add @bazel_tools//src/conditions:windows_arm64 entries to wasm-tools
  and clang-tidy selects, falling back to x64 binaries under emulation

NPM distribution:
- Create npm/workerd-windows-arm64/ package (os: win32, cpu: arm64)
- Add "win32 arm64 LE" to knownPackages in node-platform.ts
- Add @cloudflare/workerd-windows-arm64 to optionalDependencies in the
  workerd meta-package and build-shim-package.mjs

CI/CD:
- Add windows-arm64 build matrix entry in release.yml using the
  windows-11-arm GitHub Actions runner
- Add windows-arm64 upload-artifacts entry for release asset publishing
  and npm per-arch package publishing
- Add windows-arm64 to test.yml matrix (debug builds excluded, matching
  the x64 Windows exclusion for disk space reasons)

Note: the @local_config_cc toolchain name for ARM64 Windows is
auto-generated by Bazel based on what it discovers on the host. If the
first CI run fails with a toolchain resolution error, query
@local_config_cc//:all on the runner to find the correct label.

Made-with: Cursor
aspect_rules_js uses yq to parse pnpm lockfiles during repository setup.
It resolves the yq binary via @yq_{platform}, where {platform} is detected
from the host. On Windows ARM64, this becomes @yq_windows_arm64, which
doesn't exist in yq.bzl 0.3.2 (the version transitively pulled in by
aspect_rules_js 3.0.3).

yq.bzl 0.3.5 includes Windows ARM64 yq binaries, so we override the
transitive version with a direct bazel_dep in the root MODULE.bazel.

This was the primary blocker identified in the prior Windows ARM64 attempt
(PR #4062). The other blockers from that attempt have since been resolved:
- rules_nodejs 6.7.3 (already used) includes windows_arm64 Node.js
  toolchain support (merged in 6.5.0, fix in 6.6.1)
- bazel_lib 3.2.2 (already used) includes windows_arm64 binaries for
  copy_to_directory, coreutils, expand_template, and zstd
- The setup-runner action already enables developer mode on Windows via
  reg add, which also runs on ARM64 runners

Made-with: Cursor
The script derives Git's bash.exe location by stripping known git.exe
suffixes from the path returned by `where git.exe`. It handled
\cmd\git.exe and \mingw64\bin\git.exe but not \bin\git.exe, which is
the path returned on ARM64 Windows runners.

Also fix the `for` loop to use `goto` instead of `break` (which doesn't
exit batch for-loops) so only the first result from `where` is used.

Made-with: Cursor
Bazel 9.0.2rc1 is now available with the fix for the ARM64 Windows JNI
DLL mismatch (bazelbuild/bazel#28941, PR #29012). This allows native
ARM64 Bazel to run correctly, which means the toolchain auto-detection
will find the ARM64 clang-cl toolchain instead of x64 MSVC.

Replace the BAZELISK_FORMAT_URL workaround (which forced x64 Bazel and
caused toolchain misdetection) with USE_BAZEL_VERSION=9.0.2rc1 scoped
to ARM64 Windows only. Other platforms continue using 9.0.1 from
.bazelversion.

Made-with: Cursor
The vendored crate definitions in deps/rust/crates/defs.bzl were
generated without the aarch64-pc-windows-msvc triple, causing Rust
crates like windows_sys, mio, socket2, and termcolor to fail to
resolve their Windows-specific dependencies on ARM64.

Add the triple to the cfg(windows), cfg(any(target_arch = "aarch64")),
cfg(not(all(target_arch = "arm"))) conditions and as a literal triple
mapping. A proper crates_vendor --repin should be done once the
googlesource auth issue is resolved for local development.

Made-with: Cursor
aspect_rules_esbuild 0.25.1 doesn't include Windows ARM64 in its
platform list despite esbuild shipping native win32-arm64 binaries.
This caused 911 targets to fail toolchain resolution on ARM64 Windows.

Add a patch that:
- Adds win32-arm64 to _PLATFORMS in toolchains_repo.bzl
- Adds the npm integrity hash for @esbuild/win32-arm64@0.19.9 to
  versions.bzl

Made-with: Cursor
The oci_load and multirun targets in images/ were missing
target_compatible_with constraints, causing toolchain resolution
failures on Windows (both x64 and ARM64). The oci_image target already
had the Linux constraint but the load/multirun targets that depend on
it did not.

Made-with: Cursor
The windows-11-arm runner has 4 vCPUs (vs 16 for windows-2025-16core),
so tests need longer timeouts. Four tests were timing out at the
default limits (3s/15s/60s/240s for small/medium/large/enormous).
Increase to 15s/60s/240s/240s to match the slower hardware.

Made-with: Cursor
@threepointone threepointone force-pushed the add-windows-arm64-support branch from 098d766 to d0b90bc Compare April 9, 2026 21:54
clang-cl on ARM64 Windows doesn't automatically link the compiler-rt
builtins library, causing undefined symbol errors for 128-bit integer
operations like __udivti3 (used by V8's bigint division). The library
is available in the LLVM lib path but needs to be linked explicitly.

Made-with: Cursor
@threepointone threepointone marked this pull request as ready for review April 10, 2026 06:29
@threepointone threepointone requested a review from a team as a code owner April 10, 2026 06:29
@threepointone

Copy link
Copy Markdown
Contributor Author

Update: Windows ARM64 CI is fully green 🎉

Since the last status update, we've cleared all remaining blockers. The windows-arm64 CI job now builds all 2,545 targets and passes all tests.

Blockers resolved since last update

# Blocker Fix
5 aspect_rules_js can't see @yq_windows_arm64 (bzlmod visibility) archive_override with patch adding yq_windows_arm64 to use_repo
6 Rust crate vendored mappings missing aarch64-pc-windows-msvc Added platform to defs.bzl + 8 crate BUILD files
7 aspect_rules_esbuild missing win32-arm64 (911 targets failed) archive_override with patch adding platform + integrity hash
8 OCI container targets missing target_compatible_with Added @platforms//os:linux to oci_load and multirun targets
9 Test timeouts on 4-vCPU runner Increased --test_timeout for ci-windows-arm64
10 V8 linking fails — __udivti3 undefined Explicit --linkopt=clang_rt.builtins-aarch64.lib

What's left before merging

The PR description has a full checklist, but the key items are:

  • Bump Bazel: We're using 9.0.2rc1 scoped to ARM64 Windows. Once 9.0.2 final ships (RC2 is out, final is imminent), bump .bazelversion globally and remove the USE_BAZEL_VERSION override.
  • Re-pin Rust crates: The vendored crate platform mappings were manually edited. A proper crates_vendor --repin should validate them.
  • Upstream patches: We're carrying patches for aspect_rules_js, aspect_rules_esbuild, and yq.bzl. Contributing these upstream would reduce maintenance.

cc @fhanau — turns out the ecosystem has come far enough since #4062 that this was achievable with targeted patches. Happy to discuss any of the changes.

@danlapid danlapid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! This might become useful in the future!
At present, I am more interested to learn if window for arm's x64 emulation can run workerd well enough.
If it can, I am not convinced this is worth the maintenance burden.

@petebacondarwin petebacondarwin marked this pull request as draft April 15, 2026 11:33
@petebacondarwin petebacondarwin removed the request for review from vicb April 15, 2026 11:34
@eliasblume

eliasblume commented Apr 20, 2026

Copy link
Copy Markdown

@danlapid I'm running a patched workderd on my Windows ARM X-Elite notebook since almost a year now.
it works perfectly fine - never had any issues...

although it should be said that im not rly using it much other than local nextjs on workers dev.

@Badbird5907

Copy link
Copy Markdown

Great work! This might become useful in the future! At present, I am more interested to learn if window for arm's x64 emulation can run workerd well enough. If it can, I am not convinced this is worth the maintenance burden.

Hi, I've been running workerd under emulation with my Snapdragon X Elite X1E78100 for ~6 months with no issues. I've gone ahead and opened #6882 in hopes we can merge that while we wait for this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants