Skip to content

[General]: FST can set C1 when unmasked underflow prevents the store #45

Description

@shuklaayush

Problem

FST and FSTP store an x87 floating-point value to memory. If a numeric underflow or
overflow exception is unmasked, the instruction must suppress the store and clear C1, an
x87 condition flag used to report rounding. Unmasked means the corresponding exception
is enabled in the control word.

The source correctly suppresses the store but can leave C1 set. It first sets
C1 from its attempted rounded result, then checks whether the exception prevents the
write:

FP87_Status.C1 := Bit(rounded_up);
...
let write_result := not FP87::Is_Unmasked_Exception(...);

The later exception handling does not clear C1 for the underflow case below.

Reproducing case

Here m32fp is a 32-bit floating-point memory destination. In D9 /2, D9 is the opcode
and 2 is the register-selection field of the following ModR/M byte; the other fields
select the memory address.

Use 64-bit mode with x87 enabled: CR0.EM=0 and CR0.TS=0 allow these instructions to
execute with x87 enabled. ST(0) is the top x87 register and
ST(1) is the next. The control word selects rounding and which exceptions are masked.
“Masked” means the instruction uses the defined fallback behavior for that exception.

Execute FST m32fp (D9 /2) with ST(0) equal to the smallest positive
extended-precision subnormal, a nonzero value below the smallest normal value (raw
80-bit encoding 00000000000000000001), x87 control word 0x0b6f, and a writable
four-byte destination.

With ST(0) nonempty and the initial exception flags clear, source execution records
status & 0x023f = 0x0210 (underflow and C1) and unchanged destination bytes.
The hexadecimal mask keeps only C1 and the six exception flags. It
excludes TOP (the register-stack index), ES (exception summary) and B (busy); these are
not full status words.

Each 20-digit hexadecimal input specifies the exact 80-bit x87 register contents.

Source checked: Intel SDM executable specification revision
d307f89f742765865b87c5d4d23f552b3c72e871.

Separate hardware test

A separate hardware test on an AMD EPYC-Milan processor recorded
status & 0x023f = 0x0010 (underflow, C1 clear) and left the destination unchanged.
This agrees with the manual's C1 requirement.

Manual reference

References use Intel SDM 325462-089US, October 2025.

Intel SDM Volume 1, section 8.5.6, page 8-30 (PDF page 238) states that, when
unmasked numeric overflow or underflow affects a memory store, the inexact
condition is not reported and C1 is cleared. Volume 2A, FST/FSTP, page
3-379 (PDF page 1075)
describes the store, its exception flags, and C1.

Proposed fix

Clear C1 for an unmasked numeric overflow or underflow before processing the exceptions,
immediately after the endif following the assignment of FP87_Status.C1 (source line
215):

UE and OE are the underflow and overflow status flags. UM and OM are their control-word
mask bits; zero enables the corresponding exception.

if (exceptions.UE == 0b1 and FP87_Control.UM == 0b0) or
   (exceptions.OE == 0b1 and FP87_Control.OM == 0b0) then
    FP87_Status.C1 := 0b0;
endif;

Keep the current write-suppression behavior. The same correction applies to binary32 and
binary64 memory stores. The helper also has a separate case where rounding produces
infinity without setting the overflow flag. That flag must be corrected before this C1
check can handle that case. The reproducer here uses underflow and does not depend on
that separate correction.

AI disclosure

Assisted-by: Codex

Codex assisted with source analysis, test review, and drafting this report.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions