Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 63 additions & 26 deletions sse2neon.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,6 @@
#endif
#endif /* SSE2NEON_ARM64EC */

#include <fenv.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -2708,20 +2707,33 @@ FORCE_INLINE unsigned int _sse2neon_mm_get_flush_zero_mode(void)
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_GET_ROUNDING_MODE
FORCE_INLINE unsigned int _MM_GET_ROUNDING_MODE(void)
{
const int mask = FE_TONEAREST | FE_DOWNWARD | FE_UPWARD | FE_TOWARDZERO;
switch (fegetround() & mask) {
case FE_TONEAREST:
// Read FPCR/FPSCR directly rather than going through <fenv.h>, so that the
// header does not leak the FE_* macros into everything that includes it.
union {
fpcr_bitfield field;
#if SSE2NEON_ARCH_AARCH64
uint64_t value;
#else
uint32_t value;
#endif
} r;

#if SSE2NEON_ARCH_AARCH64
r.value = _sse2neon_get_fpcr();
#else
__asm__ __volatile__("vmrs %0, FPSCR" : "=r"(r.value)); /* read */
#endif

// FPCR.RMode occupies bits [23:22]: 0b00 nearest, 0b01 toward +infinity,
// 0b10 toward -infinity, 0b11 toward zero.
switch ((r.field.bit23 << 1) | r.field.bit22) {
case 0:
return _MM_ROUND_NEAREST;
case FE_DOWNWARD:
return _MM_ROUND_DOWN;
case FE_UPWARD:
case 1:
return _MM_ROUND_UP;
case FE_TOWARDZERO:
return _MM_ROUND_TOWARD_ZERO;
case 2:
return _MM_ROUND_DOWN;
default:
// fegetround() must return _MM_ROUND_NEAREST, _MM_ROUND_DOWN,
// _MM_ROUND_UP, _MM_ROUND_TOWARD_ZERO on success. all the other error
// cases we treat them as FE_TOWARDZERO (truncate).
return _MM_ROUND_TOWARD_ZERO;
}
}
Expand Down Expand Up @@ -3342,26 +3354,51 @@ FORCE_INLINE __m128 _mm_set_ps1(float _w)
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_SET_ROUNDING_MODE
FORCE_INLINE void _MM_SET_ROUNDING_MODE(int rounding)
{
// Write FPCR/FPSCR directly rather than going through <fenv.h>, so that the
// header does not leak the FE_* macros into everything that includes it.
union {
fpcr_bitfield field;
#if SSE2NEON_ARCH_AARCH64
uint64_t value;
#else
uint32_t value;
#endif
} r;

#if SSE2NEON_ARCH_AARCH64
r.value = _sse2neon_get_fpcr();
#else
__asm__ __volatile__("vmrs %0, FPSCR" : "=r"(r.value)); /* read */
#endif

// FPCR.RMode occupies bits [23:22]. Anything that is not one of the four
// documented modes is treated as truncation, matching the previous
// behaviour of this function.
switch (rounding) {
case _MM_ROUND_NEAREST:
rounding = FE_TONEAREST;
break;
case _MM_ROUND_DOWN:
rounding = FE_DOWNWARD;
case _MM_ROUND_NEAREST: /* 0b00 */
r.field.bit22 = 0;
r.field.bit23 = 0;
break;
case _MM_ROUND_UP:
rounding = FE_UPWARD;
case _MM_ROUND_UP: /* 0b01, toward +infinity */
r.field.bit22 = 1;
r.field.bit23 = 0;
break;
case _MM_ROUND_TOWARD_ZERO:
rounding = FE_TOWARDZERO;
case _MM_ROUND_DOWN: /* 0b10, toward -infinity */
r.field.bit22 = 0;
r.field.bit23 = 1;
break;
case _MM_ROUND_TOWARD_ZERO: /* 0b11 */
default:
// rounding must be _MM_ROUND_NEAREST, _MM_ROUND_DOWN, _MM_ROUND_UP,
// _MM_ROUND_TOWARD_ZERO. all the other invalid values we treat them as
// FE_TOWARDZERO (truncate).
rounding = FE_TOWARDZERO;
r.field.bit22 = 1;
r.field.bit23 = 1;
break;
}
fesetround(rounding);

#if SSE2NEON_ARCH_AARCH64
_sse2neon_set_fpcr(r.value);
#else
__asm__ __volatile__("vmsr FPSCR, %0" ::"r"(r)); /* write */
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How about unifying _sse2neon_set_fpcr for both cases?

@alanhc alanhc Aug 21, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 9197ec2. It turned out the same #if was repeated at nine call sites across six functions, so I pushed the split down into the accessors and added a _sse2neon_fpcr_t typedef for the register width. That also collapsed the six copies of the union { fpcr_bitfield field; <width> value; } r; declaration, which each carried the same #if inside — net 41 lines removed.

I touched all six functions rather than only the two this PR added. Leaving _MM_GET/SET_FLUSH_ZERO_MODE and the denormals pair in the old shape would have made the file inconsistent with itself, but it does widen a bug fix into a small refactor.

The AArch32 write sites were passing the union rather than its value:

__asm__ __volatile__("vmsr FPSCR, %0" ::"r"(r)); /* r is the union */

Going through the accessor passes r.value, so those three are gone. An AArch32 build now emits six vmrs and three vmsr, matching the six reads and three writes, and objdump shows the write reaching vmsr through a plain register (bfi r1, r0, #24, #1 then vmsr fpscr, r1). Same 34 pre-existing warnings as master, none added.

One caveat on the green CI: it was for 31c24f7. This commit changes the accessors themselves, including the _ReadStatusReg(ARM64_FPCR) path the MSVC jobs exercise, so those results do not carry over and the workflows need another approval to be meaningful.

}

// Copy single-precision (32-bit) floating-point element a to the lower element
Expand Down
Loading