Skip to content

x86_64: check OSXSAVE+XCR0 for AVX, read BMI1/AVX2 from EBX - #198

Merged
Jarred-Sumner merged 1 commit into
oven-sh:mainfrom
robobun:farm/7c027ef5/fix-avx-detection-xgetbv
Apr 24, 2026
Merged

Jarred-Sumner merged 1 commit into
oven-sh:mainfrom
robobun:farm/7c027ef5/fix-avx-detection-xgetbv

Conversation

@robobun

@robobun robobun commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

What

Fixes two bugs in MacroAssemblerX86_64::collectCPUFeatures() on Linux/Windows that cause the JIT to emit VEX-encoded (AVX) instructions on machines where AVX is not actually usable.

1. AVX detection missing OSXSAVE / XCR0 check

The non-Darwin path decided AVX was available based on CPUID.1:ECX[28] alone:

s_avxCheckState = (cpuid[2] & (1 << 28)) ? CPUIDCheckState::Set : CPUIDCheckState::Clear;

Per the Intel SDM, software must verify three things before executing AVX:

  1. CPUID.1:ECX.AVX[28] = 1 — CPU supports AVX
  2. CPUID.1:ECX.OSXSAVE[27] = 1 — OS has enabled XGETBV
  3. XCR0[2:1] = 11b — OS saves XMM and YMM state on context switch

Checking only (1) is insufficient. Under many hypervisors (Xen, Hyper-V, older KVM configs, cloud VMs without AVX passthrough), ECX[28] is passed through from the physical CPU while the guest kernel has not enabled XSAVE/YMM state. Every VEX-encoded instruction then raises #UD → SIGILL.

Every if (supportsAVX()) guard in MacroAssemblerX86_64.h (~80 sites), MacroAssembler::probe(), isX86_64_AVX() → Options::useWasmSIMD(), and the Yarr JIT all sit downstream of this — so a false positive here means the JIT emits VEX everywhere and crashes when the code runs.

This is the cause of the "baseline build SIGILL in JIT code" class of Bun crash reports on VMs:

Bun's own bun_cpu_features() uses __builtin_cpu_supports("avx") / IsProcessorFeaturePresent(PF_AVX_INSTRUCTIONS_AVAILABLE), both of which do perform the full OSXSAVE/XCR0 check — which is why Bun's crash reporter correctly prints no_avx while JSC's JIT simultaneously believes AVX is available.

Fix: require ECX[27] (OSXSAVE), then XGETBV(0) and check (XCR0 & 0x6) == 0x6 before setting s_avxCheckState. Same approach as simdutf (Source/WTF/wtf/simdutf/simdutf_impl.h:1029-1038), compiler-rt, and every other correct AVX detector.

2. BMI1 / AVX2 read from wrong CPUID register

CPUID cpuid = getCPUID(0x7);
s_bmi1CheckState = (cpuid[2] & (1 << 3)) ? ... ;  // ECX[3] is PKU, not BMI1
s_avx2CheckState = (cpuid[2] & (1 << 5)) ? ... ;  // ECX[5] is WAITPKG, not AVX2

getCPUIDEx returns [EAX, EBX, ECX, EDX]. For leaf 7 sub-leaf 0, BMI1 is EBX[3] and AVX2 is EBX[5]. The code was reading cpuid[2] (ECX) — so supportsBMI1() actually reported PKU and supportsAVX2() reported WAITPKG. On most modern Intel server CPUs this mis-reports AVX2 as unavailable (verified: Xeon Platinum 8375C has EBX[5]=1 but ECX[5]=0).

Fix: read cpuid[1] (EBX). Also gate AVX2 on the AVX result since AVX2 is VEX-encoded and requires the same OS YMM state.

Scope

Linux and Windows only (#if !OS(DARWIN) branches). Darwin is unchanged — it hardcodes s_avxCheckState = Set and uses sysctlbyname for BMI1/AVX2, which is correct since every x86-64 Mac has AVX and macOS always enables it. There is no darwin-x64-baseline Bun build.

The same code exists in upstream WebKit main — this is not a fork regression; probably worth upstreaming.

collectCPUFeatures() on Linux/Windows was deciding AVX is available
based solely on CPUID.1:ECX[28]. That bit only says the CPU implements
AVX; it says nothing about whether the OS has enabled XSAVE and set
XCR0 to preserve YMM state. Under several hypervisors (Xen, Hyper-V,
some KVM configurations, Windows VMs without AVX passthrough) the AVX
feature bit is passed through from the host while OSXSAVE/XCR0 is not,
so every VEX-encoded instruction raises #UD. The JIT's supportsAVX()
guard returned true in these environments and the generated code
faulted with SIGILL.

Follow the Intel SDM detection sequence: require ECX[28] (AVX) and
ECX[27] (OSXSAVE), then execute XGETBV and require XCR0[2:1]==11b
before reporting AVX as usable.

Separately, the non-Darwin leaf-7 probe was reading ECX for BMI1 and
AVX2. Those bits live in EBX (BMI1=EBX[3], AVX2=EBX[5]); ECX[3]/ECX[5]
are PKU/WAITPKG. Read EBX instead, and additionally gate AVX2 on the
AVX result since AVX2 instructions are VEX-encoded and need the same
OS state.
@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a6883e12-6198-4dee-b89d-3114c046d592

📥 Commits

Reviewing files that changed from the base of the PR and between a4ddad8 and 7314b7d.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/assembler/MacroAssemblerX86_64.cpp

Walkthrough

Modified CPU feature detection for x86-64 architecture. AVX usability now requires XCR0 check via xgetbv in addition to CPUID verification on non-Darwin systems. AVX2 detection corrected to check appropriate register and depends on AVX being enabled.

Changes

Cohort / File(s) Summary
CPU Feature Detection
Source/JavaScriptCore/assembler/MacroAssemblerX86_64.cpp
Refined AVX usability detection to require XCR0 check via xgetbv alongside CPUID verification on non-Darwin platforms; corrected AVX2 detection to use proper register indices (EBX) and gate AVX2 enablement on AVX being set.
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description is well-structured and comprehensive, clearly explaining both bugs with technical details, root causes, and references. However, it does not follow the required WebKit pull request template structure with a Bugzilla link and proper formatting. Add a Bugzilla bug link (https://bugs.webkit.org/show_bug.cgi?id=#####) and format as per WebKit template with 'Reviewed by' line and changelog section.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: checking OSXSAVE+XCR0 for AVX and reading BMI1/AVX2 from EBX, which directly correspond to the two primary bugs fixed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

@Jarred-Sumner
Jarred-Sumner merged commit 522d568 into oven-sh:main Apr 24, 2026
2 of 3 checks passed
tobocop2 added a commit to tobocop2/WebKit that referenced this pull request Jul 15, 2026
JSC assumed every x86_64 Mac has AVX in two places, so it emitted VEX-encoded
instructions on CPUs that cannot decode them and died with SIGILL:

  - collectCPUFeatures() hardcoded s_avxCheckState = Set on Darwin, so
    supportsAVX() reported true and the ~10 VEX codegen sites in
    MacroAssemblerX86_64.h emitted AVX once code tiered up.
  - The probe trampoline used vmovaps unconditionally on Darwin, and probe()
    selected it without consulting supportsAVX(). This is reachable from
    WebAssembly (WasmBBQJIT loop OSR entry and tier-up, WasmOMGIRGenerator's
    catch prologue), not only from the debug-gated OSR-exit paths.

oven-sh#198 fixed the first bug for Linux/Windows and guarded the new code with
#if !OS(DARWIN), leaving Darwin on the old assumption. Use the same runtime
checks and the same dual-trampoline selection on Darwin, and un-gate readXCR0()
so it is available there.

Also gate Darwin's AVX2 detection on the AVX result, matching the non-Darwin
path. AVX2 is VEX-encoded and needs OS-enabled YMM state, so now that AVX is a
runtime check the two must not be able to disagree.

The assumption holds for every Mac Apple shipped with a supported macOS (Sandy
Bridge, 2011, and later). It does not hold on an older Mac running current macOS
via OpenCore, nor under Rosetta 2, which reports no AVX.

Non-Darwin is unaffected: the preprocessed output of this file for
!OS(DARWIN) is byte-identical before and after.

Reported by Wolfgang Fahl on a Xeon X5690 (Westmere, no AVX).
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.

2 participants