Skip to content

Fix ICX segfault caused by unconditional ARM64 SIMD wrapper types - #621

Open
stefanatwork wants to merge 3 commits into
masterfrom
fix/arm64-simd-wrapper-icx-segfault
Open

Fix ICX segfault caused by unconditional ARM64 SIMD wrapper types#621
stefanatwork wants to merge 3 commits into
masterfrom
fix/arm64-simd-wrapper-icx-segfault

Conversation

@stefanatwork

Copy link
Copy Markdown
Collaborator

Problem

Since the merge of the new ARM64 support (b28233586 "Add Windows ARM64 MSVC support"), the Linux ICX nightly CI jobs (nightly-linux-DEBUG-ICX-AVX, -ICX-AVX2, -ICX-AVX512) started SegFaulting in embree_verify/embree_verify_i2, crashing partway through regression_static_memory_monitor right after instance_arrays reported [FAILED]. All three ISA widths failed identically, which points to a compiler/codegen issue rather than an ISA-specific bug.

Root cause

common/simd/arm/simd_wrapper_types.h defines __m128_wrapper/__m128i_wrapper/__m128d_wrapper structs to work around a genuine MSVC/ARM64-specific overload ambiguity (__m128 and __m128i both alias __n128 there). However, the wrapper was applied unconditionally in vboolf4_sse2.h, vfloat4_sse2.h, vint4_sse2.h, and vuint4_sse2.h, replacing the plain __m128/__m128i union members (and their implicit conversions) with the wrapper struct on all platforms, including x86.

This introduced:

  • Non-trivial-constructor types placed inside unions alongside raw int/float arrays — technically UB type punning.
  • A class simultaneously exposing a value-returning const conversion operator and a non-const reference conversion operator — a known overload-resolution footgun.

This combination is suspected to have caused miscompilation under ICX's aggressive optimizer.

Fix

Scope the wrapper-based union member and conversion operators to #if defined(_MSC_VER) && defined(_M_ARM64) only. Restore the original plain __m128/__m128i union member with implicit conversions for all other builds (x86, and non-MSVC ARM64 such as Linux/macOS aarch64 via clang/gcc, which already worked fine without the wrapper prior to this change). The explicit .m128i()/.m128d() accessor methods remain available on both paths so existing call sites elsewhere in these files continue to compile unchanged — keeping the diff minimal and surgical.

Validation

  • Intel ICX/ICPX 2026.0.0, RelWithDebInfo, EMBREE_MAX_ISA=AVX, TBB tasking, testing intensity 4 (matching CI settings as closely as possible locally): compiles cleanly, embree_verify: 6346 passed / 0 failed.
  • Clang 21.1.8, Debug, AVX2: compiles cleanly, embree_verify: 8476 passed / 0 failed.
  • GCC: SIMD headers compile cleanly (smoke test).

Caveat: I was unable to reproduce the exact segfault locally (different CPU/exact ICX version than the CI Docker image), so this fix is based on strong code-review evidence (the UB pattern, and the fact the crash is ISA-independent) plus confirmation that no test regressions were introduced, rather than a direct before/after repro of the crash itself.

The __m128_wrapper/__m128i_wrapper structs in
common/simd/arm/simd_wrapper_types.h were introduced to work around an
MSVC/ARM64-specific overload ambiguity (__m128 and __m128i both alias
__n128 on that platform). However, the wrapper was applied
unconditionally in vboolf4_sse2.h, vfloat4_sse2.h, vint4_sse2.h, and
vuint4_sse2.h, replacing the plain __m128/__m128i union members (and
their implicit conversions) with a wrapper struct on ALL platforms,
including x86.

This introduced non-trivial-constructor types placed inside unions
alongside raw int/float arrays (type punning that is technically UB)
plus a class exposing both a value-returning const conversion operator
and a non-const reference conversion operator simultaneously - a known
overload-resolution footgun. This is suspected to have caused
miscompilation under ICX's aggressive optimizer, leading to SegFaults
in embree_verify during the nightly-linux-DEBUG-ICX-AVX/AVX2/AVX512 CI
jobs shortly after the new ARM64 support merged (all three ISA widths
failed identically, pointing to a compiler/ABI issue rather than an
ISA-specific bug).

Fix: scope the wrapper-based union member and conversion operators to
MSVC+ARM64 only, and restore the original plain __m128/__m128i union
member with implicit conversions for all other builds (x86 and
non-MSVC ARM64, e.g. Linux/macOS aarch64 via clang/gcc, which already
worked without the wrapper before this change). The explicit
m128i()/m128d() accessor methods are kept available on both paths so
call sites elsewhere in these files that already use them continue to
compile unchanged.

Validated: compiles cleanly and all embree_verify tests pass with
Intel ICX/ICPX 2026.0.0 (RelWithDebInfo, AVX, TBB tasking, testing
intensity 4 - matching CI settings as closely as possible locally:
6346 passed / 0 failed), Clang 21.1.8 (Debug, AVX2: 8476 passed / 0
failed), and GCC (header compile smoke test).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@stefanatwork stefanatwork self-assigned this Aug 19, 2026
@stefanatwork
stefanatwork requested review from svenwoop and a lite review from Copilot August 19, 2026 12:25
@stefanatwork

Copy link
Copy Markdown
Collaborator Author

Addresses issue introduced in b282335

Copilot AI 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.

Pull request overview

This PR addresses ICX (Intel oneAPI) runtime segfaults introduced after adding Windows ARM64 support by scoping ARM64/MSVC-specific SIMD wrapper usage to only the platform that needs it, restoring the original plain __m128 / __m128i union members on other toolchains/architectures to avoid UB-prone union storage of non-trivial wrapper types.

Changes:

  • Restricts __m128_wrapper / __m128i_wrapper union members to #if defined(_MSC_VER) && defined(_M_ARM64) in the affected SSE2 vector headers.
  • Restores plain __m128 / __m128i union members and their reference conversion operators on non-MSVC/ARM64 builds.
  • Clarifies rationale via comments (and keeps .m128i() / .m128d() accessors available for call-site compatibility).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
common/simd/vboolf4_sse2.h Restores plain __m128 union member and non-wrapper conversions on non-MSVC/ARM64; keeps wrapper path only for MSVC ARM64.
common/simd/vfloat4_sse2.h Makes the __m128_wrapper union member and v.data conversions MSVC/ARM64-only; otherwise uses plain __m128.
common/simd/vint4_sse2.h Makes the __m128i_wrapper union member and v.data conversions MSVC/ARM64-only; otherwise uses plain __m128i.
common/simd/vuint4_sse2.h Makes the __m128i_wrapper union member and v.data conversions MSVC/ARM64-only; otherwise uses plain __m128i.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@svenwoop svenwoop 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.

In general I do not understand why you introduced these wrapper types in the first place. This looks like solving the problem in two different ways. We added the explicit conversions which should fix all issues don't they?

Also the wrapper is implemented twice actually, and both implementations are identical.

#else
__forceinline operator const __m128&() const { return v; }
#if !defined(__EMSCRIPTEN__)
__forceinline operator const __m128i() const { return _mm_castps_si128(v); }

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.

Why do we add this operator only in that case? We should now only go through new functions.

#if defined(_MSC_VER) && defined(_M_ARM64)
__forceinline operator const __m128&() const { return v.data; }
#if !defined(__EMSCRIPTEN__)
__forceinline const __m128i m128i() const { return _mm_castps_si128(v.data); }

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.

One could just remove the .data here and the functions should also work without the wrapper, the wrapper has conversion operators to the base type

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