Skip to content

Conversation

@jmmartinez
Copy link
Contributor

@jmmartinez jmmartinez commented Dec 17, 2025

First attempt at implementing SPV_KHR_float_controls2.

Some highlights:

  • When doing SPIRV->LLVM-IR, we first read the ExecutionModeFPFastMathDefault for every kernel, and if instructions in that kernel do not specify a particular FPFastMathMode, we use the kernel one (question below).
  • According to SPV_KHR_float_controls2#issues; we do not have an equivalent of LLVM's afn flag. If we map fadd fast float %a, %b to SPIRV and back, it becomes fadd reassoc nnan ninf nsz arcp contract float %a, %b losing the afn flag.

Some questions:

  • Since not all functions are kernels; what happens when a kernel calls a function with the FPFastMathMode? Should we propagate the attribute down to the callees?
    • From the SPEC: The execution model and any execution modes associated with an entry point apply to the entire static function call graph rooted at that entry point. This rule implies that a function appearing in both call graphs of two distinct entry points may behave differently in each case.
  • This patch doesn't set an ExecutionModeFPFastMathDefault when writing SPIRV. Instead it writes the appropriate FPFastMathMode for every instruction. In that case, should we emit a "zero" FPFastMathMode for instructions without any fast-math-flags ?

@MrSidims MrSidims requested review from MrSidims, maarquitos14, svenvh and vmaksimo and removed request for vmaksimo December 18, 2025 11:20
@MrSidims
Copy link
Contributor

Should we propagate the attribute down to the callees?

We shouldn't, as you have quoted: "This rule implies that a function appearing in both call graphs of two distinct entry points may behave differently in each case.". Runtime should be able to pass fast math controls from a caller to a callee.

In that case, should we emit a "zero" FPFastMathMode for instructions without any fast-math-flags

I'm a bit worried about bloating size of SPIR-V modules in this case. In general I'd suggest to align behaviour of the translator and SPIR-V backend in areas where it's possible. So I'd expect llvm-spirv's implementation resulting in the same SPIR-V as llvm/llvm-project#146941 aka there should be FPFastMathDefault set.

@maarquitos14
Copy link
Contributor

I'll go on vacation in a few hours, and I'm afraid I will not have time to review this before I leave. Feel free to merge this without my approval, and I'll make sure I review when I'm back, even if it's a post-merge review.

I did want to bring up a couple of related issues, though. Hopefully they can be resolved by this PR.

@jmmartinez
Copy link
Contributor Author

Should we propagate the attribute down to the callees?

We shouldn't, as you have quoted: "This rule implies that a function appearing in both call graphs of two distinct entry points may behave differently in each case.". Runtime should be able to pass fast math controls from a caller to a callee.

Then the current implementation should be good, since it doesn't propagate anything.

In that case, should we emit a "zero" FPFastMathMode for instructions without any fast-math-flags

I'm a bit worried about bloating size of SPIR-V modules in this case. In general I'd suggest to align behavior of the translator and SPIR-V backend in areas where it's possible. So I'd expect llvm-spirv's implementation resulting in the same SPIR-V as llvm/llvm-project#146941 aka there should be FPFastMathDefault set.

I see. Then I should fix this implementation to always emit a FPFastMathDefault with all flags set to 0 for every kernel. Right?

@jmmartinez
Copy link
Contributor Author

In that case, should we emit a "zero" FPFastMathMode for instructions without any fast-math-flags

I'm a bit worried about bloating size of SPIR-V modules in this case. In general I'd suggest to align behavior of the translator and SPIR-V backend in areas where it's possible. So I'd expect llvm-spirv's implementation resulting in the same SPIR-V as llvm/llvm-project#146941 aka there should be FPFastMathDefault set.

I see. Then I should fix this implementation to always emit a FPFastMathDefault with all flags set to 0 for every kernel. Right?

I've addressed this in b691977 . This commit emits an FPFastMathDefault with all flags set to 0 for every kernel.

@jmmartinez
Copy link
Contributor Author

This one is tricky. reassoc maps to AllowTransform; but AllowTransform requires AllowReassoc and AllowContract to be set. So AllowTransform maps back to reassoc contract.

I've added a commit related to this, but I'll file a separate patch since this issue is not related to the float_controls2 extension.

@jmmartinez jmmartinez force-pushed the users/jmmartinez/spv_khr_float_controls2 branch from cd6a10d to 57840f3 Compare December 22, 2025 15:42
@MrSidims
Copy link
Contributor

I've added a commit related to this, but I'll file a separate patch since this issue is not related to the float_controls2 extension.

Fine with me.

Most (if not all) of the folks working on the translator are currently on holidays (including myself), so guess review will be done a bit later :)

(unless there is a super urgency - in this case I can take a look before New Year)

@jmmartinez
Copy link
Contributor Author

I've added a commit related to this, but I'll file a separate patch since this issue is not related to the float_controls2 extension.

Fine with me.

Most (if not all) of the folks working on the translator are currently on holidays (including myself), so guess review will be done a bit later :)

(unless there is a super urgency - in this case I can take a look before New Year)

No problem! It's not urgent.

@jmmartinez jmmartinez force-pushed the users/jmmartinez/spv_khr_float_controls2 branch from 14519f7 to 8fa049e Compare January 5, 2026 12:40
Copy link
Contributor

@MrSidims MrSidims left a comment

Choose a reason for hiding this comment

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

LGTM

I'd like to hear from @maarquitos14 before merging.

@jmmartinez
Copy link
Contributor Author

Just in case, I'd like to bring the attention to one of my previous messages about the issue #3125 :

Currently, this PR maps LLVM's reassoc -> AllowReassoc (this is the behavior that was implemented before float_controls2).

In the issue it is suggested that we'd better translate reassoc -> AllowTransform. The problem with this is that AllowTransform implies both AllowContract and AllowReassoc.

Then, if we map LLVM's to SPIRV and back to LLVM we end up with different semantics:

reassoc -> AllowTransform AllowContract AllowReassoc -> contract reassoc

To avoid this, we could translate

reassoc -> AllowReassoc -> no-flags
contract reassoc -> AllowTransform AllowContract AllowReassoc -> contract reassoc

@MrSidims
Copy link
Contributor

MrSidims commented Jan 7, 2026

Currently, this PR maps LLVM's reassoc -> AllowReassoc (this is the behavior that was implemented before float_controls2).

Thanks for bringing the attention back. I believe we should do one thing at a time and fix behaviour in unrelated to this PR patch.

Before, the extension would be used only when an operation having
fast-math flags that can only be represented using float_controls2 was enabled.

Afer this patch, the extension is added if floating-point types are used in the module.
@jmmartinez jmmartinez force-pushed the users/jmmartinez/spv_khr_float_controls2 branch from 8fa049e to dd4806c Compare January 8, 2026 16:11
@maarquitos14
Copy link
Contributor

LGTM

I'd like to hear from @maarquitos14 before merging.

I plan to look at this today/tomorrow.

@maarquitos14
Copy link
Contributor

I've added a commit related to this, but I'll file a separate patch since this issue is not related to the float_controls2 extension

That works for me, thanks. Just highlighted it here to make sure it worked well with the current implementation.

@maarquitos14
Copy link
Contributor

Currently, this PR maps LLVM's reassoc -> AllowReassoc (this is the behavior that was implemented before float_controls2).

Thanks for bringing the attention back. I believe we should do one thing at a time and fix behaviour in unrelated to this PR patch.

@jmmartinez ping me if you do create a separate patch for this.

Copy link
Contributor

@maarquitos14 maarquitos14 left a comment

Choose a reason for hiding this comment

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

First pass. I'll do a second pass to check tests.


case spv::ExecutionModeSignedZeroInfNanPreserve:
// With SPV_KHR_float_controls2 this is deprecated
if (BM->hasCapability(CapabilityFloatControls2))
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we need to add FPFastMathDefault execution mode too? It is required to set the equivalent of SignedZeroInfNanPreserve, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the moment, since the default fast-math flags are all disabled, both are preserved (ContractionOff/SignedZeroInfNanPreserve disable the contract/nsz ninf nnan flags).

I should add a comment explaining how these are preserved.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I see what you mean. However, I vaguely recall that having no flags isn't the same as having all flags set to zero from my implementation of this extension in the SPIRV BE. Let me try and find that again.

Also, a comment would help anyway :)

Copy link
Contributor

Choose a reason for hiding this comment

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

If an operation is decorated with FPFastMathMode then the flags from that decoration apply. Otherwise, if the current entry point sets any FPFastMathDefault execution mode then all flags specified for any operand type or for the result type of the operation apply. If the operation is not decorated with FPFastMathMode and the entry point sets no FPFastMathDefault execution modes then the flags to be applied are determined by the client API and not by SPIR-V.

My understanding of this quote from the spec is that no decoration is not the same than decoration with all flags set to zero: all flags set to zero clearly specify the fast math mode, while no decoration means the client API can decide. Do you agree?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree.

Currently, if float_controls2 is available, we enable it always with all the flags set to zero. Then an LLVM floating-point operation with no flags has the same semantics in SPIRV.
However, I think there is a problem in my implementation: functions getting called by kernels.

Currently the FastMathModeDefault are not preserved when doing spirv->llvm-ir->spirv. When doing spirv->llvm-ir we set the FastMathModeDefault into the kernel operations, but we cannot do that on the called functions. Then, when doing llvm-ir->spirv we end up with the right flags on the kernel, but stricter flags (all set to 0 propagated through the new FastMathModeDefault) on the called function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a second contradicting thought. In fact, depending on how you see it, setting no flags in SPIRV can also be seen as enabling all rewrite flags in LLVM: contract / reassociate / ... are all permitted and is up to the client to decide if it optimizes it or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

Exactly, setting everything to zero might prevent possible client optimizations. I think we shouldn't do that.

entry:
; IR-LABEL: define {{.*}} @foo
; IR-NEXT: entry:
; IR-NEXT: %rh = fadd contract half %ah, %bh
Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that you don't check decorations in SPIRV because you assume that they have to be present in SPIRV if they are present in the reverse translation. Am I right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sort of. I wanted to check only that the ExecutionModeId was set correctly (the flags set on the instructions are verified in other tests). And reverse translated it to ensure the contract flag doesn't get overridden by it.

I can add the checks for the individual instructions if it make more sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

As long as the intent is clearly specified in the test, I'm happy with that. Can you add a comment explaining this?


// We encode an fp-operaiton with no FPFastMathMode flags set as an
// fp-operation with all the flags set to 0. Instead of setting the flag for
// every individual operation, we set it once, for the entry-point.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not really sure if this is correct. Like I said in a different thread, I don't think it's the same having all flags set to 0 than not having the decoration.

; SPIRV-DAG: TypeFloat [[#double:]] 64
;
; 6028 is FPFastMathDefault
; SPIRV-DAG: ExecutionModeId [[#foo]] 6028 [[#half]] [[#zero]]
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me where is the OpExecutionModeId coming from. I don't see any !spirv.ExecutionMode metadata.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the moment this patch is not setting !spirv.ExecutionMode and it always sets the FastMathDefaultFlags to 0 (which is probably too strict).

Copy link
Contributor

Choose a reason for hiding this comment

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

So where does the OpExecutionMode come from, then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the moment, it's always setting it to 0 by default in SPIRV. So even if the decoration doesn't appear in the LLVM-IR, if we allow SPV_KHR_float_controls2, it is added to every kernel.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I understand. I have doubts about such an approach for the reasons mentioned in another thread: we would be setting constraints to kernels that actually don't come from the original source. Without those constraints, client APIs have more freedom to optimize. @MrSidims what's your opinion on this?

Copy link
Contributor

@MrSidims MrSidims Jan 15, 2026

Choose a reason for hiding this comment

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

My understanding, is that None FP Fast Math Mode set in FPFastMathDefault is not overriding default controls of client API. @bashbaug @alan-baker is my understanding correct?

Choose a reason for hiding this comment

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

Any default specified overrides the client. None is saying the default is no fast math (which may match what the client requires).

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for clarification, in such case current behaviour is undesired,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good. Then, I'll modify the code such that when translating LLVM-IR to SPIRV we should avoid setting the FPFastMathDefault (unless there is metadata specifying it).

In that case a fp-operation without flags in LLVM, fadd %a %b will map to a fp-operation without flags in SPIRV (instead of having the flags set to 0 through FPFastMathDefault).


One case that I still have to reflect more on how to translate ContractionOff/SignedZeroInfNanPreserve.

We cannot set an FPFastMathDefault or flags on individual instructions such that only some flags are set and the rest are left to the client API.

From what I understand, float_controls2 deprecates those execution modes, but doesn't forbid them. We could still set them and print a warning as a first step.

What do you think?

Copy link
Contributor

@maarquitos14 maarquitos14 Jan 16, 2026

Choose a reason for hiding this comment

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

Then, I'll modify the code such that when translating LLVM-IR to SPIRV we should avoid setting the FPFastMathDefault (unless there is metadata specifying it).

I think that is what we want, yes. You can reuse, or at least cross-check with, the tests in the SPIRV BE: https://github.com/llvm/llvm-project/tree/main/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_float_controls2

From what I understand, float_controls2 deprecates those execution modes, but doesn't forbid them. We could still set them and print a warning as a first step.

What we did in the SPIRV BE was to replace them with the appropriate flags through FPFastMathDefault. In that case, I think it's okay to do it, because it comes explicitly from the source. They set ContractionOff/SignedZeroInfNanPreserve, and we need to translate that.

; RUN: llvm-spirv --spirv-ext=+SPV_KHR_float_controls2 %s -o %t.spv
; RUN: spirv-val %t.spv

; Do not add extension if no floating-point type is used in this module.
Copy link
Contributor

Choose a reason for hiding this comment

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

What if there is an fp type but it doesn't have any flags, or there is none of the FP operations? Does it enable the extension in that case? Should it enable the extension in that case?

; SPIRV-DAG: TypeFloat [[#float:]] 32
;
; 6028 is FPFastMathDefault
; SPIRV-DAG: ExecutionModeId [[#foo]] 6028 [[#float]] [[#zero]]
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit lost here: the test is named extension_not_needed_but_used, however, you expect FPFastMathDefault execution mode, which does need the extension, I think?

At the same time, similarly to a previous test, I don't see any !spirv.ExecutionMode metadata, so I'm not sure why we expect this execution mode.

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.

4 participants