Skip to content

[LangRef] Clarify denormal_fpenv slightly - #237290

Open
LewisCrawford wants to merge 1 commit into
llvm:mainfrom
LewisCrawford:denormal_fpenv_langref
Open

LewisCrawford wants to merge 1 commit into
llvm:mainfrom
LewisCrawford:denormal_fpenv_langref

Conversation

@LewisCrawford

@LewisCrawford LewisCrawford commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Clarify in the langref that denormal_fpenv settings can also cause flushing to occur for calculations whose results are the smallest normal number, not just denormal numbers.

Also, tidy up some duplicated text about the separate float: attribute left over from merging with the old "denormal-fp-math-f32" value #174293.

Also, fix small typo "If this is attribute is"

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-ir

Author: Lewis Crawford (LewisCrawford)

Changes

Clarify in the langref that denormal_fpenv settings can also cause flushing to occur for calculations whose results are the smallest normal number, not just denormal numbers.

Also, tidy up some duplicated text about the separate float: attribute left over from merging with the old "denormal-fp-math-f32" value #174293.


Full diff: https://github.com/llvm/llvm-project/pull/237290.diff

1 Files Affected:

  • (modified) llvm/docs/LangRef.md (+8-11)
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 1e8342fd119be9..2d7131eec4ba54 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -2876,9 +2876,11 @@ fn -> other_fn -> other_fn ; fn is norecurse
 
     If the output mode is `preservesign`, or `positivezero`,
     denormal outputs may be flushed to zero by standard floating-point
-    operations. It is not mandated that flushing to zero occurs, but if
-    a denormal output is flushed to zero, it must respect the sign
-    mode. Not all targets support all modes.
+    operations. On some targets, outputs that are rounded up to the smallest
+    positive normal number, or rounded down to the smallest negative normal
+    number may also be flushed to zero. It is not mandated that
+    flushing to zero occurs, but if an output is flushed to zero,
+    it must respect the sign mode. Not all targets support all modes.
 
     If the mode is `dynamic`, the behavior is derived from the
     dynamic state of the floating-point environment. Transformations
@@ -2890,10 +2892,6 @@ fn -> other_fn -> other_fn ; fn is norecurse
     the mode is consistent. User or platform code is expected to set
     the floating point mode appropriately before function entry.
 
-    This may optionally specify a second pair, prefixed with
-    `float:`. This provides an override for the behavior of 32-bit
-    float type (or vectors of 32-bit floats).
-
     If the input mode is `preservesign`, or `positivezero`,
     a floating-point operation must treat any input denormal value as
     zero. In some situations, if an instruction does not respect this
@@ -2902,11 +2900,10 @@ fn -> other_fn -> other_fn ; fn is norecurse
 
     This may optionally specify a second pair, prefixed with
     `float:`. This provides an override for the behavior of 32-bit
-    float type. (or vectors of 32-bit floats). If this is present,
-    this overrides the base handling of the default mode. Not all
+    float type (or vectors of 32-bit floats). Not all
     targets support separately setting the denormal mode per type, and
-    no attempt is made to diagnose unsupported uses. Currently this
-    attribute is respected by the AMDGPU and NVPTX backends.
+    no attempt is made to diagnose unsupported uses. Currently the separate
+    `float:` attribute is respected by the AMDGPU and NVPTX backends.
 
     **Examples:**
 

@arsenm arsenm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mixing in some other phrasing fixups. But the core change is wrong and is just a bug?

@arsenm arsenm added the floating-point Floating-point math label Oct 30, 2026
@jcranmer-intel

Copy link
Copy Markdown
Contributor

But the core change is wrong and is just a bug?

Denormal flushing on x86 and ARM at least is defined not in terms of "is the result a denormal" but "would an underflow exception occur" (not in so few words though). Because of the rules for how underflow works, that does mean that there are circumstances where the precise result is in the range (largest subnormal, smallest normal) triggers underflow and yet the rounded result ends up being the smallest normal. See https://godbolt.org/z/nd5Mbq76n for some examples.

@jcranmer-intel

Copy link
Copy Markdown
Contributor

(I don't know the behavior on all architectures, and quite frankly, this is the kind of detail that I wouldn't trust without having executable programs to prove the actual behavior of hardware, because the ISA manuals tend to really gloss over this sort of stuff.)

@LewisCrawford

LewisCrawford commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

It seems like NVPTX behaves the same as x86 in this regard too (at least on the GPUs I tried it with).

I think it's fine at the IR level to treat things in the simple case of "subnormal results get flushed", since that's a case all hardware implementations should agree on.

However, I also think it is useful to inform users that most actual target lowerings e.g. x86 with FTZ, Arm and AArch64 (with both AH=0 and AH=1 floating-point modes) with FZ, and NVTPX's .ftz instruction-modifiers can lead to these min-normal edge-cases where underflow detection causes a flush, even if the un-flushed result would be min-normal.

This occurs because x86 and AArch64 (in both AH modes) detect when to do FTZ according to IEEE 754 section 7.5's Underflow definition. This definition allows checking for Underflow either:

  • a) after rounding — when a non-zero result computed as though the exponent range were unbounded
    would lie strictly between ± b^emin, or

  • b) before rounding — when a non-zero result computed as though both the exponent range and the
    precision were unbounded would lie strictly between ± b^emin.

Since it does this detection with either an unbounded exponent or both unbounded exponent+precision, it detects Underflow (and hence FTZ) before the final normalization step that may round things up to min-normal.

So I don't think this edge-case requires any behaviour changes, but would be useful to highlight to users to avoid confusion about why it's not always strictly subnormal results that end up getting flushed.

Clarify in the langref that denormal_fpenv settings can also cause
flushing to occur for calculations whose results are the smallest
normal number, not just denormal numbers.
@LewisCrawford
LewisCrawford force-pushed the denormal_fpenv_langref branch from 8d8adfa to 29eaef2 Compare November 2, 2026 11:39
@LewisCrawford

Copy link
Copy Markdown
Contributor Author

I've split out the typo clean-up stuff into a separate patch here: #237921 to make the actual min-normal wording change clearer to review.

Another potential wording here could be something like:

Note that on some targets, the determination of whether to flush is tied to underflow detection rather than to whether the final result is denormal. This means flushing may occur even when the final result would be the smallest normal number.

But I wasn't sure how much we wanted to tie this wording specifically to Underflow, or whether to keep it more abstract and less target-specific in case there are other similar min-normal edge-cases on other hardware that don't use Underflow detection for this though.

This branch has not been deployed

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

Labels

floating-point Floating-point math llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants