Skip to content

[LangRef] Clarify the semantics of fast-math flags #89442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
39 changes: 39 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3656,6 +3656,45 @@ floating-point transformations.
``fast``
This flag implies all of the others.

When performing a transformation that involves more that one instruction, the
flags required to enable the transformation must be set on all instructions
involved in the transformation, and any new instructions created by the
transformation should have only those flags set which were set on all the
original instructions that are being transformed.
Comment on lines +3662 to +3663
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't universally true. In some cases, you can or in the flags. In others you can infer new nnan/ninf. It should be true for reassoc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I suppose you're right that the semantics we've defined for nnan and ninf make that true in many cases. This leads to an increasing sense I have that those two flags are fundamentally different from the other fast-math flags. Those two flags provide clear, consistent, and rather strict semantics, whereas the other flags all have a sort of "you're allowed to do something here that you normally couldn't" character.

This also significantly complicates the reasoning involved. For instance

%mul1 = fmul reassoc float %x, %y
%mul2 = fmul fast float %mul1, %z

becomes

%mul1.1 = fmul reassoc nnan float %x, %z
%mul2.1 = fmul reassoc nnan float %mul1, %y

which is already tricky because the possibility of %x=INF, %y=0 prevents me from setting ninf on %mul2.1 and %x=0. %y=INF prevents me from setting ninf on %mul1.1

but also

%mul1 = fmul fast float %x, %y
%mul2 = fmul reassoc float %mul1, %z

becomes

%mul1.1 = fmul reassoc float %x, %z
%mul2.1 = fmul reassoc float %mul1, %y

because we don't have enough information to set ninf or nnan on either transformed instruction.

In practice, I think this would be difficult to implement any way other than to apply the rule I stated and rely on a later pass to infer nnan and ninf where it can. Of course, for the purposes of describing the semantics, it may be best to say something like

any new instructions created by the transformation should have only those flags set which were set on all the original instructions that are being transformed unless additional flags can be strictly deduced from the original IR.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you want to add more of a guideline, maybe could say something about how you can infer that. e.g. since nnan/ninf imply something about the inputs in one instruction, it implies something about the value in the other use

Copy link
Contributor

Choose a reason for hiding this comment

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

I've been putting together an RFC on FMF semantics, but one of the things I note is that there's a definite dichotomy between nnan/ninf/nsz (which have clear value-based semantics) and reassoc/afn/arcp/contract (which probably have to be rewrite-based semantics). For the rewrite-based semantics flags, it's necessary that the flags be present on all operations in the expression.

I don't have great wording to suggest at the moment (not unless you want a page of text), but maybe it would be better to delineate the flags for which the all-instructions-must-have-the-flags-to-be-rewritten property is true?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't have great wording to suggest at the moment (not unless you want a page of text), but maybe it would be better to delineate the flags for which the all-instructions-must-have-the-flags-to-be-rewritten property is true?

I think for the purposes of this change, explicitly listing the flags for which this guidance applies would indeed be best.


For example

::

%mul1 = fmul float %x, %y
%mul2 = fmul fast float %mul1, %z

cannot be transformed to

::

%mul1 = fmul float %x, %z
%mul2 = fmul fast float %mul1, %y

because the %mul1 instruction does not have the 'reassoc' flag set.

Similarly, if applying reassociation to

::

%mul1 = fmul reassoc float %x, %y
%mul2 = fmul fast float %mul1, %z

the result must be

::

%mul1 = fmul reassoc float %x, %z
%mul2 = fmul reassoc float %mul1, %y

because only the reassoc flag is set on both original instructions.


.. _uselistorder:

Use-list Order Directives
Expand Down