* [PATCH 0/3] alpha: fix floating-point exception state handling
@ 2026-08-03 23:40 Matt Turner
2026-08-03 23:40 ` [PATCH 1/3] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally Matt Turner
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Matt Turner @ 2026-08-03 23:40 UTC (permalink / raw)
To: Richard Henderson, Matt Turner, Magnus Lindholm
Cc: linux-alpha, linux-kernel, sparclinux, linuxppc-dev, linux-sh,
David S. Miller, Andreas Larsson, stable
Three fixes to floating-point exception handling on Alpha. The first two
are a pair and should be applied and backported together; each commit
message explains why taking one alone is worse than taking neither.
The first fixes ieee_swcr_to_fpcr() setting FPCR_DNOD unconditionally,
which disabled denormal operand traps for every process using the default
FPU settings. Instructions built with the software completion suffix
never reached the kernel emulator, and the hardware silently substituted
zero for denormal operands. This affects anything built with -mieee,
glibc included.
The second stops hardware-fabricated exception bits from reaching user
space. On EV6 and later the hardware writes exception status into the
FPCR before delivering a software completion trap, and those bits can be
wrong for the instruction that trapped. alpha_fp_emul() only wrote the
FPCR back when soft-fp raised something, so whenever it found the
instruction exact the fabricated bits stayed visible to fetestexcept().
The exception summary register is now passed down so the handler can tell
which exceptions the hardware attributed to the trapping instruction.
The third makes the emulation determine tininess after rounding, as the
hardware does. soft-fp had no notion of the distinction and always
determined it before rounding, so an operation that trapped for software
completion could report an underflow the same operation would not report
when it did not trap. The two paths disagreed on the same machine, which
IEEE 754 does not allow. This adds _FP_TININESS_AFTER_ROUNDING to the
shared soft-fp code, as glibc's copy already has, and sets it for Alpha
only. It defaults to zero, so powerpc, sh and sparc are unchanged.
Measured with the glibc testsuite on an UP1500 (EV67), with
CONFIG_MATHEMU=y. Without that option alpha_fp_emul() is not built at
all and roughly 830 math tests fail on their own, so it is a prerequisite
for any of this being reachable.
glibc make check subdirs=math
before 831 failures
after patch 1 48 failures
after patch 2 28 failures
after patch 3 (and glibc fixes) 3 failures
The three that remain are a GCC bug: a long double to float cast on Alpha
is lowered as quad to double to float, but C requires it to round once,
and the intermediate can land on a float halfway point. Two glibc fixes
are being posted separately to libc-alpha.
stdlib/tst-tininess passes, which confirms the hardware determines
tininess after rounding for the results it produces itself.
The shared soft-fp change was compile tested on sparc32, sparc64 and
ppc32 in addition to alpha. Only Alpha was tested at runtime; elsewhere
the new code is unreachable by default.
---
Matt Turner (3):
alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally
alpha: don't leak hardware-fabricated FP exception bits to user space
alpha: determine tininess after rounding in the FP emulation
arch/alpha/include/asm/sfp-machine.h | 4 ++
arch/alpha/include/uapi/asm/fpu.h | 8 +++-
arch/alpha/kernel/traps.c | 6 +--
arch/alpha/math-emu/math.c | 88 +++++++++++++++++++++++++++++++-----
include/math-emu/op-common.h | 23 +++++++++-
include/math-emu/soft-fp.h | 8 ++++
6 files changed, 119 insertions(+), 18 deletions(-)
---
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
change-id: 20260803-alpha-fp-exceptions-5c77d9e057a3
Best regards,
--
Matt Turner <mattst88@gmail.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/3] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally
2026-08-03 23:40 [PATCH 0/3] alpha: fix floating-point exception state handling Matt Turner
@ 2026-08-03 23:40 ` Matt Turner
2026-08-05 9:28 ` Magnus Lindholm
2026-08-03 23:40 ` [PATCH 2/3] alpha: don't leak hardware-fabricated FP exception bits to user space Matt Turner
2026-08-03 23:40 ` [PATCH 3/3] alpha: determine tininess after rounding in the FP emulation Matt Turner
2 siblings, 1 reply; 7+ messages in thread
From: Matt Turner @ 2026-08-03 23:40 UTC (permalink / raw)
To: Richard Henderson, Matt Turner, Magnus Lindholm
Cc: linux-alpha, linux-kernel, sparclinux, linuxppc-dev, linux-sh,
David S. Miller, Andreas Larsson, stable
ieee_swcr_to_fpcr() converts the software IEEE trap-enable and status
bits kept in thread_info.ieee_state into the hardware FPCR format. It
contained:
fp |= (~sw & IEEE_TRAP_ENABLE_DNO) << 41;
FPCR_DNOD (bit 47) disables denormal operand traps: with it set the
hardware handles a denormal operand itself, treating it as zero, instead
of trapping for software completion. The intent was to set DNOD when the
user has not asked for SIGFPE on denormal operands, but
IEEE_TRAP_ENABLE_DNO is clear by default, so ieee_swcr_to_fpcr(0) always
set DNOD.
Instructions built with the software completion suffix therefore never
trapped on a denormal operand. The hardware silently substituted zero
and produced wrong results, affecting every program compiled with -mieee
and default FPU settings, glibc included.
Set FPCR_DNOD only when IEEE_MAP_DMZ is requested, which is exactly the
case where flushing denormal inputs to zero is what the user asked for.
DNOD then encodes MAP_DMZ, which ieee_fpcr_to_swcr() already recovers
from FPCR_DNZ, so drop its attempt to recover IEEE_TRAP_ENABLE_DNO from
DNOD; the DNO trap enable lives solely in ieee_state.
Both functions are in a uapi header, so the encoding change is visible to
userspace, but nothing outside the kernel is known to depend on DNOD
carrying the DNO trap enable, and the kernel is the only writer of the
FPCR.
This must not be backported on its own. Re-enabling denormal operand
traps exposes a second bug, fixed in the following patch: those traps
usually find an exact result, and for an exact result the emulator did
not write the FPCR back, leaving hardware-fabricated exception bits
visible to user space. Taken alone this change would make spurious
exception flags more common.
The bug predates the git history, so there is no commit to reference in a
Fixes tag.
Cc: stable@vger.kernel.org # 5.15+
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/include/uapi/asm/fpu.h | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/arch/alpha/include/uapi/asm/fpu.h b/arch/alpha/include/uapi/asm/fpu.h
index cea9eafa056f..d28dc36786e2 100644
--- a/arch/alpha/include/uapi/asm/fpu.h
+++ b/arch/alpha/include/uapi/asm/fpu.h
@@ -101,7 +101,12 @@ ieee_swcr_to_fpcr(unsigned long sw)
| IEEE_TRAP_ENABLE_OVF)) << 48;
fp |= (~sw & (IEEE_TRAP_ENABLE_UNF | IEEE_TRAP_ENABLE_INE)) << 57;
fp |= (sw & IEEE_MAP_UMZ ? FPCR_UNDZ | FPCR_UNFD : 0);
- fp |= (~sw & IEEE_TRAP_ENABLE_DNO) << 41;
+ /*
+ * Disable denormal operand traps only when denormal inputs are to be
+ * flushed to zero. Otherwise they must keep trapping, so that /S
+ * instructions reach the kernel emulation handler.
+ */
+ fp |= (sw & IEEE_MAP_DMZ ? FPCR_DNOD : 0);
return fp;
}
@@ -116,7 +121,6 @@ ieee_fpcr_to_swcr(unsigned long fp)
| IEEE_TRAP_ENABLE_OVF);
sw |= (~fp >> 57) & (IEEE_TRAP_ENABLE_UNF | IEEE_TRAP_ENABLE_INE);
sw |= (fp >> 47) & IEEE_MAP_UMZ;
- sw |= (~fp >> 41) & IEEE_TRAP_ENABLE_DNO;
return sw;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/3] alpha: don't leak hardware-fabricated FP exception bits to user space
2026-08-03 23:40 [PATCH 0/3] alpha: fix floating-point exception state handling Matt Turner
2026-08-03 23:40 ` [PATCH 1/3] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally Matt Turner
@ 2026-08-03 23:40 ` Matt Turner
2026-08-05 9:33 ` Magnus Lindholm
2026-08-03 23:40 ` [PATCH 3/3] alpha: determine tininess after rounding in the FP emulation Matt Turner
2 siblings, 1 reply; 7+ messages in thread
From: Matt Turner @ 2026-08-03 23:40 UTC (permalink / raw)
To: Richard Henderson, Matt Turner, Magnus Lindholm
Cc: linux-alpha, linux-kernel, sparclinux, linuxppc-dev, linux-sh,
David S. Miller, Andreas Larsson, stable
On EV6 and later the hardware records exception status bits in the FPCR
before delivering a software completion trap, and those bits can be wrong
for the instruction that trapped. Converting a double that is exactly
representable as a subnormal float sets FPCR_UNF even though the result
is exact, and an underflow trap additionally sets FPCR_INE even when the
emulated operation turns out to be exact.
alpha_fp_emul() only wrote the FPCR when soft-fp raised an exception, so
whenever it determined that the instruction was exact the fabricated bits
stayed in the FPCR and were reported to user space by fetestexcept().
Pass the exception summary register down from do_entArith() so the
handler can tell which exceptions the hardware attributed to the trapping
instruction, and always write the FPCR. Clear the exceptions that the
trap reported but that soft-fp did not raise. EXC_SUM reports only the
underflow or overflow when the hardware also set INE, so treat INE as a
candidate in that case, and treat a trap with no reported exception as a
denormal operand trap, for which the hardware can fabricate INE and UNF
as well. Bits that software has already confirmed in ieee_state belong
to this or an earlier instruction and are never cleared.
The imprecise path passes no summary. There the trap was taken somewhere
in the trap shadow, so EXC_SUM is not attribution for the instruction
being re-executed -- and only EV6, which traps precisely and so never
takes that path, has fabricated bits to clear. For the same reason the
clearing is guarded by implver(), matching swcr_update_status().
On an UP1500 (EV68) this takes the glibc math testsuite from 831 failures
to 28, the remainder being unrelated to exception status.
This belongs with the preceding fix to ieee_swcr_to_fpcr(), and should
not be backported without it -- nor it without this. That fix stops
FPCR_DNOD being set unconditionally, so denormal operand traps start
firing again. Those traps very often find an exact result, which is
precisely the case where the old code left the FPCR unwritten and the
fabricated bits visible. Applied alone it would make spurious exception
flags more common, not less.
One case cannot be resolved here: an inexact instruction without the
software completion suffix never traps, so its INE reaches the FPCR
without being recorded anywhere else. Such a bit is indistinguishable
from an INE the hardware fabricated for a trapping instruction, and is
lost if an underflow or overflow trap with an exact result follows it.
The FPCR is the only record of those instructions and it carries no
attribution.
The bug predates the git history, so there is no commit to reference in a
Fixes tag.
Cc: stable@vger.kernel.org # 5.15+
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/kernel/traps.c | 6 ++--
arch/alpha/math-emu/math.c | 88 ++++++++++++++++++++++++++++++++++++++++------
2 files changed, 80 insertions(+), 14 deletions(-)
diff --git a/arch/alpha/kernel/traps.c b/arch/alpha/kernel/traps.c
index 7004397937cf..7cd20e5f9ee0 100644
--- a/arch/alpha/kernel/traps.c
+++ b/arch/alpha/kernel/traps.c
@@ -166,12 +166,12 @@ static long dummy_emul(void) { return 0; }
long (*alpha_fp_emul_imprecise)(struct pt_regs *regs, unsigned long writemask)
= (void *)dummy_emul;
EXPORT_SYMBOL_GPL(alpha_fp_emul_imprecise);
-long (*alpha_fp_emul) (unsigned long pc)
+long (*alpha_fp_emul) (unsigned long pc, unsigned long summary)
= (void *)dummy_emul;
EXPORT_SYMBOL_GPL(alpha_fp_emul);
#else
long alpha_fp_emul_imprecise(struct pt_regs *regs, unsigned long writemask);
-long alpha_fp_emul (unsigned long pc);
+long alpha_fp_emul (unsigned long pc, unsigned long summary);
#endif
asmlinkage void
@@ -185,7 +185,7 @@ do_entArith(unsigned long summary, unsigned long write_mask,
emulate the instruction. If the processor supports
precise exceptions, we don't have to search. */
if (!amask(AMASK_PRECISE_TRAP))
- si_code = alpha_fp_emul(regs->pc - 4);
+ si_code = alpha_fp_emul(regs->pc - 4, summary);
else
si_code = alpha_fp_emul_imprecise(regs, write_mask);
if (si_code == 0)
diff --git a/arch/alpha/math-emu/math.c b/arch/alpha/math-emu/math.c
index 68d420bfd3c0..e3f2df3729e3 100644
--- a/arch/alpha/math-emu/math.c
+++ b/arch/alpha/math-emu/math.c
@@ -52,13 +52,13 @@ MODULE_DESCRIPTION("FP Software completion module");
MODULE_LICENSE("GPL v2");
extern long (*alpha_fp_emul_imprecise)(struct pt_regs *, unsigned long);
-extern long (*alpha_fp_emul) (unsigned long pc);
+extern long (*alpha_fp_emul) (unsigned long pc, unsigned long summary);
static long (*save_emul_imprecise)(struct pt_regs *, unsigned long);
-static long (*save_emul) (unsigned long pc);
+static long (*save_emul) (unsigned long pc, unsigned long summary);
long do_alpha_fp_emul_imprecise(struct pt_regs *, unsigned long);
-long do_alpha_fp_emul(unsigned long);
+long do_alpha_fp_emul(unsigned long, unsigned long);
static int alpha_fp_emul_init_module(void)
{
@@ -86,7 +86,22 @@ module_exit(alpha_fp_emul_cleanup_module);
/*
- * Emulate the floating point instruction at address PC. Returns -1 if the
+ * Exception bits of the exception summary register (EXC_SUM). Bit 0 is the
+ * software completion bit; bits 1 through 5 report the exceptions the
+ * hardware attributed to the trapping instruction, and lie at the same
+ * positions as the corresponding IEEE_TRAP_ENABLE_* bits.
+ */
+#define EXC_SUM_INV (1UL << 1)
+#define EXC_SUM_DZE (1UL << 2)
+#define EXC_SUM_OVF (1UL << 3)
+#define EXC_SUM_UNF (1UL << 4)
+#define EXC_SUM_INE (1UL << 5)
+#define EXC_SUM_MASK (EXC_SUM_INV | EXC_SUM_DZE | EXC_SUM_OVF \
+ | EXC_SUM_UNF | EXC_SUM_INE)
+
+/*
+ * Emulate the floating point instruction at address PC. SUMMARY is the
+ * exception summary register the trap was delivered with. Returns -1 if the
* instruction to be emulated is illegal (such as with the opDEC trap), else
* the SI_CODE for a SIGFPE signal, else 0 if everything's ok.
*
@@ -95,7 +110,7 @@ module_exit(alpha_fp_emul_cleanup_module);
* stick the result of the operation into the appropriate register.
*/
long
-alpha_fp_emul (unsigned long pc)
+alpha_fp_emul (unsigned long pc, unsigned long summary)
{
FP_DECL_EX;
FP_DECL_S(SA); FP_DECL_S(SB); FP_DECL_S(SR);
@@ -300,12 +315,56 @@ alpha_fp_emul (unsigned long pc)
swcr |= (_fex << IEEE_STATUS_TO_EXCSUM_SHIFT);
current_thread_info()->ieee_state
|= (_fex << IEEE_STATUS_TO_EXCSUM_SHIFT);
+ }
- /* Update hardware control register. */
- fpcr &= (~FPCR_MASK | FPCR_DYN_MASK);
- fpcr |= ieee_swcr_to_fpcr(swcr);
- wrfpcr(fpcr);
+ /*
+ * EV6 records exception status bits in the FPCR before delivering the
+ * software completion trap, and swcr_update_status() above merged them
+ * into SWCR. Some can be wrong for the instruction we just emulated:
+ * a CVTTS of a value exactly representable as a subnormal sets FPCR_UNF
+ * even though the result is exact. Clear the exceptions the trap
+ * reported but that soft-fp did not raise.
+ */
+ if (implver() == IMPLVER_EV6) {
+ unsigned long spurious = summary & EXC_SUM_MASK;
+ if (spurious & (EXC_SUM_UNF | EXC_SUM_OVF)) {
+ /*
+ * EXC_SUM reports only the underflow or overflow,
+ * but the hardware sets INE alongside it in the FPCR.
+ */
+ spurious |= EXC_SUM_INE;
+ } else if (!spurious) {
+ /*
+ * No exception reported, so this was a denormal
+ * operand trap, for which INE and UNF can be
+ * fabricated as well.
+ */
+ spurious = EXC_SUM_INE | EXC_SUM_UNF;
+ }
+
+ /*
+ * Never clear an exception software has confirmed. Every
+ * instruction that genuinely raises one traps for software
+ * completion and is recorded in ieee_state above, so a bit
+ * found there -- including one just set from _fex -- belongs
+ * to this or an earlier instruction and must survive.
+ */
+ spurious &= ~(current_thread_info()->ieee_state
+ >> IEEE_STATUS_TO_EXCSUM_SHIFT);
+
+ swcr &= ~(spurious << IEEE_STATUS_TO_EXCSUM_SHIFT);
+ }
+
+ /*
+ * Update hardware control register. This has to happen even when
+ * soft-fp raised nothing, to clear any fabricated bits.
+ */
+ fpcr &= (~FPCR_MASK | FPCR_DYN_MASK);
+ fpcr |= ieee_swcr_to_fpcr(swcr);
+ wrfpcr(fpcr);
+
+ if (_fex) {
/* Do we generate a signal? */
_fex = _fex & swcr & IEEE_TRAP_ENABLE_MASK;
si_code = 0;
@@ -387,9 +446,16 @@ alpha_fp_emul_imprecise (struct pt_regs *regs, unsigned long write_mask)
break;
}
if (!write_mask) {
- /* Re-execute insns in the trap-shadow. */
+ /*
+ * Re-execute insns in the trap-shadow. Pass no
+ * exception summary: it describes the trap, which
+ * was taken anywhere in the shadow, and so is not
+ * attribution for this instruction. Nothing is
+ * lost, since only EV6 -- which traps precisely and
+ * never comes this way -- needs it.
+ */
regs->pc = trigger_pc + 4;
- si_code = alpha_fp_emul(trigger_pc);
+ si_code = alpha_fp_emul(trigger_pc, 0);
goto egress;
}
trigger_pc -= 4;
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/3] alpha: determine tininess after rounding in the FP emulation
2026-08-03 23:40 [PATCH 0/3] alpha: fix floating-point exception state handling Matt Turner
2026-08-03 23:40 ` [PATCH 1/3] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally Matt Turner
2026-08-03 23:40 ` [PATCH 2/3] alpha: don't leak hardware-fabricated FP exception bits to user space Matt Turner
@ 2026-08-03 23:40 ` Matt Turner
2026-08-05 9:24 ` Magnus Lindholm
2 siblings, 1 reply; 7+ messages in thread
From: Matt Turner @ 2026-08-03 23:40 UTC (permalink / raw)
To: Richard Henderson, Matt Turner, Magnus Lindholm
Cc: linux-alpha, linux-kernel, sparclinux, linuxppc-dev, linux-sh,
David S. Miller, Andreas Larsson, stable
IEEE 754 lets an architecture determine tininess of a floating-point
result either before or after rounding, but requires the same choice for
every operation. Alpha determines it after rounding, and stdlib's
tst-tininess confirms the hardware does so for the results it produces
itself.
The soft-fp emulation has no notion of the distinction and always
determines tininess before rounding, so a result that the hardware would
not consider tiny is reported as underflowing whenever the instruction
happens to trap for software completion. The two paths then disagree on
the same machine. A multiply of the largest subnormal double by
1 + 2^-52 rounds up to the smallest normal and raises no underflow when
the operands are normal, but raises it when an operand is subnormal and
the instruction traps:
glibc math testsuite, test-float32x-float64-mul:
Failure: mul_double (0x3.ffffffffffffcp-1024, 0x1.0000000000001p+0):
Exception "Underflow" set
Add _FP_TININESS_AFTER_ROUNDING, as glibc's copy of soft-fp has, and set
it for alpha. It determines tininess by rounding a copy of the result as
if the exponent range were unbounded, which is what the definition asks
for; a plain check of whether the rounded result came out normal is not
equivalent and would be wrong for values that stay tiny under an
unbounded exponent range but round up to the smallest normal in the
subnormal grid. The macro defaults to zero, so powerpc, sh and sparc
keep determining tininess before rounding as they do now.
Cc: stable@vger.kernel.org # 5.15+
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/include/asm/sfp-machine.h | 4 ++++
include/math-emu/op-common.h | 23 +++++++++++++++++++++--
include/math-emu/soft-fp.h | 8 ++++++++
3 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/arch/alpha/include/asm/sfp-machine.h b/arch/alpha/include/asm/sfp-machine.h
index 5fe63afbd474..bff1ad963c68 100644
--- a/arch/alpha/include/asm/sfp-machine.h
+++ b/arch/alpha/include/asm/sfp-machine.h
@@ -59,6 +59,10 @@
R##_c = FP_CLS_NAN; \
} while (0)
+/* Alpha determines tininess after rounding, so the emulation must do the
+ same as the hardware does for the results it produces itself. */
+#define _FP_TININESS_AFTER_ROUNDING 1
+
/* Obtain the current rounding mode. */
#define FP_ROUNDMODE mode
#define FP_RND_NEAREST (FPCR_DYN_NORMAL >> FPCR_DYN_SHIFT)
diff --git a/include/math-emu/op-common.h b/include/math-emu/op-common.h
index 8ce066c035cf..1d1ce5c08efc 100644
--- a/include/math-emu/op-common.h
+++ b/include/math-emu/op-common.h
@@ -135,6 +135,24 @@ do { \
else \
{ \
/* we've got a denormalized number */ \
+ int _FP_PACK_CANONICAL_is_tiny = 1; \
+ if (_FP_TININESS_AFTER_ROUNDING && X##_e == 0) \
+ { \
+ /* Architectures that detect tininess after rounding \
+ only signal underflow if the result is still \
+ subnormal once rounded as if the exponent range \
+ were unbounded. Round a copy to find out. */ \
+ FP_DECL_##fs(_FP_PACK_CANONICAL_T); \
+ /* The class field is not used by the rounding below, \
+ and is unused entirely where this block is dead. */ \
+ (void)_FP_PACK_CANONICAL_T##_c; \
+ _FP_FRAC_COPY_##wc(_FP_PACK_CANONICAL_T, X); \
+ _FP_PACK_CANONICAL_T##_s = X##_s; \
+ _FP_PACK_CANONICAL_T##_e = X##_e; \
+ _FP_ROUND(wc, _FP_PACK_CANONICAL_T); \
+ if (_FP_FRAC_OVERP_##wc(fs, _FP_PACK_CANONICAL_T)) \
+ _FP_PACK_CANONICAL_is_tiny = 0; \
+ } \
X##_e = -X##_e + 1; \
if (X##_e <= _FP_WFRACBITS_##fs) \
{ \
@@ -161,8 +179,9 @@ do { \
_FP_FRAC_SRL_##wc(X, _FP_WORKBITS); \
} \
} \
- if ((FP_CUR_EXCEPTIONS & FP_EX_INEXACT) || \
- (FP_TRAPPING_EXCEPTIONS & FP_EX_UNDERFLOW)) \
+ if (_FP_PACK_CANONICAL_is_tiny \
+ && ((FP_CUR_EXCEPTIONS & FP_EX_INEXACT) || \
+ (FP_TRAPPING_EXCEPTIONS & FP_EX_UNDERFLOW))) \
FP_SET_EXCEPTION(FP_EX_UNDERFLOW); \
} \
else \
diff --git a/include/math-emu/soft-fp.h b/include/math-emu/soft-fp.h
index 5650c1628383..02ada0a9fca1 100644
--- a/include/math-emu/soft-fp.h
+++ b/include/math-emu/soft-fp.h
@@ -31,6 +31,14 @@
#include <endian.h>
#endif
+/* Whether the architecture determines tininess of a floating-point
+ result after rounding rather than before it. IEEE 754 permits either
+ but requires the same choice for every operation, so this has to agree
+ with what the hardware does for the results it produces itself. */
+#ifndef _FP_TININESS_AFTER_ROUNDING
+#define _FP_TININESS_AFTER_ROUNDING 0
+#endif
+
#define _FP_WORKBITS 3
#define _FP_WORK_LSB ((_FP_W_TYPE)1 << 3)
#define _FP_WORK_ROUND ((_FP_W_TYPE)1 << 2)
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 3/3] alpha: determine tininess after rounding in the FP emulation
2026-08-03 23:40 ` [PATCH 3/3] alpha: determine tininess after rounding in the FP emulation Matt Turner
@ 2026-08-05 9:24 ` Magnus Lindholm
0 siblings, 0 replies; 7+ messages in thread
From: Magnus Lindholm @ 2026-08-05 9:24 UTC (permalink / raw)
To: Matt Turner
Cc: Richard Henderson, linux-alpha, linux-kernel, sparclinux,
linuxppc-dev, linux-sh, David S. Miller, Andreas Larsson, stable
On Tue, Aug 4, 2026 at 1:40 AM Matt Turner <mattst88@gmail.com> wrote:
>
> IEEE 754 lets an architecture determine tininess of a floating-point
> result either before or after rounding, but requires the same choice for
> every operation. Alpha determines it after rounding, and stdlib's
> tst-tininess confirms the hardware does so for the results it produces
> itself.
>
> The soft-fp emulation has no notion of the distinction and always
> determines tininess before rounding, so a result that the hardware would
> not consider tiny is reported as underflowing whenever the instruction
> happens to trap for software completion. The two paths then disagree on
> the same machine. A multiply of the largest subnormal double by
> 1 + 2^-52 rounds up to the smallest normal and raises no underflow when
> the operands are normal, but raises it when an operand is subnormal and
> the instruction traps:
>
> glibc math testsuite, test-float32x-float64-mul:
> Failure: mul_double (0x3.ffffffffffffcp-1024, 0x1.0000000000001p+0):
> Exception "Underflow" set
>
> Add _FP_TININESS_AFTER_ROUNDING, as glibc's copy of soft-fp has, and set
> it for alpha. It determines tininess by rounding a copy of the result as
> if the exponent range were unbounded, which is what the definition asks
> for; a plain check of whether the rounded result came out normal is not
> equivalent and would be wrong for values that stay tiny under an
> unbounded exponent range but round up to the smallest normal in the
> subnormal grid. The macro defaults to zero, so powerpc, sh and sparc
> keep determining tininess before rounding as they do now.
>
> Cc: stable@vger.kernel.org # 5.15+
> Signed-off-by: Matt Turner <mattst88@gmail.com>
> ---
> arch/alpha/include/asm/sfp-machine.h | 4 ++++
> include/math-emu/op-common.h | 23 +++++++++++++++++++++--
> include/math-emu/soft-fp.h | 8 ++++++++
> 3 files changed, 33 insertions(+), 2 deletions(-)
>
> diff --git a/arch/alpha/include/asm/sfp-machine.h b/arch/alpha/include/asm/sfp-machine.h
> index 5fe63afbd474..bff1ad963c68 100644
> --- a/arch/alpha/include/asm/sfp-machine.h
> +++ b/arch/alpha/include/asm/sfp-machine.h
> @@ -59,6 +59,10 @@
> R##_c = FP_CLS_NAN; \
> } while (0)
>
> +/* Alpha determines tininess after rounding, so the emulation must do the
> + same as the hardware does for the results it produces itself. */
> +#define _FP_TININESS_AFTER_ROUNDING 1
> +
> /* Obtain the current rounding mode. */
> #define FP_ROUNDMODE mode
> #define FP_RND_NEAREST (FPCR_DYN_NORMAL >> FPCR_DYN_SHIFT)
> diff --git a/include/math-emu/op-common.h b/include/math-emu/op-common.h
> index 8ce066c035cf..1d1ce5c08efc 100644
> --- a/include/math-emu/op-common.h
> +++ b/include/math-emu/op-common.h
> @@ -135,6 +135,24 @@ do { \
> else \
> { \
> /* we've got a denormalized number */ \
> + int _FP_PACK_CANONICAL_is_tiny = 1; \
> + if (_FP_TININESS_AFTER_ROUNDING && X##_e == 0) \
> + { \
> + /* Architectures that detect tininess after rounding \
> + only signal underflow if the result is still \
> + subnormal once rounded as if the exponent range \
> + were unbounded. Round a copy to find out. */ \
> + FP_DECL_##fs(_FP_PACK_CANONICAL_T); \
> + /* The class field is not used by the rounding below, \
> + and is unused entirely where this block is dead. */ \
> + (void)_FP_PACK_CANONICAL_T##_c; \
> + _FP_FRAC_COPY_##wc(_FP_PACK_CANONICAL_T, X); \
> + _FP_PACK_CANONICAL_T##_s = X##_s; \
> + _FP_PACK_CANONICAL_T##_e = X##_e; \
> + _FP_ROUND(wc, _FP_PACK_CANONICAL_T); \
> + if (_FP_FRAC_OVERP_##wc(fs, _FP_PACK_CANONICAL_T)) \
> + _FP_PACK_CANONICAL_is_tiny = 0; \
> + } \
> X##_e = -X##_e + 1; \
> if (X##_e <= _FP_WFRACBITS_##fs) \
> { \
> @@ -161,8 +179,9 @@ do { \
> _FP_FRAC_SRL_##wc(X, _FP_WORKBITS); \
> } \
> } \
> - if ((FP_CUR_EXCEPTIONS & FP_EX_INEXACT) || \
> - (FP_TRAPPING_EXCEPTIONS & FP_EX_UNDERFLOW)) \
> + if (_FP_PACK_CANONICAL_is_tiny \
> + && ((FP_CUR_EXCEPTIONS & FP_EX_INEXACT) || \
> + (FP_TRAPPING_EXCEPTIONS & FP_EX_UNDERFLOW))) \
> FP_SET_EXCEPTION(FP_EX_UNDERFLOW); \
> } \
> else \
> diff --git a/include/math-emu/soft-fp.h b/include/math-emu/soft-fp.h
> index 5650c1628383..02ada0a9fca1 100644
> --- a/include/math-emu/soft-fp.h
> +++ b/include/math-emu/soft-fp.h
> @@ -31,6 +31,14 @@
> #include <endian.h>
> #endif
>
> +/* Whether the architecture determines tininess of a floating-point
> + result after rounding rather than before it. IEEE 754 permits either
> + but requires the same choice for every operation, so this has to agree
> + with what the hardware does for the results it produces itself. */
> +#ifndef _FP_TININESS_AFTER_ROUNDING
> +#define _FP_TININESS_AFTER_ROUNDING 0
> +#endif
> +
> #define _FP_WORKBITS 3
> #define _FP_WORK_LSB ((_FP_W_TYPE)1 << 3)
> #define _FP_WORK_ROUND ((_FP_W_TYPE)1 << 2)
>
> --
> 2.54.0
>
Hi,
The generic soft-fp policy switch together with the Alpha-specific
selection looks like the right approach. The default preserves the
existing behaviour for the other soft-fp users, so I do not see an
expected behavioural change outside Alpha.
I tested the complete series with CONFIG_MATHEMU=y on the same machine
and using the same glibc build. The kernel boots normally, and the
glibc math testsuite improves from 48 to 28 failing test programs, with
no new failing tests. In particular, math/test-float32x-float64-mul,
which exercises the incorrect tininess handling described in the commit
message, now passes.
Reviewed-by: Magnus Lindholm linmag7@gmail.com
Tested-by: Magnus Lindholm linmag7@gmail.com
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/3] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally
2026-08-03 23:40 ` [PATCH 1/3] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally Matt Turner
@ 2026-08-05 9:28 ` Magnus Lindholm
0 siblings, 0 replies; 7+ messages in thread
From: Magnus Lindholm @ 2026-08-05 9:28 UTC (permalink / raw)
To: Matt Turner
Cc: Richard Henderson, linux-alpha, linux-kernel, sparclinux,
linuxppc-dev, linux-sh, David S. Miller, Andreas Larsson, stable
On Tue, Aug 4, 2026 at 1:40 AM Matt Turner <mattst88@gmail.com> wrote:
>
> ieee_swcr_to_fpcr() converts the software IEEE trap-enable and status
> bits kept in thread_info.ieee_state into the hardware FPCR format. It
> contained:
>
> fp |= (~sw & IEEE_TRAP_ENABLE_DNO) << 41;
>
> FPCR_DNOD (bit 47) disables denormal operand traps: with it set the
> hardware handles a denormal operand itself, treating it as zero, instead
> of trapping for software completion. The intent was to set DNOD when the
> user has not asked for SIGFPE on denormal operands, but
> IEEE_TRAP_ENABLE_DNO is clear by default, so ieee_swcr_to_fpcr(0) always
> set DNOD.
>
> Instructions built with the software completion suffix therefore never
> trapped on a denormal operand. The hardware silently substituted zero
> and produced wrong results, affecting every program compiled with -mieee
> and default FPU settings, glibc included.
>
> Set FPCR_DNOD only when IEEE_MAP_DMZ is requested, which is exactly the
> case where flushing denormal inputs to zero is what the user asked for.
> DNOD then encodes MAP_DMZ, which ieee_fpcr_to_swcr() already recovers
> from FPCR_DNZ, so drop its attempt to recover IEEE_TRAP_ENABLE_DNO from
> DNOD; the DNO trap enable lives solely in ieee_state.
>
> Both functions are in a uapi header, so the encoding change is visible to
> userspace, but nothing outside the kernel is known to depend on DNOD
> carrying the DNO trap enable, and the kernel is the only writer of the
> FPCR.
>
> This must not be backported on its own. Re-enabling denormal operand
> traps exposes a second bug, fixed in the following patch: those traps
> usually find an exact result, and for an exact result the emulator did
> not write the FPCR back, leaving hardware-fabricated exception bits
> visible to user space. Taken alone this change would make spurious
> exception flags more common.
>
> The bug predates the git history, so there is no commit to reference in a
> Fixes tag.
>
> Cc: stable@vger.kernel.org # 5.15+
> Signed-off-by: Matt Turner <mattst88@gmail.com>
> ---
> arch/alpha/include/uapi/asm/fpu.h | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/arch/alpha/include/uapi/asm/fpu.h b/arch/alpha/include/uapi/asm/fpu.h
> index cea9eafa056f..d28dc36786e2 100644
> --- a/arch/alpha/include/uapi/asm/fpu.h
> +++ b/arch/alpha/include/uapi/asm/fpu.h
> @@ -101,7 +101,12 @@ ieee_swcr_to_fpcr(unsigned long sw)
> | IEEE_TRAP_ENABLE_OVF)) << 48;
> fp |= (~sw & (IEEE_TRAP_ENABLE_UNF | IEEE_TRAP_ENABLE_INE)) << 57;
> fp |= (sw & IEEE_MAP_UMZ ? FPCR_UNDZ | FPCR_UNFD : 0);
> - fp |= (~sw & IEEE_TRAP_ENABLE_DNO) << 41;
> + /*
> + * Disable denormal operand traps only when denormal inputs are to be
> + * flushed to zero. Otherwise they must keep trapping, so that /S
> + * instructions reach the kernel emulation handler.
> + */
> + fp |= (sw & IEEE_MAP_DMZ ? FPCR_DNOD : 0);
> return fp;
> }
>
> @@ -116,7 +121,6 @@ ieee_fpcr_to_swcr(unsigned long fp)
> | IEEE_TRAP_ENABLE_OVF);
> sw |= (~fp >> 57) & (IEEE_TRAP_ENABLE_UNF | IEEE_TRAP_ENABLE_INE);
> sw |= (fp >> 47) & IEEE_MAP_UMZ;
> - sw |= (~fp >> 41) & IEEE_TRAP_ENABLE_DNO;
> return sw;
> }
>
>
> --
> 2.54.0
>
Hi,
The new mapping of FPCR_DNOD to IEEE_MAP_DMZ looks correct to me.
I tested the complete series with CONFIG_MATHEMU=y on the same machine
and using the same glibc build. The kernel boots normally, and the
glibc math testsuite improves from 48 to 28 failing test programs, with
no new failing tests.
Reviewed-by: Magnus Lindholm linmag7@gmail.com
Tested-by: Magnus Lindholm linmag7@gmail.com
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/3] alpha: don't leak hardware-fabricated FP exception bits to user space
2026-08-03 23:40 ` [PATCH 2/3] alpha: don't leak hardware-fabricated FP exception bits to user space Matt Turner
@ 2026-08-05 9:33 ` Magnus Lindholm
0 siblings, 0 replies; 7+ messages in thread
From: Magnus Lindholm @ 2026-08-05 9:33 UTC (permalink / raw)
To: Matt Turner
Cc: Richard Henderson, linux-alpha, linux-kernel, sparclinux,
linuxppc-dev, linux-sh, David S. Miller, Andreas Larsson, stable
On Tue, Aug 4, 2026 at 1:40 AM Matt Turner <mattst88@gmail.com> wrote:
>
> On EV6 and later the hardware records exception status bits in the FPCR
> before delivering a software completion trap, and those bits can be wrong
> for the instruction that trapped. Converting a double that is exactly
> representable as a subnormal float sets FPCR_UNF even though the result
> is exact, and an underflow trap additionally sets FPCR_INE even when the
> emulated operation turns out to be exact.
>
> alpha_fp_emul() only wrote the FPCR when soft-fp raised an exception, so
> whenever it determined that the instruction was exact the fabricated bits
> stayed in the FPCR and were reported to user space by fetestexcept().
>
> Pass the exception summary register down from do_entArith() so the
> handler can tell which exceptions the hardware attributed to the trapping
> instruction, and always write the FPCR. Clear the exceptions that the
> trap reported but that soft-fp did not raise. EXC_SUM reports only the
> underflow or overflow when the hardware also set INE, so treat INE as a
> candidate in that case, and treat a trap with no reported exception as a
> denormal operand trap, for which the hardware can fabricate INE and UNF
> as well. Bits that software has already confirmed in ieee_state belong
> to this or an earlier instruction and are never cleared.
>
> The imprecise path passes no summary. There the trap was taken somewhere
> in the trap shadow, so EXC_SUM is not attribution for the instruction
> being re-executed -- and only EV6, which traps precisely and so never
> takes that path, has fabricated bits to clear. For the same reason the
> clearing is guarded by implver(), matching swcr_update_status().
>
> On an UP1500 (EV68) this takes the glibc math testsuite from 831 failures
> to 28, the remainder being unrelated to exception status.
>
> This belongs with the preceding fix to ieee_swcr_to_fpcr(), and should
> not be backported without it -- nor it without this. That fix stops
> FPCR_DNOD being set unconditionally, so denormal operand traps start
> firing again. Those traps very often find an exact result, which is
> precisely the case where the old code left the FPCR unwritten and the
> fabricated bits visible. Applied alone it would make spurious exception
> flags more common, not less.
>
> One case cannot be resolved here: an inexact instruction without the
> software completion suffix never traps, so its INE reaches the FPCR
> without being recorded anywhere else. Such a bit is indistinguishable
> from an INE the hardware fabricated for a trapping instruction, and is
> lost if an underflow or overflow trap with an exact result follows it.
> The FPCR is the only record of those instructions and it carries no
> attribution.
>
> The bug predates the git history, so there is no commit to reference in a
> Fixes tag.
>
> Cc: stable@vger.kernel.org # 5.15+
> Signed-off-by: Matt Turner <mattst88@gmail.com>
> ---
> arch/alpha/kernel/traps.c | 6 ++--
> arch/alpha/math-emu/math.c | 88 ++++++++++++++++++++++++++++++++++++++++------
> 2 files changed, 80 insertions(+), 14 deletions(-)
>
Hi,
Passing EXC_SUM to the precise emulation path and always writing the
corrected FPCR state back looks correct to me.
I tested the complete series with CONFIG_MATHEMU=y on the same machine
and using the same glibc build. The kernel boots normally, and the
glibc math testsuite improves from 48 to 28 failing test programs, with
no new failing tests. Tested on Alphaserver ES40.
Reviewed-by: Magnus Lindholm linmag7@gmail.com
Tested-by: Magnus Lindholm linmag7@gmail.com
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-05 9:34 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 23:40 [PATCH 0/3] alpha: fix floating-point exception state handling Matt Turner
2026-08-03 23:40 ` [PATCH 1/3] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally Matt Turner
2026-08-05 9:28 ` Magnus Lindholm
2026-08-03 23:40 ` [PATCH 2/3] alpha: don't leak hardware-fabricated FP exception bits to user space Matt Turner
2026-08-05 9:33 ` Magnus Lindholm
2026-08-03 23:40 ` [PATCH 3/3] alpha: determine tininess after rounding in the FP emulation Matt Turner
2026-08-05 9:24 ` Magnus Lindholm
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox