Skip to content

deps(tinycc): enable bun:ffi on Windows ARM64 - #29476

Closed
dylan-conway wants to merge 5 commits into
mainfrom
claude/objective-swanson-cbc905
Closed

deps(tinycc): enable bun:ffi on Windows ARM64#29476
dylan-conway wants to merge 5 commits into
mainfrom
claude/objective-swanson-cbc905

Conversation

@dylan-conway

Copy link
Copy Markdown
Member

oven-sh/tinycc gained an arm64-coff/PE backend in oven-sh/tinycc#1 and the follow-up commits on mob, but Bun was still pinned to the commit before that work landed.

This PR:

  • Bumps TINYCC_COMMIT from 12882eee to c49c2204 (current mob HEAD).
  • Drops the !(windows && arm64) default for cfg.tinycc so the dep builds on all targets.
  • Removes the isFFIUnavailable = isWindows && isArm64 skip guards from the FFI/cc/napi tests and the fs.writeSync SetStdHandle repro so CI exercises bun:ffi on windows-aarch64.

Left untouched: the esbuild/require-cache win-arm64 skips, which are about npm packages lacking win32-arm64 prebuilts and unrelated to FFI.

The existing tcc.h.patch still applies cleanly; the upstream diff only touches lines further down. tinycc compiles with the new commit on linux-x64.

Opening to see what windows-aarch64 CI says.

Bumps oven-sh/tinycc to c49c2204 which adds the arm64-coff/PE backend,
drops the windows-arm64 build exclusion, and removes the corresponding
test skips so CI exercises bun:ffi on that target.
@robobun

robobun commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator
Updated 10:22 AM PT - Jul 7th, 2026

@autofix-ci[bot], your commit 7a45e12 has 5 failures in Build #69861 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29476

That installs a local version of the PR into your bun-29476 executable, so you can run:

bun-29476 --bun

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f12eb6bb-be3a-4e6d-aebc-c4d461f61073

📥 Commits

Reviewing files that changed from the base of the PR and between f8d4254 and 1ad258e.

📒 Files selected for processing (8)
  • scripts/build/config.ts
  • scripts/build/deps/tinycc.ts
  • scripts/build/source.ts
  • test/js/bun/ffi/cc.test.ts
  • test/js/bun/ffi/ffi-error-messages.test.ts
  • test/js/bun/ffi/ffi-viewSource-non-object.test.ts
  • test/js/node/fs/fs-writeSync-stdio-windows.test.ts
  • test/napi/napi-value-ffi.test.ts

Walkthrough

This PR enables TinyCC support on Windows ARM64 by changing its default feature flag to true on all platforms, updating the TinyCC commit reference for DirectBuild, and removing platform-specific gatekeeping from build configuration, documentation, and test suites.

Changes

Cohort / File(s) Summary
Build Configuration & Versioning
scripts/build/config.ts, scripts/build/deps/tinycc.ts, scripts/build/source.ts
Changed default tinycc feature flag to true on all platforms, updated TinyCC commit hash affecting version macros, and removed Windows ARM64 platform-specific documentation.
FFI Test Suite
test/js/bun/ffi/cc.test.ts, test/js/bun/ffi/ffi-error-messages.test.ts, test/js/bun/ffi/ffi-viewSource-non-object.test.ts
Removed Windows ARM64 conditional gating and test skipping logic, allowing FFI tests to execute on all Windows architectures.
Regression & Integration Tests
test/js/node/fs/fs-writeSync-stdio-windows.test.ts, test/napi/napi-value-ffi.test.ts
Removed Windows ARM64 platform guards from filesystem regression test and NAPI FFI integration test setup, enabling full test execution on ARM64 Windows systems.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely captures the main change: enabling bun:ffi support on Windows ARM64 by updating TinyCC.
Description check ✅ Passed The description covers both required sections: it explains what the PR does and mentions verification steps, though it lacks explicit verification subsection following the template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Test message

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Support bun:ffi on Windows ARM64 #28055 - Directly requests bun:ffi support on Windows ARM64, which this PR enables by bumping TinyCC to include the arm64-coff/PE backend

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #28055

Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Support bun:ffi on Windows ARM64 #28055 - Directly requests bun:ffi support on Windows ARM64, which this PR enables by bumping TinyCC to include the arm64-coff/PE backend

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #28055

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — mechanical commit bump, config default update, and skip-guard removal all align correctly with the upstream ARM64-COFF support landing in oven-sh/tinycc.

Extended reasoning...

Overview

The PR touches three areas: the TinyCC dependency definition (, , ) and test skip guards across four test files. The core change is bumping from to and removing the platform exclusion that was the only thing preventing TinyCC from building on Windows ARM64.

Security Risks

None. This is a dependency commit bump for an embedded C compiler used at runtime by bun:ffi. The change doesn't touch auth, crypto, permissions, or any code path accessible from untrusted input beyond what was already exercised by FFI on other platforms.

Level of Scrutiny

Low. The changes are mechanical: the build config default goes from a conditional to true, the commit hash is bumped, and test skip guards () are removed. The tinycc.ts DirectBuild spec already had the correct conditional source lists — arm64-gen.c, arm64-link.c, arm64-asm.c for cfg.arm64, and tccpe.c for cfg.windows — so the infrastructure was already in place; only the gate and the pinned commit needed updating.

Other Factors

The PR author explicitly notes CI is being used to validate ("opening to see what windows-aarch64 CI says"), which is the right approach. No bugs were found by the automated system. The patch noted as still applying cleanly is a good signal. The comment cleanups in and are accurate and non-breaking.

Conflict resolution keeps main's newer tinycc gate exclusions (android,
freebsd) and drops only the windows-arm64 term this PR removes. In the
FFI tests it drops the windows-arm64 half of isFFIUnavailable everywhere
main added it, including the two skip sites (cc.test.ts:394,460) that
did not exist when this branch was written, while keeping the
canBuildNodeAddons() guard main introduced in napi-value-ffi.test.ts.
@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Merged main into this branch to get a fresh CI signal — it was ~200 commits behind. Two notes for whoever picks this back up:

Conflict resolution. The tinycc gate in scripts/build/config.ts had since grown android/freebsd exclusions, so the branch's naive ?? true would have wrongly enabled tinycc there; the resolution keeps main's newer gate and drops only the (windows && arm64) term. Likewise the FFI tests now had four isFFIUnavailable skip sites in cc.test.ts (this branch predated two of them, at :394 and :460) and napi-value-ffi.test.ts's guard had grown a !canBuildNodeAddons() half that must stay. All resolved, no dangling refs.

The real blocker isn't the diff. On the last full CI run before this (build #46411), the windows-aarch64 lane — the only target this PR exists to enable — crashed on the newly un-skipped tests (cc.test.ts code 1, napi-value-ffi.test.ts segfault, plus two illegal-instructions). So the change isn't merely stale; it doesn't work yet on the target. The TINYCC_COMMIT bump plus 200 commits of main may or may not have changed that — this fresh run will say. If windows-aarch64 is still red, that's the actual work.

Heads-up on #32013. That PR adds a patches/tinycc/tccrun.c.patch (win32 VirtualAlloc JIT-memory backport for #31941) against the old TINYCC_COMMIT. I checked: the new pin c49c2204 does not contain those upstream commits, so the patch is still needed — but whichever of these two lands second needs to re-verify the patch still applies to the moved tccrun.c. The clean end-state is probably one oven-sh/tinycc bump carrying both.

Comment thread test/js/bun/ffi/cc.test.ts
Comment thread test/js/bun/ffi/cc.test.ts
Comment thread scripts/build/config.ts
The tinycc term was removed from cfg.tinycc in scripts/build/config.ts, but
two Rust-side mirrors of that predicate (both marked "keep in sync with
cfg.tinycc") still excluded windows-aarch64, so libtcc.a was built there
while bun_core::Environment::ENABLE_TINYCC stayed false and every bun:ffi
entry point kept returning "TinyCC is disabled":

- scripts/build/buildOptionsRs.ts: drop the all(windows, aarch64) term from
  the emitted ENABLE_TINYCC cfg
- src/tcc_sys/tcc.rs: drop it from both cfg lists around tcc_externs! so the
  real extern block (not the unreachable!() stubs) is used, and fix the
  stale "no aarch64-pe-coff backend" comment

Also finish the test sweep the earlier pass missed:

- test/js/bun/ffi/ffi.test.js: remove isFFIUnavailable (isWindows && isArm64)
  and its two skipIf uses
- test/js/node/fs/cp.test.ts: un-gate the bun:ffi dlopen handle-leak repro on
  Windows arm64
- test/js/bun/ffi/cc.test.ts: delete the stale "disabled on Windows ARM64"
  comment
- test/js/node/process/process.test.js: update the expected process.versions
  tinycc commit to the bumped TINYCC_COMMIT

cargo check passes for all 10 CI targets including aarch64-pc-windows-msvc.
@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Pushed 6dd7783 addressing the review comments.

The two Rust mirror predicates are now updated (#29476 (comment)): scripts/build/buildOptionsRs.ts no longer emits all(windows, target_arch = "aarch64") in the ENABLE_TINYCC cfg, and src/tcc_sys/tcc.rs drops that term from both #[cfg] lists around tcc_externs! (plus the stale "no aarch64-pe-coff backend" comment). Until now, on windows-aarch64 libtcc.a was built but bun_core::Environment::ENABLE_TINYCC stayed false, so every bun:ffi entry point early-returned "not available in this build (TinyCC is disabled)".

The two extra skip sites are un-gated (#29476 (comment)): test/js/bun/ffi/ffi.test.js (isFFIUnavailable = isWindows && isArm64, used twice) and test/js/node/fs/cp.test.ts:438. Also deleted the stale "TinyCC is disabled on Windows ARM64" comment in cc.test.ts (#29476 (comment)).

Exhaustive sweep over the repo (aarch64/arm64/isArm64/tinycc/ENABLE_TINYCC/"Windows ARM64", across scripts/src/test/packages/docs/.buildkite):

  • One more consumer of the old fact was found and fixed: test/js/node/process/process.test.js pins process.versions.tinycc to the dep commit, which this PR bumps — updated to c49c2204b0… (verified against a fresh debug build).
  • Everything else that mentions windows+arm64 is unrelated to the FFI gate and stays: third-party prebuilt availability skips (esbuild, msgpackr, rspack), the WebKit LTO exclusion in config.ts/windows-cross-config.test.ts, chkstk/TEB/crash-handler #[cfg]s, PE machine-type assertions, release/upgrade asset names, and the --compile target tables. No docs or packages/bun-types claim bun:ffi is unavailable on Windows ARM64, and nothing in .buildkite gates the win-aarch64 FFI tests.

Verified locally: bun run rust:check-all → 10 ok, 0 failed, 0 skipped (includes aarch64-pc-windows-msvc, the only target where the changed #[cfg] arm compiles); cc.test.ts, ffi.test.js, and process.test.js pass on Linux with the debug build, and cc()/JSCallback/CFunction work end-to-end there against the bumped TinyCC.

One important note on reading CI history for this PR: all previous windows-aarch64 failures/crashes on this branch are not evidence that FFI crashes on that platform. Until this commit the Rust runtime gate (ENABLE_TINYCC) was still off on win-arm64 — the C library was built but bun:ffi reported itself disabled, so the newly un-skipped tests were failing on the "TinyCC is disabled" error path, not exercising the arm64-PE backend at all. This push is the first CI run where win-arm64 FFI is actually enabled end-to-end; that run's result is the real datum for whether the backend works.

Comment thread test/js/bun/ffi/ffi.test.js
@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

The fresh windows-11-aarch64-test-bun run — the first one ever to exercise bun:ffi on Windows ARM64 end-to-end, now that the Rust ENABLE_TINYCC gate is open — failed on exactly the newly-enabled FFI tests, and the way they failed is the whole story:

  • test/js/bun/ffi/ffi.test.jsillegal instruction
  • test/js/node/fs/fs-writeSync-stdio-windows.test.tsillegal instruction
  • test/napi/napi-value-ffi.test.ts — segmentation fault
  • test/js/bun/ffi/cc.test.ts — code 1

I believe this is a missing instruction-cache flush in TinyCC's Windows JIT path, and it's ~5 lines.

vendor/tinycc/tccrun.c, protect_pages():

#ifdef _WIN32
    ...
    if (!VirtualProtect(ptr, length, protect[mode], &old))   /* no icache flush */
        return -1;
#else
    ...
    if (mprotect(ptr, length, protect[mode]))
        return -1;
# if (defined TCC_TARGET_ARM && !TARGETOS_BSD) || defined TCC_TARGET_ARM64
    if (mode == 0 || mode == 3) {
        void __clear_cache(void *beginning, void *end);
        __clear_cache(ptr, (char *)ptr + length);            /* icache flush: POSIX arm ONLY */
    }
# endif
#endif

The post-JIT instruction-cache flush exists only in the POSIX arm. That was always harmless, because Windows TinyCC has only ever run on x86/x86_64, whose i-cache is hardware-coherent with the d-cache. AArch64's is not: JIT'd code written through the d-cache and then executed without an explicit flush runs whatever stale bytes are in the i-cache — which presents as exactly illegal instruction at an arbitrary address (and, when the stale bytes happen to decode, as a wild SIGSEGV). TinyCC itself demonstrates it knows aarch64 requires this (it flushes on every other aarch64 platform); the Windows arm simply predates the existence of a windows-aarch64 target.

The fix is textbook, and the API is already in TinyCC's own bundled Win32 headers (win32/include/winapi/winbase.h:1123) and export list (win32/lib/kernel32.def:189):

#ifdef _WIN32
    DWORD old;
    if (!VirtualProtect(ptr, length, protect[mode], &old))
        return -1;
# if defined TCC_TARGET_ARM || defined TCC_TARGET_ARM64
    if (mode == 0 || mode == 3)
        FlushInstructionCache(GetCurrentProcess(), ptr, length);
# endif
#else

Guarded to _WIN32 + ARM/ARM64, so it is provably a no-op for every configuration that works today.

Confidence, stated honestly: the mechanism is proven absent from the source and required by the architecture, it is present on every sibling aarch64 platform, and it matches the observed crash signatures exactly — but I cannot execute windows-aarch64 here, so it is a very-high-confidence static conclusion, not an observation. The windows-11-aarch64-test-bun lane on this PR is the experiment: one push with this patch answers it.

Where the fix should live. The real home is one commit in oven-sh/tinycc (then every pin bump carries it), which matters because three different pins are in flight: main's, this PR's bump to c49c2204, and #33653's bump to c22ca054 — a patches/tinycc/*.patch here would have to track all of them. I'm happy to push it as a patches/tinycc/ entry on this branch to get an immediate CI answer, but the clean end-state is the upstream commit + one pin.

Secondary suspect, explicitly unverified: win64_add_function_table registers .pdata via RtlAddFunctionTable and divides by sizeof(RUNTIME_FUNCTION), whose layout/size differs between x64 and arm64. If TinyCC's arm64 codegen never emits uw_pdata this is a harmless no-op; if it does, that's a second, independent arm64-PE bug. Only worth chasing if the icache flush alone doesn't green the lane.

cc @dylan-conway (your PR) — with this, #28055 looks like it goes from "needs hardware and unknown work" to a 5-line upstream fix.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Update on my icache analysis above — good news, and it changes the conclusion:

The flush already exists. oven-sh/tinycc@c22ca054 ("tccrun: flush the instruction cache on arm64 Windows", Jul 7) is exactly this fix, at exactly that line, and #33653 already pins that commit. So the diagnosis stands (the illegal instructions on the windows-11-aarch64 lane are JIT'd code executed through a stale, uncoherent i-cache), but the fix is already written and I'm withdrawing the suggestion of a bun-side patches/ entry — it would just duplicate a commit the fork already carries.

The one thing this PR needs to know: it cannot simply bump its own TINYCC_COMMIT to c22ca054. That's a pure fast-forward from this branch's c49c2204 (ahead_by: 85, behind_by: 0, so nothing here is lost), but the newer TinyCC needs a bun-side companion that lives in #33653 (src/runtime/ffi/libtcc1.c + build changes). So:

This PR depends on #33653. Merge order: land #33653 first, then rebase this branch onto main (which drops this branch's now-stale TINYCC_COMMIT line as already-superseded) — and the windows-11-aarch64 lane, which is now running bun:ffi end-to-end for the first time, becomes the acceptance test for #28055. If the SIGILLs in ffi.test.js / cc.test.ts / napi-value-ffi.test.ts disappear on that run, Windows ARM64 FFI works.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Correction and, I think, the resolution — my two comments above are superseded and I want the record straight rather than someone acting on them.

The icache flush is real but necessary-and-not-sufficient. A deeper investigation (see #33696) already established that with the flush present the win-aarch64 FFI failures persist, and found the actual remaining killer: TinyCC's arm64 backend tests "fits in an add/sub immediate" with !(val & ~0xffful), and on an LLP64 host (Windows — how tcc is compiled into bun.exe) unsigned long is 32-bit, so the mask zero-extends and any 64-bit constant with no bits in [12,32) "fits". 1<<49 — JSC's DoubleEncodeOffset, used by every bun:ffi trampoline — became add xN, xN, #0: every double crossing the FFI boundary came back off by 2^49 and pointer args decoded to wild addresses. Fixed in oven-sh/tinycc@8a6cbc12, which is newer than the c22ca054 that #33653 pins — so my "merge #33653 then rebase this" suggestion would not have gone green either.

The complete, working enablement already exists: #33696 (TinyCC upgrade + both fixes + libtcc1.c companion + the enablement). Its windows-11-aarch64-test-bun lane is SUCCESS on head eef6e546 — the FFI suites pass there. So bun:ffi on Windows ARM64 is done on that branch; it's a draft only because it still carries two temporary diagnostic commits to drop.

Which makes this PR, as far as I can tell, superseded by #33696 (this branch is strictly behind: it has the enablement but neither TinyCC fix nor the libtcc1.c companion, which is exactly why its win-aarch64 lane fails). Worth a quick diff against #33696 for anything unique here (the test un-gating sweep, the process.versions.tinycc pin fix) and then closing in its favor. Sorry for the churn from my earlier comments — I analyzed the pin this branch uses without checking that a newer branch had already gone further.

@robobun

robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #33696, which carries the same windows-arm64 un-gating plus the two TinyCC fork fixes (LLP64 immediate masks, arm64 FlushInstructionCache) that turned out to be needed for bun:ffi to actually work on the platform.

@robobun robobun closed this Jul 14, 2026
dylan-conway pushed a commit that referenced this pull request Jul 20, 2026
## Summary

Supersedes #33653 (the TinyCC upgrade; the first commit here is that
PR's content) and #29476. Enables `bun:ffi` on Windows ARM64: drops the
windows-arm64 exclusion from `cfg.tinycc`, the generated `ENABLE_TINYCC`
constant, and the `tcc_sys` link stubs, un-skips the FFI tests that were
gated on the platform, and bumps the TinyCC pin to
[`oven-sh/tinycc@05f0fafa`](oven-sh/tinycc@05f0faf)
(which is
[`8a6cbc12`](oven-sh/tinycc@8a6cbc1)
plus oven-sh/tinycc#3).

### The windows-arm64 bug this uncovered (root-caused and fixed in the
TinyCC fork)

Enabling the platform made the windows-11-aarch64 CI lane run the FFI
suites on real hardware for the first time, which exposed wrong doubles
(`sum(0.5…9.5)` = 46 instead of 50) and segfaults in every JSCallback
test. Root cause, proven with an in-CI probe that dumped the JIT machine
code from the runner:

TinyCC's arm64 backend decides whether a 64-bit constant fits an ADD/SUB
immediate with `!(val & ~0xffful)`. On an **LLP64 host** (which is
exactly how Bun builds TinyCC into `bun.exe` on Windows via clang-cl)
`unsigned long` is 32 bits, so `~0xffful` zero-extends to
`0x00000000fffff000`, and any constant with no bits in [12,32) "fits".
`1ll << 49` is JSC's `DoubleEncodeOffset`, used by every `bun:ffi`
trampoline, and it compiled to `add xN, xN, #0`:

- machine code on the CI runner: `… ldur x0,[x29,#-8]; add x0,x0,#0 …`
- same source, LP64-built tcc: `… mov x30,#0x2000000000000; add
x0,x0,x30 …`

So every double crossing the FFI boundary lost the NaN-boxing offset (JS
saw `bits − 2^49`: 12.25→11.25, 50→46) and pointer arguments decoded to
wild addresses (the segfaults). LP64-built TinyCC (Linux/macOS) is
unaffected; the fix produces byte-identical output there, which is why
the bug only ever existed on Windows ARM64. Fixed in the fork
(`arm64_gen_opic`, `arm64_check_offset`, `arm64_sym`) by using
`uint64_t`-typed masks; TinyCC's own test suite passes, and
oven-sh/tinycc#3 adds regression cases to `tests2/73_arm64`. This also
affects any natively-built windows-arm64 TinyCC upstream.

### Known parity notes (pre-existing, unchanged)

- `cc()` code with a >4 KB stack frame needs `__chkstk`, which Bun
doesn't provide on any Windows target.
- `long double` soft-float helpers aren't provided on any arm64 target.

## Test plan

- [x] Linux/macOS/Windows-x64 behavior unchanged (same suites as #33653;
LP64 tcc output byte-identical across the fix)
- [x] Cross-built windows-arm64 `bun.exe` from Linux links with TinyCC
enabled
- [x] windows-11-aarch64 CI lane: `bun:ffi` suites green with the LLP64
fix (build #71302, and re-verified locally on Windows 11 ARM64 at
`e83d3d00`)

Fixes #28055

---------

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
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.

3 participants