Skip to content

Correct the architecture-specific code paths in the native math layer - #1283

Merged
dsuponitskiy merged 3 commits into
devfrom
issue1282
Sep 4, 2026
Merged

Correct the architecture-specific code paths in the native math layer#1283
dsuponitskiy merged 3 commits into
devfrom
issue1282

Conversation

@pascoec

@pascoec pascoec commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Two fixes to hardware carveouts that had drifted out of correctness, found while
reviewing the native-integer layer for non-x86 targets.

Double-word multiply. The __arm__/__powerpc__ branch of MultD sat in
the 64-bit block but computed a truncated single-word product and split that into
halves, so a default NATIVE_SIZE=64 build on 32-bit ARM or PowerPC produced
silently wrong results (2^32 * 2^32 returned 0). The per-architecture fallbacks
are replaced with one half-word decomposition that is correct everywhere, which
also covers Emscripten and removes the #error for unlisted architectures. The
aarch64 inline assembly is dropped: gcc and clang provide __int128 on aarch64,
so that block was unreachable, and the surviving path compiles to a single
umulh anyway.

Machine-specific optimization flag. WITH_NATIVEOPT=ON hardcoded
-march=native, the x86 spelling. Since cmake does not validate compiler flags
at configure time, a toolchain that rejects it fails at the first compiled source
file rather than at configuration. The flag is now probed, preferring
-mcpu=native on aarch64 (it selects the tuning model too), with a warning and a
portable build if neither spelling is accepted. The clang lane-selection
predicates are scoped to __x86_64__ at the same time, since they were chosen
from x86 measurements and other targets should take the plain forms.

The 32-bit arm/powerpc branch computed a truncated single-word product and
split that into halves, so a NATIVE_SIZE=64 build on those targets produced
silently incorrect results. Replace the per-architecture fallbacks with one
half-word decomposition, which also covers Emscripten and removes the #error
for unlisted architectures. The aarch64 assembly is dropped: gcc and clang
provide __int128 on aarch64, so it was unreachable.
… target

-march=native is the x86 spelling; on aarch64 the preferred spelling is
-mcpu=native, which selects the tuning model as well as the architecture.
Current Apple clang accepts both, but other aarch64 toolchains accept only one
and some accept neither, and cmake does not validate compiler flags at
configure time, so a bad flag fails at the first compiled source file instead
of at configuration. Probe the candidates and use the first the compiler
accepts, warning instead of failing when none are.

Also scope the clang lane-selection predicates to x86. The borrow-test
conditional subtract and the inline Barrett form were chosen from x86 clang
measurements, where the plain unsigned compare lowers to a mispredicting branch
below AVX2; requiring __x86_64__ lets other targets take the plain forms. On
aarch64 the two are equivalent (24 versus 23 instructions under Apple clang 21)
and it has an unsigned 64-bit vector compare. Generated code on x86 is
unchanged, verified by assembly diff under gcc-13, clang-18 and clang-18
-march=native.
@pascoec pascoec added this to the Release 1.6.0 milestone Aug 27, 2026
@pascoec
pascoec requested a review from yspolyakov August 27, 2026 17:14
@pascoec pascoec self-assigned this Aug 27, 2026
@pascoec pascoec added the bug Something isn't working label Aug 27, 2026
@pascoec pascoec linked an issue Aug 27, 2026 that may be closed by this pull request
@pascoec
pascoec requested a review from dsuponitskiy August 29, 2026 02:47
- Probe -march=native/-mcpu=native separately for the C and C++
  compilers, since the two frontends may accept different options, and
  apply each language's flag only when its own probe passes.
- Reset WITH_NATIVEOPT to OFF when the C++ compiler accepts neither
  flag, so config_core.h, OpenFHEConfig.cmake, and the
  WITH_NATIVEOPT-gated code paths stay consistent with the flags
  actually applied.
- Replace the hand-rolled uint128_t block in MultD with the
  bit-identical MultDPortable, which also gives the portable algorithm
  CI coverage by construction through the NATIVE_SIZE=128 jobs.
@dsuponitskiy
dsuponitskiy merged commit 5d53209 into dev Sep 4, 2026
42 of 45 checks passed
@dsuponitskiy
dsuponitskiy deleted the issue1282 branch September 4, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix the double-word multiply on targets without a 128-bit integer type

2 participants