Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,44 @@ hashes are given where a change is easiest to understand by reading the commit.
bus's devices; and a `Jitv2` joins its workers before it frees the pages
they read. The Indy, Indigo2 and O2 still leak their tables.

### cpu: FPU Invalid and Overflow

- **Invalid operations raise Invalid.** Infinity minus infinity, zero times
infinity, 0/0 and infinity/infinity set Cause and Flag V and, untrapped,
deliver the MIPS default quiet NaN (0x7FBFFFFF, 0x7FF7FFFF_FFFFFFFF) instead
of the host's NaN. 0/0 raises Invalid rather than divide-by-zero, and
infinity/0 raises nothing.
- **Overflow raises Overflow and Inexact** in ADD, SUB, MUL and DIV. The result
was already rounded for the FCSR rounding mode (an infinity, or the largest
finite number when the mode rounds toward zero); only the flags were
missing. With Overflow enabled it now traps.
- SQRT, RECIP, RSQRT, MADD, MSUB, NMADD, NMSUB and CVT.S.D deliver the default
NaN for an Invalid Operation (the square root of a negative number,
infinity times zero, a signalling NaN operand) and raise V where they raised
nothing. They still compute no Overflow or Inexact.
- Applies to every CPU model. An ordinary result costs no more host
instructions than before in either engine: one range compare on the result
replaces the denormal test, and the multiply-add family, RECIP and RSQRT now
classify their operands only when the result is an infinity or a NaN.
- The Octane PROM diagnostics' FPU invalid-result and overflow-result tests
pass (14 of 14 `ide fpu` subtests).

### cpu: signalling and quiet NaNs use the MIPS encoding

- The FPU told the two NaN kinds apart with the IEEE 754-2008 quiet bit. The
R4400, R5000 and R10000 use the original MIPS encoding, where a NaN with the
fraction MSB set is signalling and one with it clear is quiet, so the default
NaN is 0x7FBFFFFF and 0x7FC00000 is signalling. A signalling operand now
raises Invalid and a quiet one takes the Unimplemented path, in the
interpreter and in compiled code, matching the R4400 results in cpu-tests
`fpu/qnan_operand`.

### jitv2: FCSR.FS flushes denormal results

- Compiled code trapped every denormal ADD/SUB/MUL/DIV/SQRT result as
Unimplemented, even with FS set. It now flushes to a signed zero with Cause
and Flag U|I, like the interpreter.

### Disk write cache

- A disk now has a `write_cache` setting in its `[scsi.N]` table (and a
Expand Down
56 changes: 52 additions & 4 deletions clanker/jitv2/fpu-flags-are-computed-not-read.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,58 @@ formulation both engines can implement identically.

For plain ADD/SUB/MUL/DIV/SQRT/RECIP/RSQRT and the MADD family, `flags` is **0**
unless a specific condition applies (signalling-NaN operand -> FV,
divide-by-zero -> FZ). In particular:
divide-by-zero -> FZ, and the result rules below). In
particular:

- **Inexact (FI) is never set by arithmetic.**
- **Overflow (FO) is never set by arithmetic.**
- **Inexact (FI) is never set by arithmetic on its own.** It is set only as
part of an Overflow.
- **Overflow (FO) and Invalid (FV) are computed for ADD/SUB/MUL/DIV only**,
from the result: one range test on the result's magnitude, which replaced
the denormal-result test on the same path, sends zero, denormals, ±MAX,
±infinity and NaN to a cold side. There `fpu_binop_top_flags_s/d`
(`mips_exec.rs`) classifies the top of the range: a NaN from non-NaN
operands is Invalid (inf-inf, 0*inf, 0/0, inf/inf) and delivers the MIPS
default NaN 0x7FBFFFFF / 0x7FF7FFFF_FFFFFFFF; an infinity, or a ±MAX whose
exact value reaches 2^(emax+1), from finite operands is Overflow+Inexact.
The JIT reaches the same function through `fpu_binop_commit_fn`, one
call that also does the operand checks and the denormal-result rule for
a denormal, infinite or NaN operand or a denormal or top-of-range result
(zero operands and results stay inline), so the engines cannot disagree
and each ADD/SUB/MUL/DIV carries one call, not a copy of the rules. 0/0 is Invalid, not divide-by-zero; FZ needs a
finite nonzero dividend. The Octane PROM's IDE `fpu` test checks both
(its "invalid result" and "overflow result" cases). SQRT, RECIP,
RSQRT, the MADD, MSUB, NMADD and NMSUB families and CVT.S.D follow the
Invalid rule only (next bullet) and still compute no Overflow or Inexact:
an overflowing result from any of them is delivered as the host rounds
it, with FCSR untouched. CVT.S.D has its own overflow check but no
Inexact-with-Overflow.
- **Invalid results of the operations above deliver the MIPS default NaN.**
The host's NaN for an invalid result (x86: 0xFFC00000) has the quiet bit
set, which is a *signalling* NaN under the MIPS encoding, so an
instruction that read it would raise V where hardware takes E.
`fpu_fix_invalid_s/d` (interpreter) and `emit_fix_invalid` (JIT) replace
a NaN result with 0x7FBFFFFF / 0x7FF7FFFF_FFFFFFFF and add V when an
operand was a signalling NaN or no operand was a NaN (infinity times
zero, infinity minus infinity, the square root of a negative number). A
quiet NaN operand propagates unchanged. Both engines gate it on one
compare of the result's magnitude, with the classification on the cold
side. A finite result proves the operands raise nothing (a NaN operand
makes the host result a NaN, a zero divisor or an infinity makes it an
infinity, a negative square-rooted operand makes it a NaN), so for RECIP,
RSQRT and the MADD family the operand flags are not even evaluated on that
path (`fpu_fix_invalid_s/d` takes them as a closure,
`emit_commit_finite_first` as an emitter). CVT.S.D, which runs in `cvt_d_to_s_and_commit` for both engines,
does the same for a signalling NaN source.

Known limit: RECIP, RSQRT and the MADD family have no operand check, so a
quiet-NaN operand (quiet bit clear, the MIPS encoding) propagates with the
host's quieting, which sets the fraction MSB. The result, for example
0x7FE00000, then reads as a *signalling* NaN to the next operation, which
raises V where hardware probably takes E. SQRT traps a quiet-NaN operand
with E before this point. What an R10000 does with a quiet-NaN operand to
these operations is not stated in the manuals we have, so it is left.
A quiet-NaN source to CVT.S.D has a related quirk: it is never equal to
its widened result, so it leaves the exact path and sets Cause and Flag I.

Stated in `fpu_update_fcsr`'s doc comment as: *"not worth the IR to compute for
ordinary arithmetic, and nothing in IRIX depends on them."* That trade is sound
Expand All @@ -41,7 +89,7 @@ the computed *result* is correct in every case.
| `fpu/vec_arith_single` | `o.flags` got `0x0`, want `0x1`/`0x5` — missing FI |
| `fpu/vec_arith_double` | same |
| `fpu/vec_sqrt` | `o.flags [sqrt.s]` got `0x0`, want `0x1` — missing FI |
| `fpu/cvt_s_d_rounds` | got `0x4`, want `0x5` — has FO, missing FI |
| `fpu/cvt_s_d_rounds` | got `0x4`, want `0x5` — has FO, missing FI (CVT.S.D, not covered by the ADD/SUB/MUL/DIV overflow rule) |
| `fpu/cvt_out_of_range` | `cvt.w.s` of 2^31 does not set FV (though `+inf` does) |

`fpu/cvt_out_of_range` is the odd one out: it is about Invalid on an
Expand Down
Loading
Loading