Skip to content
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

[mlir][llvmir] Add llvm.intr.ldexp operation #133070

Merged
merged 3 commits into from
Mar 27, 2025
Merged

Conversation

FantasqueX
Copy link
Contributor

https://llvm.org/docs/LangRef.html#llvm-ldexp-intrinsic
https://llvm.org/docs/LangRef.html#llvm-powi-intrinsic

ldexp and powi are similar. According to LLVM IR reference, power should be i32.

Generally, the only supported type for the exponent is the one matching with the C type int.

@FantasqueX FantasqueX marked this pull request as ready for review March 26, 2025 12:03
@llvmbot
Copy link
Member

llvmbot commented Mar 26, 2025

@llvm/pr-subscribers-mlir-llvm

@llvm/pr-subscribers-mlir

Author: Letu Ren (FantasqueX)

Changes

https://llvm.org/docs/LangRef.html#llvm-ldexp-intrinsic
https://llvm.org/docs/LangRef.html#llvm-powi-intrinsic

ldexp and powi are similar. According to LLVM IR reference, power should be i32.

> Generally, the only supported type for the exponent is the one matching with the C type int.


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

4 Files Affected:

  • (modified) mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td (+13-9)
  • (modified) mlir/test/Target/LLVMIR/Import/intrinsic.ll (+11)
  • (modified) mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir (+9)
  • (modified) mlir/test/Target/LLVMIR/llvmir-invalid.mlir (+1-1)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
index ace880bd28590..d702247244808 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
@@ -106,10 +106,22 @@ def LLVM_IsFPClass : LLVM_OneResultIntrOp<"is.fpclass", [], [0], [Pure],
   let arguments = (ins LLVM_ScalarOrVectorOf<LLVM_AnyFloat>:$in, I32Attr:$bit);
 }
 
+class LLVM_PowFI<string func> :
+    LLVM_OneResultIntrOp<func, [], [0,1],
+        [Pure], /*requiresFastmath=*/1> {
+    let arguments =
+        (ins LLVM_ScalarOrVectorOf<LLVM_AnyFloat>:$val,
+            AnyI32:$power,
+            DefaultValuedAttr<LLVM_FastmathFlagsAttr, "{}">:$fastmathFlags);
+    let assemblyFormat = "`(` operands `)` attr-dict `:` "
+            "functional-type(operands, results)";
+}
+
 def LLVM_CopySignOp : LLVM_BinarySameArgsIntrOpF<"copysign">;
 def LLVM_ExpOp : LLVM_UnaryIntrOpF<"exp">;
 def LLVM_Exp2Op : LLVM_UnaryIntrOpF<"exp2">;
 def LLVM_Exp10Op : LLVM_UnaryIntrOpF<"exp10">;
+def LLVM_LoadExpOp: LLVM_PowFI<"ldexp">;
 def LLVM_FAbsOp : LLVM_UnaryIntrOpF<"fabs">;
 def LLVM_FCeilOp : LLVM_UnaryIntrOpF<"ceil">;
 def LLVM_FFloorOp : LLVM_UnaryIntrOpF<"floor">;
@@ -130,15 +142,7 @@ def LLVM_RoundOp : LLVM_UnaryIntrOpF<"round">;
 def LLVM_FTruncOp : LLVM_UnaryIntrOpF<"trunc">;
 def LLVM_SqrtOp : LLVM_UnaryIntrOpF<"sqrt">;
 def LLVM_PowOp : LLVM_BinarySameArgsIntrOpF<"pow">;
-def LLVM_PowIOp : LLVM_OneResultIntrOp<"powi", [], [0,1],
-                                       [Pure], /*requiresFastmath=*/1> {
-  let arguments =
-      (ins LLVM_ScalarOrVectorOf<LLVM_AnyFloat>:$val,
-           AnySignlessInteger:$power,
-           DefaultValuedAttr<LLVM_FastmathFlagsAttr, "{}">:$fastmathFlags);
-  let assemblyFormat = "`(` operands `)` attr-dict `:` "
-      "functional-type(operands, results)";
-}
+def LLVM_PowIOp : LLVM_PowFI<"powi">;
 def LLVM_RintOp : LLVM_UnaryIntrOpF<"rint">;
 def LLVM_NearbyintOp : LLVM_UnaryIntrOpF<"nearbyint">;
 class LLVM_IntRoundIntrOpBase<string func> :
diff --git a/mlir/test/Target/LLVMIR/Import/intrinsic.ll b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
index 09f1eb5529228..3ba3ba26c54ae 100644
--- a/mlir/test/Target/LLVMIR/Import/intrinsic.ll
+++ b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
@@ -51,6 +51,15 @@ define void @exp10_test(float %0, <8 x float> %1) {
   ret void
 }
 
+; CHECK-LABEL:  llvm.func @ldexp_test
+define void @ldexp_test(float %0, <8 x float> %1, i32 %2) {
+  ; CHECK:  llvm.intr.ldexp(%{{.*}}, %{{.*}}) : (f32, i32) -> f32
+  %4 = call float @llvm.ldexp.f32.i32(float %0, i32 %2)
+  ; CHECK:  llvm.intr.ldexp(%{{.*}}, %{{.*}}) : (vector<8xf32>, i32) -> vector<8xf32>
+  %5 = call <8 x float> @llvm.ldexp.v8f32.i32(<8 x float> %1, i32 %2)
+  ret void
+}
+
 ; CHECK-LABEL:  llvm.func @log_test
 define void @log_test(float %0, <8 x float> %1) {
   ; CHECK:  llvm.intr.log(%{{.*}}) : (f32) -> f32
@@ -1060,6 +1069,8 @@ declare float @llvm.exp2.f32(float)
 declare <8 x float> @llvm.exp2.v8f32(<8 x float>)
 declare float @llvm.exp10.f32(float)
 declare <8 x float> @llvm.exp10.v8f32(<8 x float>)
+declare float @llvm.ldexp.f32.i32(float, i32)
+declare <8 x float> @llvm.ldexp.v8f32.i32(<8 x float>, i32)
 declare float @llvm.log.f32(float)
 declare <8 x float> @llvm.log.v8f32(<8 x float>)
 declare float @llvm.log10.f32(float)
diff --git a/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir b/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
index d54cd3cb11b51..cba47e3274386 100644
--- a/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
@@ -49,6 +49,15 @@ llvm.func @exp10_test(%arg0: f32, %arg1: vector<8xf32>) {
   llvm.return
 }
 
+// CHECK-LABEL: @ldexp_test
+llvm.func @ldexp_test(%arg0: f32, %arg1: vector<8xf32>, %arg2: i32) {
+  // CHECK: call float @llvm.ldexp.f32.i32(float %{{.*}}, i32 %{{.*}})
+  "llvm.intr.ldexp"(%arg0, %arg2) : (f32, i32) -> f32
+  // CHECK: call <8 x float> @llvm.ldexp.v8f32.i32(<8 x float> %{{.*}}, i32 %{{.*}})
+  "llvm.intr.ldexp"(%arg1, %arg2) : (vector<8xf32>, i32) -> vector<8xf32>
+  llvm.return
+}
+
 // CHECK-LABEL: @log_test
 llvm.func @log_test(%arg0: f32, %arg1: vector<8xf32>) {
   // CHECK: call float @llvm.log.f32
diff --git a/mlir/test/Target/LLVMIR/llvmir-invalid.mlir b/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
index 83566d6649932..3397dfbfdfe72 100644
--- a/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
@@ -147,7 +147,7 @@ llvm.func @ternary_float_intr_wrong_type(%arg0 : f32, %arg1 : f32, %arg2 : i32)
 // -----
 
 llvm.func @powi_intr_wrong_type(%arg0 : f32, %arg1 : f32) -> f32 {
-  // expected-error @below{{op operand #1 must be signless integer, but got 'f32'}}
+  // expected-error @below{{op operand #1 must be 32-bit integer, but got 'f32'}}
   %0 = "llvm.intr.powi"(%arg0, %arg1) : (f32, f32) -> f32
   llvm.return %0 : f32
 }

@FantasqueX
Copy link
Contributor Author

Cc @gysit

@gysit
Copy link
Contributor

gysit commented Mar 26, 2025

The language ref actually has an example where the power is an i16 (https://llvm.org/docs/LangRef.html#llvm-powi-intrinsic). Where did you see that only i32 is supported. Is there a verifier?

@FantasqueX
Copy link
Contributor Author

You are right. LLVM allows different width power since https://reviews.llvm.org/D99439. However, I still think power shouldn't be a signless integer.
https://godbolt.org/z/j4h5v3cco https://godbolt.org/z/WbW9naohx
llvm.powi can handle both signed and unsigned integer.
What's more, LLVM's document says "The ‘llvm.powi.*’ intrinsics return the first operand raised to the specified (positive or negative) power." Only if it accepts signed integer, it can handle both positive and negative.

Copy link
Contributor

@gysit gysit left a comment

Choose a reason for hiding this comment

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

Thanks for adding the intrinsic!

LGTM modulo the the AnySignlessInteger change.

I tried to explain in the review why we should stick to it.

Comment on lines 113 to 115
(ins LLVM_ScalarOrVectorOf<LLVM_AnyFloat>:$val,
AnyInteger:$power,
DefaultValuedAttr<LLVM_FastmathFlagsAttr, "{}">:$fastmathFlags);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(ins LLVM_ScalarOrVectorOf<LLVM_AnyFloat>:$val,
AnyInteger:$power,
DefaultValuedAttr<LLVM_FastmathFlagsAttr, "{}">:$fastmathFlags);
(ins LLVM_ScalarOrVectorOf<LLVM_AnyFloat>:$val,
AnySignlessInteger:$power,
DefaultValuedAttr<LLVM_FastmathFlagsAttr, "{}">:$fastmathFlags);

Let's use a signless integer nevertheless.

LLVM IR and hence also LLVM dialect works only with signless integer values (such as i32 or i16, it does not support ui16 or si32). The rational behind this is that the type itself should not encode if a value is signed or unsigned (this is different from c/c++). Instead the operations/instructions define if an integer is interpreted as signed or unsigned. That is why there is a signed and unsigned division operation and both of them take signless integer operands. Does that make sense?

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 got it. It's signless instead of unsigned. Thanks for your explanation!

@gysit gysit merged commit 9438694 into llvm:main Mar 27, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants