[llvm] [SPIR-V] Implement selection for llvm.canonicalize(x) as fmul(x, 1.0) (PR #178439)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jan 28 06:53:44 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-spir-v
Author: Mészáros Gergely (Maetveis)
<details>
<summary>Changes</summary>
---
Full diff: https://github.com/llvm/llvm-project/pull/178439.diff
3 Files Affected:
- (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+32)
- (modified) llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp (+3)
- (modified) llvm/test/CodeGen/SPIRV/llvm-intrinsics/fp-intrinsics.ll (+45)
``````````diff
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 915db6824d7c6..8bd3a7ce73ff2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -339,6 +339,8 @@ class SPIRVInstructionSelector : public InstructionSelector {
MachineInstr &I) const;
bool selectDerivativeInst(Register ResVReg, const SPIRVType *ResType,
MachineInstr &I, const unsigned DPdOpCode) const;
+ bool selectFCanonicalize(Register ResVReg, const SPIRVType *ResType,
+ MachineInstr &I) const;
// Utilities
std::pair<Register, bool>
buildI32Constant(uint32_t Val, MachineInstr &I,
@@ -987,6 +989,9 @@ bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
case TargetOpcode::G_FMAXIMUM:
return selectExtInst(ResVReg, ResType, I, CL::fmax, GL::NMax);
+ case TargetOpcode::G_FCANONICALIZE:
+ return selectFCanonicalize(ResVReg, ResType, I);
+
case TargetOpcode::G_FCOPYSIGN:
return selectExtInst(ResVReg, ResType, I, CL::copysign);
@@ -3007,6 +3012,33 @@ SPIRVInstructionSelector::buildI32Constant(uint32_t Val, MachineInstr &I,
return {NewReg, Result};
}
+bool SPIRVInstructionSelector::selectFCanonicalize(Register ResVReg,
+ const SPIRVType *ResType,
+ MachineInstr &I) const {
+ // There is no native fcanonicalize instruction in SPIRV. We can lower it to:
+ // - fmin(x, x) or
+ // - fmul(x, 1.0)
+ //
+ // We use fmul(x, 1.0) here, because:
+ // - llvm-spirv translates fmin to a function call, whereas
+ // fmul is translated to the LLVM fmul instruction.
+ // - fmin requires either OpenCL or GLSL extended instruction set, whereas
+ // fmul does not.
+
+ // fcanonicalize(x) -> fmul(x, 1.0)
+ SPIRVType *SpirvScalarType = GR.getScalarOrVectorComponentType(ResType);
+ auto Opcode = ResType->getOpcode() == SPIRV::OpTypeVector
+ ? SPIRV::OpVectorTimesScalar
+ : SPIRV::OpFMulS;
+
+ return BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(Opcode))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(I.getOperand(1).getReg())
+ .addUse(buildOnesValF(SpirvScalarType, I))
+ .constrainAllUses(TII, TRI, RBI);
+}
+
bool SPIRVInstructionSelector::selectFCmp(Register ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index 03d846cb90b4c..fcfb2f33b88c3 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -467,6 +467,9 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
G_INTRINSIC_ROUNDEVEN})
.legalFor(allFloatScalarsAndVectors);
+ getActionDefinitionsBuilder(G_FCANONICALIZE)
+ .legalFor(allFloatScalarsAndVectors);
+
getActionDefinitionsBuilder(G_FCOPYSIGN)
.legalForCartesianProduct(allFloatScalarsAndVectors,
allFloatScalarsAndVectors);
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/fp-intrinsics.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/fp-intrinsics.ll
index 70030ca1a0316..41a69f3dfd6b3 100644
--- a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/fp-intrinsics.ll
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/fp-intrinsics.ll
@@ -8,6 +8,11 @@
; CHECK: %[[#var2:]] = OpTypeFloat 64
; CHECK: %[[#var3:]] = OpTypeVector %[[#var1]] 4
+; 15360 = 0x3c00 = 1.0 (bf16)
+; CHECK: %[[#one_f16:]] = OpConstant %[[#var0]] 15360
+; CHECK: %[[#one_f32:]] = OpConstant %[[#var1]] 1
+; CHECK: %[[#one_f64:]] = OpConstant %[[#var2]] 1
+
; CHECK: OpFunction
; CHECK: %[[#]] = OpExtInst %[[#var0]] %[[#extinst_id]] fabs
; CHECK: OpFunctionEnd
@@ -403,3 +408,43 @@ return:
}
declare { double, double } @llvm.modf.f64(double)
+
+; CHECK: OpFunction
+; CHECK: %[[#x:]] = OpFunctionParameter %[[#]]
+; CHECK: %[[#]] = OpFMul %[[#var0]] %[[#x]] %[[#one_f16]]
+; CHECK: OpFunctionEnd
+define dso_local half @TestCanonicalizeF16(half %x) {
+entry:
+ %t = tail call half @llvm.canonicalize.f16(half %x)
+ ret half %t
+}
+
+; CHECK: OpFunction
+; CHECK: %[[#x:]] = OpFunctionParameter %[[#]]
+; CHECK: %[[#]] = OpFMul %[[#var1]] %[[#x]] %[[#one_f32]]
+; CHECK: OpFunctionEnd
+define dso_local float @TestCanonicalizeF32(float %x) {
+entry:
+ %t = tail call float @llvm.canonicalize.f32(float %x)
+ ret float %t
+}
+
+; CHECK: OpFunction
+; CHECK: %[[#x:]] = OpFunctionParameter %[[#]]
+; CHECK: %[[#]] = OpFMul %[[#var2]] %[[#x]] %[[#one_f64]]
+; CHECK: OpFunctionEnd
+define dso_local double @TestCanonicalizeF64(double %x) {
+entry:
+ %t = tail call double @llvm.canonicalize.f64(double %x)
+ ret double %t
+}
+
+; CHECK: OpFunction
+; CHECK: %[[#x:]] = OpFunctionParameter %[[#]]
+; CHECK: %[[#]] = OpVectorTimesScalar %[[#var3]] %[[#x]] %[[#one_f32]]
+; CHECK: OpFunctionEnd
+define dso_local <4 x float> @TestCanonicalizeVec(<4 x float> %x) {
+entry:
+ %t = tail call <4 x float> @llvm.canonicalize.v4f32(<4 x float> %x)
+ ret <4 x float> %t
+}
\ No newline at end of file
``````````
</details>
https://github.com/llvm/llvm-project/pull/178439
More information about the llvm-commits
mailing list