[MOD-17844] Stop ARM SIMD tiers executing instructions the running CPU lacks - #1018
Open
dor-forer wants to merge 3 commits into
Open
[MOD-17844] Stop ARM SIMD tiers executing instructions the running CPU lacks#1018dor-forer wants to merge 3 commits into
dor-forer wants to merge 3 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1018 +/- ##
=======================================
Coverage 97.16% 97.16%
=======================================
Files 141 141
Lines 8361 8361
=======================================
Hits 8124 8124
Misses 237 237 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
NEON_HP.cpp was one translation unit compiled with -march=armv8.2-a+fp16fml, but its HP-only entry points were dispatched on features.asimdhp alone. Because the whole TU carried +fp16fml, the compiler was licensed to emit FMLAL/FMLSL instructions anywhere in it, including into the HP-only functions whose source has no FMLAL intrinsic. Measured on arm-r8g.xlarge with gcc 12: the HP-only wrappers compiled from identical source went from 32 AdvSIMD FMLAL/FMLSL instructions at +fp16fml down to 0 once compiled at +fp16 alone. On a core with asimdhp but without asimdfhm, the old HP path was therefore a SIGILL. Tightening the predicate to require both asimdhp and asimdfhm would not have fixed this: it would have deleted the HP-only fallback for exactly the CPUs that need it. The fix is two tiers with two translation units, each compiled only with the license its own kernels need: NEON_HP.cpp now builds at +fp16, and the new NEON_FHM.cpp carries the FHM-only entry points at +fp16fml, where they still measure 64 AdvSIMD FMLAL/FMLSL instructions, so the fast path is intact. Dispatch sites gain a second, independently guarded branch (asimdhp && asimdfhm) that tries NEON_FHM first and falls back to the existing asimdhp-only NEON_HP branch.
Two tier translation units that include the same kernel header emitted the same weak (COMDAT) symbols, compiled under different -march flags. The linker then kept one body and discarded the other, chosen by link order, with nothing in the source deciding which. For each of the eight ARM tier TUs (NEON, NEON_DOTPROD, NEON_HP, NEON_FHM, NEON_BF16, SVE, SVE2, SVE_BF16), wrap the kernel-header includes in an anonymous namespace so the kernel instantiations get internal linkage, unique to each translation unit. Only the Choose_* entry points, still declared in the tier header, keep external linkage. The shared dependencies (space_includes.h, spaces.h, the type headers, and the ARM intrinsics headers) are hoisted above the anonymous namespace: wrapping them along with the kernel includes pulls the standard library into the anonymous namespace and fails to compile. Measured on arm-r8g.xlarge (gcc 12) with nm -g --defined-only before this change: NEON and NEON_DOTPROD shared 93 externally-defined symbols, SVE and SVE2 shared 166. This change makes the whole class of collision structurally impossible rather than fixing only those two known pairs. x86 was measured at 0 shared symbols across all 15 tier objects, every pair, because x86 kernel names embed the ISA and no x86 tier TU reuses another tier's kernel headers. x86 tier TUs are therefore untouched. Verification of the after state (all 28 ARM pairs sharing zero symbols, and every object still exporting its own Choose_* entry points) requires building on real ARM hardware and was not run as part of this change.
The three SQ8_FP16 optimization tests gated their FHM branch on optimization.asimdfhm alone, while the dispatcher now requires features.asimdhp && features.asimdfhm. The benchmark registrations already match the dispatcher; these three did not. Harmless in practice, since no core reports asimdfhm without asimdhp, but a test whose guard is looser than the code it tests will not catch the case it looks like it covers.
dor-forer
force-pushed
the
MOD-17844-arm-simd-isa-safety
branch
from
August 20, 2026 08:35
f527661 to
f2757e4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Describe the changes in the pull request
Two defects that let an ARM SIMD tier execute instructions the running CPU does not have.
functions/NEON_HP.cppwas one translation unit compiled with-march=armv8.2-a+fp16fml, holding both the HP-only and the FHM entry points. The whole TU therefore carried the fp16fml licence, and the compiler contracted the HP path'svcvt_f32_f16plusvfmaq_f32intofmlal, even though the HP source uses no FMLAL intrinsic. That path is dispatched onfeatures.asimdhpalone, so on a core withasimdhpbut withoutasimdfhmit is a SIGILL, reachable from any SQ8_FP16 IP, L2 or Cosine query. Measured on Neoverse with gcc 12: 32 AdvSIMD FMLAL instructions in the HP-only wrappers at+fp16fml, 0 at+fp16, 96 across the whole TU.Fixed by splitting into two tiers:
NEON_HPat+fp16predicated onasimdhp, and a newNEON_FHMat+fp16fmlpredicated onasimdhp && asimdfhm. Tightening the predicate to require both bits instead would have deleted the HP-only fallback for exactly the CPUs that need it. The FHM tier still emits 64 FMLAL instructions, so its fast path is unchanged.Two tier translation units that include the same kernel header emit the same weak COMDAT symbols with bodies compiled under different
-march, so the linker picks one by order and nothing in the source decides which.SVE.oandSVE2.oshared 166 externally defined symbols,NEON.oandNEON_DOTPROD.oshared 93. All 15 x86 tier objects already share 0, because x86 kernel names embed the ISA.Fixed by giving each ARM tier TU internal linkage for its kernel instantiations: the shared dependencies are hoisted above an anonymous namespace that wraps only the kernel-header includes, so just the tier's
Choose_*entry points stay external. All 28 ARM pairs now share 0 symbols, and SVE went from 219 external symbols to 21.Validated on Neoverse (gcc 12) and Ice Lake (gcc 13): full builds, spaces suite 1528/1528 and 1565/1565.
No available CPU has
asimdhpwithoutasimdfhm, so defect 1 cannot be crash-tested on our hardware. The instruction counts above measure the cause instead.Scenarios that fail today and are fixed here
A deployment on an armv8.2-a core that implements FEAT_FP16 but not FEAT_FHM, where FHM is optional. Any SQ8_FP16 inner product, L2 or cosine query selects the HP kernel, because dispatch checks
asimdhponly, and the kernel executes anfmlalthe core does not implement. SIGILL, in the query path, on a CPU the dispatch layer believed was supported. Nothing in the build or the configure output warns, because the tier compiled and linked cleanly.An SVE-capable core without SVE2, where the link happened to keep the SVE2-compiled body of a symbol shared between
SVE.oandSVE2.o. Dispatch checksfeatures.sveand selectsChoose_*_SVE, which now points at code compiled forarmv9-a+sve2. Latent on gcc 12 today, since none of the 166 shared bodies currently contains an SVE2-only opcode, but it activates on a compiler upgrade, an edit to any shared SVE kernel, or a change in object link order. Nothing in the source decides which body wins, so this can flip between builds of the same commit.A plain armv8-a core without FEAT_DotProd, for the int8 and uint8 inner products, where the link kept the
+dotprod-compiled body of the shared helper. The NEON path executessdotorudot. This is the same mechanism as scenario 2, and [MOD-17527] Make the uint8 integer accumulators exact at any dimension #1014 fixes the two named helpers; the linkage change here removes the mechanism rather than the instances.Which issues this PR fixes
Main objects this PR modified
src/VecSim/spaces/functions/NEON_HP.{cpp,h}and newNEON_FHM.{cpp,h}src/VecSim/spaces/{IP,L2}_space.cppdispatch sites for SQ8_FP16cmake/aarch64InstructionFlags.cmakeandsrc/VecSim/spaces/CMakeLists.txtfor the new tiersrc/VecSim/spaces/functions/tests/unit/test_spaces.cppandtests/benchmark/spaces_benchmarks/Mark if applicable