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.

@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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the PR. With f8ace92 we preserve the flags.

From SPIRV->LLVM we preserve the FastMathModeDefault in the !spirv.ExecutionMode metadata. Then when doing LLVM->SPIRV the metadata is lowered into the original FastMathModeDefault.

We set the default execution to 0 only when:

  • FastMathModeDefault in !spirv.ExecutionMode was 0 to begin with
  • We're adding the ContractionOff ExecutionMode
  • We're adding the SignedZeroInfNanPreserve

For these last 2, since these execution-modes are deprecated with FloatControls2, we have to translate them to something equivalent. We cannot unset some flags and leave others for the client API. For simplicity, I've chosen to set all the flags to 0.
This can be argued though.

In both cases, I've chosen to preserve the fast-math flags that are attached to the instructions. (Fadd with contract flags will still be translated to fadd contract even if ContracitonOff was set).

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added:

; By default, do not set the execution-mode when the extension is used.
; This test doesn't verify directly that the instructions have the SPIRV
; 'contract' flag (this is done in another test).
; As a sanity check, we still reverse-translate and check the IR.

@jmmartinez jmmartinez force-pushed the users/jmmartinez/spv_khr_float_controls2 branch 3 times, most recently from 2abce3e to 1108325 Compare January 19, 2026 15:14
// Get the scalar type to handle vector operands. And get the first operand
// type (instead of the result) due to fcmp instructions.
Type *FloatType = Inst->getOperand(0)->getType()->getScalarType();
auto Func2FMF = FuncToFastMathFlags.find({Inst->getFunction(), FloatType});
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'm tempted to remove this FuncToFastMathFlags stuff.

It's used to set the FPFastMathFlags that are attached to the execution mode to the individual instructions of a kernel.

But since we're preserving the FPFastMathFlags in the metadata; I'm thinking that this is not needed anymore.

@maarquitos14 should I remove this ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that this logic should be still placed somewhere as the middleend and backend are unlikely to know about this metadata out of the box and honestly it feels like for optimization passes it's easier to work with individual instruction flags. IMHO resolving ExecutionMode to FP flag right away in the SPIR-V consumer won't harm and actually make implementation lower-level drivers friendly.

@svenvh @vmaksimo WDYT?

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.

Functionally LGTM, but lets make tests passing :)

; SPIRV-ON-DAG: ExecutionModeId [[FOO]] 6028 [[HALF:[0-9]+]] [[ZERO:[0-9]+]]
; SPIRV-ON-DAG: ExecutionModeId [[FOO]] 6028 [[FLOAT:[0-9]+]] [[ZERO]]
; SPIRV-ON-DAG: ExecutionModeId [[FOO]] 6028 [[DOUBLE:[0-9]+]] [[ZERO]]
; SPIRV-ON-DAG: TypeFloat [[HALF]] 16
Copy link
Contributor

Choose a reason for hiding this comment

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

Test is currently failing, not 100% why. May be we should move this line before checks of ExecutionModeId to ensure correct REGEX variables definition:

; SPIRV-ON-DAG: TypeFloat [[#HALF:]] 16
...
; SPIRV-ON-DAG: ExecutionModeId [[#FOO]] 6028 [[#HALF]] [[#ZERO]]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's weird. I haven't managed to reproduce the issue. But I think I know what the problem is: I'm scanning the TypeMap and there is no guarantee of the order of its elements. I'll fix this to always generate the same code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the last commit by sorting the floating-point types by bit-width and encoding.

@jmmartinez
Copy link
Contributor Author

Functionally LGTM, but lets make tests passing :)

My bad ! In my defense... It passed in my machine. There was one matrix test failing though (but also over main so I didn't look much into it).

With this extension, the execution modes `ContractionOff and
`SignedZeroInfNanPreserve` are deprecated and we should use
`FPFastMathDefault` instead.

Additionally, the `FPFastMathMode` mode `Fast` bit is also deprecated.
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.
…tractionOff and SignedZeroInfNanPreserve to FPFastMathDefault 0
@jmmartinez jmmartinez force-pushed the users/jmmartinez/spv_khr_float_controls2 branch from 1108325 to ebdcd76 Compare January 23, 2026 15:19
@jmmartinez jmmartinez force-pushed the users/jmmartinez/spv_khr_float_controls2 branch from 9633e0d to 2c6901c Compare January 23, 2026 15:39
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