Skip to content

riscv: avoid FMA error recovery for directed fmadd.s - #315

Open
carlosqwqqwq wants to merge 4 commits into
LekKit:stagingfrom
carlosqwqqwq:fix/fma-f32-directed
Open

carlosqwqqwq wants to merge 4 commits into
LekKit:stagingfrom
carlosqwqqwq:fix/fma-f32-directed

Conversation

@carlosqwqqwq

@carlosqwqqwq carlosqwqqwq commented Sep 6, 2026 •

Copy link
Copy Markdown

riscv: avoid FMA error recovery for directed fmadd.s

Fixes #309

Commit message

riscv: avoid FMA error recovery for directed fmadd.s

Description

The fpu_fma32 error-recovery calculation is exact only for RNE. For directed modes it can cross an f32 grid point and mishandle NX/UF. Convert the f64 sum directly in directed modes, keep the sticky NX flag untouched, and add UF only when this operation newly raises NX and produces a subnormal result. Compute the TwoSum correction only for the nearest modes. The change is limited to src/util/fpu_lib.h.

Validation

  • Rebased onto current staging (e7214efa), head 8f60a966; x86_64 make bin passes and the builtin-FMA -Werror warning is fixed.
  • Directed branch follows the requested calculation: compares post-operation flags with old_exceptions, leaves sticky NX untouched, and skips TwoSum in directed modes.
  • Guest probe matrix rerun against 8f60a966 (no-JIT and JIT shims vs qemu-riscv64):

The patch is independently applicable to the staging branch. Its scope is finite fmadd.s directed rounding under RTZ/RDN/RUP; other FMA root causes remain outside this patch.

@LekKit

LekKit commented Sep 22, 2026

Copy link
Copy Markdown
Owner

The result is correct, but flags regress: Only NX raised by fadd64 is kept, the NX/UF raised by f64->f32 is discarded. Whenever the f64 sum is exact but not representable in f32, NX is lost.
Fix: don't touch NX at all in the directed path, and only add the UF that the host cannot see (f64 add inexact, conversion exact, f32 result subnormal). Don't compute the TwoSum there either:

if (mode == FPU_LIB_ROUND_TZ || mode == FPU_LIB_ROUND_DN || mode == FPU_LIB_ROUND_UP) {
    ret = fpu_fcvt_f64_to_f32(sum);
    if ((fpu_get_exceptions() & ~old_exceptions & FPU_LIB_FLAG_NX)
        && (fpu_bit_f32_to_u32(ret) & FPU_LIB_FP32_NOSIGNED_MASK) < FPU_LIB_FP32_MINIMUM_NORM) {
        fpu_raise_exceptions(FPU_LIB_FLAG_UF); // Inexact and tiny after rounding
    }
} else {
    err = fpu_add_error64(sum, mul, add);
    ret = fpu_fcvt_f64_to_f32(fpu_odd_round64(sum, err));
}

@carlosqwqqwq

Copy link
Copy Markdown
Author

Rebased onto current staging (e7214efa) and force-updated the branch to 6a7d73aa. The directed path now follows the requested flag handling: it does not clear or restore sticky NX, compares post-operation flags with old_exceptions, adds UF only for newly inexact subnormal results, and skips TwoSum. The Linux x86_64 make bin build passes; the exact RISC-V guest probe matrix still needs to be rerun before claiming runtime results.

@carlosqwqqwq

Copy link
Copy Markdown
Author

The first CI run for 6a7d73aa exposed an architecture-specific -Werror failure: directed is only used by the software-FMA branch, so builtin-FMA targets reported it as unused. Commit 8f60a966 scopes mode and directed to !FPU_LIB_OPTIMAL_BUILTIN_FMA; the local x86_64 make bin build passes again. The RISC-V guest probe matrix remains to be rerun for this exact head.

@carlosqwqqwq

Copy link
Copy Markdown
Author

Follow-up: the latest head is 8f60a966. GitHub Build and CodeQL now pass across the matrix after the builtin-FMA conditional-compilation fix. The guest probe matrix is still the remaining runtime validation item.

@carlosqwqqwq

Copy link
Copy Markdown
Author

Guest probe matrix rerun against this exact head (8f60a966) on x86_64 (software-FMA path).

  • f32_single_probe (16 lines), fma_f32_rdn_probe (24 lines), f32_min_probe (1 line): 0 differences vs qemu-riscv64, in both the no-JIT and JIT shims.
  • fma_f32_random_probe (16000 lines): all 663 flag differences are rne rows, keep identical result bits, and are the pre-existing round-to-nearest NX|UF behaviour outside this patch. Every rtz/rdn/rup row matches QEMU bit-for-bit, including flags.

So the directed path this PR changes is fully aligned with QEMU in both RVVM modes; the remaining RNE-only flag delta belongs to the separate spurious-NX report (#310 / PR #316), not to the directed fix here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fmadd.s directed rounding returns wrong results and underreports UF

2 participants