Table of Contents
Floating Point
Floating point is a frequent source of porting problems on Alpha. Programs that behave correctly on other architectures may raise SIGFPE, return zero where a denormal is required, or run substantially slower than expected.
Most of these effects follow from one design decision: arithmetic traps on Alpha are imprecise. The processor does not stop at the instruction that overflowed; execution continues and the trap arrives later. IEEE 754 conformance therefore requires cooperation from the compiler, the kernel and the C library, and incorrect results follow if any of the three does not provide it. The compiler's part is the -mieee option, which the Debian and Gentoo compilers enable by default and upstream GCC does not.
See also Instruction Set and Architecture Mask (amask).
Practical consequences
- Code built with the Debian or Gentoo GCC gets
-mieeeby default, and so usually behaves as on other architectures. Code built with an upstream GCC, or with Debian's GCC and-ffast-mathor-ffinite-math-only, does not (see Compiler flags). The typical symptom isSIGFPEon an operation with a NaN, infinity, or denormal operand, or a denormal result silently replaced by zero. - A program that depends on NaNs, infinities, denormals or the IEEE exception flags must be compiled entirely with
-mieee. Objects compiled without it do not conform, and the resulting program behaves inconsistently between translation units. -mieee-with-inexactis required only where the inexact flag is read, and is the more expensive mode.-mieeeis useful when debugging any floating-point problem, whether or not it is IEEE related, because it reportsSIGFPEat the faulting instruction rather than some instructions later. 1)- Software completion requires
CONFIG_MATHEMUin the kernel. Without it, correct programs receiveSIGFPE. - Exception flags are tested through
<fenv.h>rather than by reading the FPCR status bits, since the authoritative state is the kernel's software word and the FPCR can carry spurious bits from trap shadows. -mieeeis expensive on processors before the EV6, and cheap on the EV6 and later only when the code is compiled with-mcpu=ev6or-mcpu=ev67. Code built forev56pays the full cost even on an EV6 (see What -mieee costs).fesetround()has no effect on code compiled with the default-mfp-rounding-mode=n. glibc'sfesetround()sets the dynamic rounding field of the FPCR, 2) which only instructions with the/Dqualifier consult; the others round as encoded in the instruction (see Rounding modes). Code that changes the rounding mode at run time, such as interval arithmetic or tests of<fenv.h>rounding, must be compiled with-mfp-rounding-mode=d, as glibc itself is. 3)-frounding-mathalone is not enough before GCC 17, in which it also selects dynamic rounding on Alpha. 4)floatarithmetic is genuinely single precision, so code tuned around x87 excess precision behaves differently.
Compiler flags
| Flag | Effect |
|---|---|
-mieee | IEEE-conformant except that the inexact flag is not maintained. Sets -mtrap-precision=i (except with -mcpu=ev6 or later; see Trap shadows) and emits /SU. Defines _IEEE_FP. |
-mieee-with-inexact | as above but maintains the inexact flag; emits /SUI. Additionally defines _IEEE_FP_INEXACT. Significantly slower. |
-mfp-trap-mode= | n (default), u, su, sui |
-mfp-rounding-mode= | n (default), m, c, d. There is no p value, matching the ISA. From GCC 17, -frounding-math makes d the default. |
-mtrap-precision= | p (default, program), f (function), i (instruction) |
-mieee-conformant | marks the code IEEE conformant. Requires -mtrap-precision=i and -mfp-trap-mode=su or sui. Its only effect is to emit .eflag 48 in the prologue. |
Upstream GCC does not enable -mieee by default on Alpha, because of its performance cost on pre-EV6 processors, compatibility with DEC C, which does not enable it either, and because SIGFPE from subnormals in non-numerical programs usually indicates a read of uninitialized data. 5) 6)
Debian and Gentoo patch their GCC packages to enable -mieee by default. Debian's patch enables it unless -ffinite-math-only (part of -ffast-math) is given, because the glibc math functions produce denormals, NaNs, and infinities. 7) 8) Gentoo's patch adds -mno-ieee to turn it off, and is carried in nearly every Gentoo GCC patchset. 9) Code built with these compilers therefore has -mieee semantics even when the build system does not ask for them.
What -mieee costs
On processors before the EV6 the cost comes from the trap barriers, and depends on how dense the floating-point work is: a 1000x1000 matrix multiplication built with -mcpu=ev56 ran 3.7 times slower with -mieee on a 600 MHz 21164, which is close to the worst case. 10) A program that spends most of its time elsewhere pays proportionally less. With -mcpu=ev6 or -mcpu=ev67, GCC relies on the EV6's precise traps and emits no barriers, so the cost is small (see Trap shadows, TRAPB and EXCB).
Denormals
Alpha hardware does not produce denormal results. Without a trap qualifier, underflow is disabled and the result is forced to a true zero. With /SU the underflow trap fires and the software-completion handler produces the correct denormal. GCC's own comment, in gcc/config/alpha/alpha.cc:
/* ... Without software completion -- which the compiler only arranges for
-mieee, i.e. alpha_fptm >= ALPHA_FPTM_SU -- a subnormal operand is either
flushed to zero, which makes it indistinguishable from a zero, or traps
with no completion information, which is fatal. */
-mieee is therefore sufficient for denormals, and -mieee-with-inexact adds nothing in this respect. -mieee-with-inexact is expensive because inexact fires on nearly every rounded operation, not because of the underflow path.
Trap modes and software completion
The four IEEE trap modes
| Suffix | Meaning |
|---|---|
| (none) | imprecise, underflow disabled, inexact disabled |
/U | imprecise, underflow enabled, inexact disabled |
/SU | software completion, underflow enabled, inexact disabled |
/SUI | software completion, underflow enabled, inexact enabled |
and for conversions to integer:
| Suffix | Meaning |
|---|---|
| (none) | imprecise, integer overflow disabled, inexact disabled |
/V | imprecise, integer overflow enabled, inexact disabled |
/SV | software completion, integer overflow enabled, inexact disabled |
/SVI | software completion, integer overflow enabled, inexact enabled |
There is no /I qualifier. Inexact is only ever enabled as part of /SUI or /SVI. (/S alone exists, but only on VAX-format operations and on CVTST.)
Effect of the /S modifier
The modifier has no effect at execution time.
The /S modifier does not affect instruction operation or trap behavior; it is an informational bit passed to a software trap handler. It allows a trap handler to test easily whether an instruction is intended to be completed.
Specifying /SU does not make the hardware more precise. The bit is carried into the exception summary so that a trap handler can determine that an instruction was compiled to be completable without decoding the instruction stream. The work falls on the compiler, which must obey a set of trap-shadow rules and must mark every instruction to be completed:
Each floating-point instruction to be completed must be so marked, by specifying the /S software completion modifier. The /S modifier must not be used on any floating-point instruction that is not in a trap shadow that meets these conditions.
Trap shadows, TRAPB and EXCB
Because traps are imprecise, the trap arrives with the PC pointing at some instruction after the one that faulted. The region in which a trap may still surface is the trap shadow:
The extent of the trap shadow is bounded only by an EXCB or TRAPB instruction (or the implicit TRAPB within a CALL_PAL instruction).
TRAPB forces all previous arithmetic traps to be taken before any following instruction issues. EXCB is a superset of it. Bounding the shadow is what lets the trap handler find the trigger instruction by scanning backwards, and the Architecture Reference Manual's guidance is "in effect, one TRAPB instruction per basic block can be used".
-mtrap-precision=i emits these barriers, which is why IEEE conformance costs performance on pre-EV6 Alpha: a barrier per basic block defeats the instruction scheduling the architecture was designed around.
On EV6 and later the cost disappears, because the hardware reports arithmetic traps precisely. GCC relies on this:
if (alpha_cpu == PROCESSOR_EV6) { /* Except for EV6 pass 1 (not released), we always have precise arithmetic traps. Which means we can do software completion without minding trap shadows. */ alpha_tp = ALPHA_TP_PROG; }
The test is on the processor selected with -mcpu, not on the processor that runs the code: -mtune=ev6 does not change it. Code compiled with -mieee -mcpu=ev56 still contains the trap barriers when it runs on an EV6. 11)
Precise trap support is reported by AMASK bit 9: amask(1UL « 9) == 0 means precise traps are implemented. See Architecture Mask (amask) for the instruction and its polarity.
FPCR and SWCR: two different registers
FPCR is the hardware control register and SWCR is the software control word the kernel maintains per thread.
FPCR, the hardware register
| Bits | Field |
|---|---|
| 63 | Summary (SUM), bitwise OR of the exception bits |
| 62 | Inexact Disable (INED) |
| 61 | Underflow Disable (UNFD) |
| 60 | Underflow to Zero (UNDZ) |
| 59:58 | Dynamic Rounding Mode (DYN) |
| 57 | Integer Overflow (IOV) |
| 56 | Inexact Result (INE) |
| 55 | Underflow (UNF) |
| 54 | Overflow (OVF) |
| 53 | Division by Zero (DZE) |
| 52 | Invalid Operation (INV) |
| 51 | Overflow Disable (OVFD) |
| 50 | Division by Zero Disable (DZED) |
| 49 | Invalid Operation Disable (INVD) |
| 48 | Denormal operands to zero (DNZ), implementation-specific |
| 47 | Denormal operand invalid-operation trap disable (DNOD), implementation-specific |
| 46:0 | reserved, read as zero |
Bits 48 and 47 are not in the Architecture Reference Manual's table; Linux defines them as FPCR_DNZ and FPCR_DNOD. 12) The five trap disable bits may be subsetted by an implementation.
SWCR, the software register
ieee_set_fp_control() does not take an FPCR value. It takes the software IEEE control word, which the kernel keeps per thread.
| Bit | Meaning |
|---|---|
| 1 | trap enable, invalid operation |
| 2 | trap enable, division by zero |
| 3 | trap enable, overflow |
| 4 | trap enable, underflow |
| 5 | trap enable, inexact |
| 6 | trap enable, denormal |
| 12 | map denormals to zero |
| 13 | map underflow to zero |
| 17 to 22 | status bits: INV, DZE, OVF, UNF, INE, DNO |
| 63 | IEEE_INHERIT |
The reason for the duplication is stated in the kernel source:
/* Alpha Architecture Handbook 4.7.7.3:
To be fully IEEE compiant, we must track the current IEEE
exception state in software, because spurious bits can be
set in the trap shadow of a software-complete insn. */
Because a trap can surface in the shadow of a completed instruction, the FPCR's sticky status bits are not reliable. The authoritative exception state on Alpha/Linux is held in software, in thread_info→ieee_state. The kernel merges the software word into the hardware register on each SSI_IEEE_FP_CONTROL call:
*state = (*state & ~IEEE_SW_MASK) | (swcr & IEEE_SW_MASK); fpcr = rdfpcr() & FPCR_DYN_MASK; fpcr |= ieee_swcr_to_fpcr(swcr); wrfpcr(fpcr);
The kernel side
Arithmetic traps
do_entArith() in arch/alpha/kernel/traps.c handles arithmetic traps. It emulates only when the software-completion summary bit is set, and it takes one of two paths depending on whether the processor reports traps precisely:
if (summary & 1) { /* Software-completion summary bit is set, so try to 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, summary); else si_code = alpha_fp_emul_imprecise(regs, write_mask); if (si_code == 0) return; }
The imprecise path scans backwards through the trap shadow to find the trigger instruction, then hands it to the same emulator.
CONFIG_MATHEMU
All of the above is gated on CONFIG_MATHEMU. Without it, the emulator entry points are stubs:
#ifndef CONFIG_MATHEMU static long dummy_emul(void) { return 0; } long (*alpha_fp_emul_imprecise)(...) = (void *)dummy_emul; long (*alpha_fp_emul)(...) = (void *)dummy_emul; #else
and every software-completion trap becomes SIGFPE. The option lives in arch/alpha/Kconfig.debug, not arch/alpha/Kconfig, is enabled in the shipped defconfig, and can be built as a module only in a uniprocessor kernel with CONFIG_DEBUG_KERNEL set; otherwise it is always built in.
Illegal instructions
An instruction the hardware does not implement arrives as opDEC in do_entIF() and results in SIGILL with ILL_ILLOPC.
The glibc side
The Alpha-specific interface is:
extern unsigned long int __ieee_get_fp_control (void); extern void __ieee_set_fp_control (unsigned long int __value);
declared in sysdeps/alpha/fpu/bits/fenv.h and exported from sysdeps/alpha/fpu/Versions. They are thin wrappers over osf_getsysinfo(GSI_IEEE_FP_CONTROL) and osf_setsysinfo(SSI_IEEE_FP_CONTROL). Everything portable, fegetround, fesetround, feenableexcept, fetestexcept and the rest, is built on top of them. GCC also emits calls to them directly when expanding C11 atomic compound assignment on floating-point types (TARGET_ATOMIC_ASSIGN_EXPAND_FENV).
Two consequences follow:
- glibc builds itself with
-mieee.sysdeps/alpha/Makefilesetssysdep-CFLAGS += -mieee -mfp-rounding-mode=d, and uses-mieee-with-inexactfor a handful of files (s_fma.c,s_fmaf.c, thelrintandllrintfamily). - Because IEEE behavior is a property of how each object was compiled, mixing
-mieeeand non--mieeeobjects produces a program that is IEEE-conformant in some functions and not in others.
Rounding modes
Alpha encodes the rounding mode in two bits of the instruction's function field.
| Encoding | Suffix | Mode |
|---|---|---|
| 00 | /C | chopped (toward zero) |
| 01 | /M | toward minus infinity |
| 10 | (none) | round to nearest, ties to even |
| 11 | /D | dynamic: take the mode from FPCR<DYN> |
This is the same assignment used by FPCR<DYN>, with 11 reinterpreted as "dynamic". There is no static encoding for round toward plus infinity; that mode requires /D with FPCR<DYN> set to 11.
Directed rounding is an optional architectural feature:
An implementation that includes IEEE floating-point may subset the ability to perform rounding to plus infinity and minus infinity. If not implemented, instructions requesting these rounding modes take Illegal Instruction Trap.
The 21064 (EV4) does not implement it; the 21164 (EV5) and every later processor, and so every processor current kernels support, does.
Formats
Alpha supports two families. Four formats have hardware arithmetic.
| Format | Family | Size | Sign + exponent + fraction | Bias | Hardware arithmetic |
|---|---|---|---|---|---|
| F_floating | VAX | 32-bit | 1 + 8 + 23 | excess-128 | yes |
| G_floating | VAX | 64-bit | 1 + 11 + 52 | excess-1024 | yes |
| D_floating | VAX | 64-bit | 1 + 8 + 55 | excess-128 | no |
| S_floating | IEEE | 32-bit | 1 + 8 + 23 | excess-127 | yes |
| T_floating | IEEE | 64-bit | 1 + 11 + 52 | excess-1023 | yes |
| X_floating | IEEE | 128-bit | 1 + 15 + 112 | excess-16383 | no |
X_floating has no hardware support and is implemented entirely in software.
H_floating is a VAX format with no support of any kind on Alpha. The Architecture Reference Manual lists it under "Data Types with No Hardware Support" and, unlike D_floating, promises no emulation either.
D_floating is partly supported: it is loaded and stored with the G_floating instructions (the bit reordering is identical), and CVTDG/CVTGD convert to and from G. There is no D arithmetic. The Architecture Reference Manual describes the result as "'format compatibility' in which binary files of D_floating numbers may be processed, but without the last 3 bits of fraction precision".
No instruction converts between the VAX and IEEE families: there is no CVTGT and no CVTTG. Any G to T conversion is a software operation on the exponent field, together with special-value handling.
G and T are not interchangeable
Both are 1 + 11 + 52, which suggests that they are the same layout with a different bias. They are not interchangeable: VAX normalization puts the fraction in [0.5, 1) while IEEE uses an implicit leading 1, and the biases differ by one. The same 64-bit register pattern therefore denotes a value four times larger when read as T than when read as G.
For example, 0x3FF0000000000000 is 1.0 as T_floating. As G_floating the exponent field is 1023, a true exponent of 1023 - 1024 = -1, giving 0.5 x 2^-1 = 0.25. The same factor of four holds between F and S.
Register formats
Values do not sit in registers the way they sit in memory.
F_floating is expanded on load. LDF reorders the bits, widens the exponent from 8 to 11 bits and zeroes the low fraction bits, producing "in the register an equivalent G_floating number suitable for either F_floating or G_floating operations". So an F value in a register is in G register format: sign at 63, exponent at 62:52, the 23 fraction bits at 51:29, zeros at 28:0. This is why there is no F-to-G conversion instruction: none is needed.
S_floating is likewise stored as a canonical subset of T_floating: sign at 63, exponent at 62:52, fraction at 51:29, zeros at 28:0. The exponent is re-biased on load; for non-extreme values the effect is to add 896.
No excess precision
A consequence that differs from x86:
Single-precision values are stored in the floating-point registers in canonical form, as subsets of double-precision values, with 3 exponent bits set to a fixed value and the low-order fraction bits restricted to be all zero. Single-precision operations applied to canonical single-precision values give single-precision results. Single-precision operations applied to non-canonical operands give UNPREDICTABLE results.
Alpha has no equivalent of x87's 80-bit intermediate precision. float arithmetic on Alpha is genuinely single precision, and FLT_EVAL_METHOD is 0. Code that depends on x87 excess precision changes behavior on Alpha.
There are 32 floating-point registers, F0 through F31. F31 reads as a true zero and writes to it are discarded.
Comparisons
CMPTxx writes a floating 2.0 for true and a true zero for false, rather than setting condition codes.
The two operands in Fa and Fb are compared. If the relationship specified by the qualifier is true, a non-zero floating value (2.0) is written to register Fc; otherwise, a true zero is written to Fc… The unordered relation is true if one or both operands are NaN. (This behavior must be provided by a software trap handler, since NaNs trap.) Comparisons ignore the sign of zero, so +0 = -0.
Unordered comparison on Alpha therefore requires the trap handler. A CMPTUN against a NaN traps, and the software-completion path supplies the answer.
Only the less-than forms exist, because "Compare Less Than A,B is the same as Compare Greater Than B,A". The available operations are CMPTEQ, CMPTLT, CMPTLE and CMPTUN for IEEE, and CMPGEQ, CMPGLT, CMPGLE for VAX.
Two further points from the Architecture Reference Manual:
- "To branch properly on non-finite operands, compare to F31, then branch on the result of the compare." The
FBxxbranches do not interpret NaNs. - "The largest negative integer (8000 0000 0000 0000) is the same bit pattern as floating minus zero, so it is treated as equal to zero by the branch instructions."
Conversions
| Instruction | Conversion |
|---|---|
CVTQS, CVTQT | quadword integer to S, T |
CVTQF, CVTQG | quadword integer to F, G |
CVTTQ | T to quadword integer |
CVTGQ | G to quadword integer |
CVTLQ, CVTQL | longword and quadword integer, in registers |
CVTTS | T to S |
CVTST | S to T |
CVTGF | G to F (F to G needs no instruction) |
CVTDG, CVTGD | D to G, G to D |
CVTST is exact and, for finite values, is the identity transformation on the register contents: "No rounding occurs. No underflow, overflow, or inexact result can occur." Its one listed exception is invalid operation, so a signaling NaN input does trap.
CVTGF rounds a 52-bit fraction to 23 bits, losing 29.
As noted above, no conversion instruction exists between the VAX and IEEE families in either direction.
References
- Alpha AXP Architecture Reference Manual, Second Edition. Section 2.2 covers the data formats; 4.7 covers the floating-point subsets, rounding modes, trap modes and the FPCR (Table 4-8); 4.7.6.1 states the seven software-completion conditions; 4.10 is the instruction reference.
- Alpha Architecture Handbook, Version 4, section 4.11.1 for
AMASKand Appendix D, Table D-3 for the bit assignments. - DECchip 21064 and 21164 hardware reference manuals, for which rounding modes each implements.
- ''arch/alpha/include/uapi/asm/fpu.h'' for the FPCR and SWCR definitions, ''traps.c'' for
do_entArith, ''osf_sys.c'' forosf_setsysinfo, andarch/alpha/math-emu/for the emulator. - GCC:
gcc/config/alpha/alpha.optand the DEC Alpha Options node ofgcc/doc/invoke.texi. - glibc:
sysdeps/alpha/fpu/andsysdeps/unix/sysv/linux/alpha/.
