[Mlir-commits] [mlir] [MLIR][LLVMIR] Add an op for the llvm.modf intrinsic (PR #223224)

Berke Ates llvmlistbot at llvm.org
Mon Sep 14 01:06:33 PDT 2026


https://github.com/Berke-Ates updated https://github.com/llvm/llvm-project/pull/223224

>From cb267d1e52431b1fd164f4e6fedd76a97e778c8e Mon Sep 17 00:00:00 2001
From: Berke-Ates <berke at ates.ch>
Date: Fri, 11 Sep 2026 18:37:28 +0200
Subject: [PATCH] [mlir][LLVMIR] Add an op for the llvm.modf intrinsic

`llvm.modf` had no LLVM dialect op, so it was imported as a generic
`llvm.call_intrinsic`, unlike the `llvm.sincos` and `llvm.frexp`
siblings that return the same kind of aggregate. Model it as an
`LLVM_TwoResultIntrOp` whose result is a homogeneous two-element
struct, mirroring `llvm.intr.sincos`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply at anthropic.com>
---
 .../mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td   | 10 +++++++++
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    | 19 +++++++++++++++++
 mlir/test/Dialect/LLVMIR/invalid.mlir         | 21 +++++++++++++++++++
 mlir/test/Target/LLVMIR/Import/intrinsic.ll   | 11 ++++++++++
 .../test/Target/LLVMIR/llvmir-intrinsics.mlir | 11 ++++++++++
 5 files changed, 72 insertions(+)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
index 464779e006d7f..e4c26660f643b 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
@@ -137,6 +137,16 @@ def LLVM_FractionExpOp : LLVM_TwoResultIntrOp<"frexp", [0, 1], [],
     let assemblyFormat = "`(` operands `)` attr-dict `:` "
         "functional-type(operands, results)";
 }
+def LLVM_ModfOp : LLVM_TwoResultIntrOp<"modf", [], [0],
+    [Pure], /*requiresFastmath=*/1> {
+    let arguments =
+        (ins LLVM_ScalarOrVectorOf<LLVM_AnyFloat>:$val,
+        DefaultValuedAttr<LLVM_FastmathFlagsAttr, "{}">:$fastmathFlags);
+    let assemblyFormat = "`(` operands `)` (`fastmath` `` $fastmathFlags^)? "
+        "attr-dict `:` "
+        "functional-type(operands, results)";
+    let hasVerifier = 1;
+}
 def LLVM_FAbsOp : LLVM_UnaryIntrOpF<"fabs">;
 def LLVM_FCeilOp : LLVM_UnaryIntrOpF<"ceil">;
 def LLVM_FFloorOp : LLVM_UnaryIntrOpF<"floor">;
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 6382dfcbc8ad2..ed225efc36362 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -4359,6 +4359,25 @@ LogicalResult LLVM::SincosOp::verify() {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// ModfOp (intrinsic)
+//===----------------------------------------------------------------------===//
+
+LogicalResult LLVM::ModfOp::verify() {
+  auto operandType = getOperand().getType();
+  auto resultType = getResult().getType();
+  auto resultStructType =
+      mlir::dyn_cast<mlir::LLVM::LLVMStructType>(resultType);
+  if (!resultStructType || resultStructType.getBody().size() != 2 ||
+      resultStructType.getBody()[0] != operandType ||
+      resultStructType.getBody()[1] != operandType) {
+    return emitOpError("expected result type to be an homogeneous struct with "
+                       "two elements matching the operand type, but got ")
+           << resultType;
+  }
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // AssumeOp (intrinsic)
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index bde7807dadb87..305bdaca75a6d 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -2133,6 +2133,27 @@ llvm.func @invalid_sincos_gt_2_element_struct_return_type(%f: f32) -> () {
 
 // -----
 
+llvm.func @invalid_modf_nonhomogeneous_return_type(%f: f32) -> () {
+  // expected-error at +1 {{op expected result type to be an homogeneous struct with two elements matching the operand type}}
+  llvm.intr.modf(%f) : (f32) -> !llvm.struct<(f32, f64)>
+}
+
+// -----
+
+llvm.func @invalid_modf_non_struct_return_type(%f: f32) -> () {
+  // expected-error at +1 {{op expected result type to be an homogeneous struct with two elements matching the operand type}}
+  llvm.intr.modf(%f) : (f32) -> f32
+}
+
+// -----
+
+llvm.func @invalid_modf_gt_2_element_struct_return_type(%f: f32) -> () {
+  // expected-error at +1 {{op expected result type to be an homogeneous struct with two elements matching the operand type}}
+  llvm.intr.modf(%f) : (f32) -> !llvm.struct<(f32, f32, f32)>
+}
+
+// -----
+
 module {
   // expected-error at +1 {{'llvm.blockaddress' op expects an existing block label target in the referenced function}}
   %0 = llvm.blockaddress <function = @missing_func, tag = <id = 1>> : !llvm.ptr
diff --git a/mlir/test/Target/LLVMIR/Import/intrinsic.ll b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
index d50a5e39bbe3a..5077d005badce 100644
--- a/mlir/test/Target/LLVMIR/Import/intrinsic.ll
+++ b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
@@ -69,6 +69,15 @@ define void @frexp_test(float %0, <8 x float> %1) {
   ret void
 }
 
+; CHECK-LABEL:  llvm.func @modf_test
+define void @modf_test(float %0, <8 x float> %1) {
+  ; CHECK:  llvm.intr.modf(%{{.*}}) : (f32) -> !llvm.struct<(f32, f32)>
+  %4 = call { float, float } @llvm.modf.f32(float %0)
+  ; CHECK:  llvm.intr.modf(%{{.*}}) : (vector<8xf32>) -> !llvm.struct<(vector<8xf32>, vector<8xf32>)>
+  %5 = call { <8 x float>, <8 x float> } @llvm.modf.v8f32(<8 x float> %1)
+  ret void
+}
+
 ; CHECK-LABEL:  llvm.func @log_test
 define void @log_test(float %0, <8 x float> %1) {
   ; CHECK:  llvm.intr.log(%{{.*}}) : (f32) -> f32
@@ -1261,6 +1270,8 @@ declare float @llvm.ldexp.f32.i32(float, i32)
 declare <8 x float> @llvm.ldexp.v8f32.i32(<8 x float>, i32)
 declare { float, i32 } @llvm.frexp.f32.i32(float)
 declare { <8 x float>, i32 } @llvm.frexp.v8f32.i32(<8 x float>)
+declare { float, float } @llvm.modf.f32(float)
+declare { <8 x float>, <8 x float> } @llvm.modf.v8f32(<8 x float>)
 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 7a362ebe2421e..22819cdef32ac 100644
--- a/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
@@ -67,6 +67,15 @@ llvm.func @frexp_test(%arg0: f32, %arg1: vector<8xf32>) {
   llvm.return
 }
 
+// CHECK-LABEL: @modf_test
+llvm.func @modf_test(%arg0: f32, %arg1: vector<8xf32>) {
+  // CHECK: call { float, float } @llvm.modf.f32(float %{{.*}})
+  llvm.intr.modf(%arg0) : (f32) -> !llvm.struct<(f32, f32)>
+  // CHECK: call { <8 x float>, <8 x float> } @llvm.modf.v8f32(<8 x float> %{{.*}})
+  llvm.intr.modf(%arg1) : (vector<8xf32>) -> !llvm.struct<(vector<8xf32>, vector<8xf32>)>
+  llvm.return
+}
+
 // CHECK-LABEL: @log_test
 llvm.func @log_test(%arg0: f32, %arg1: vector<8xf32>) {
   // CHECK: call float @llvm.log.f32
@@ -1441,6 +1450,8 @@ llvm.func @vector_scmp(%a: vector<4 x i32>, %b: vector<4 x i32>) -> vector<4 x i
 // CHECK-DAG: declare <8 x float> @llvm.ldexp.v8f32.i32(<8 x float>, i32)
 // CHECK-DAG: declare { float, i32 } @llvm.frexp.f32.i32(float)
 // CHECK-DAG: declare { <8 x float>, i32 } @llvm.frexp.v8f32.i32(<8 x float>)
+// CHECK-DAG: declare { float, float } @llvm.modf.f32(float)
+// CHECK-DAG: declare { <8 x float>, <8 x float> } @llvm.modf.v8f32(<8 x float>)
 // CHECK-DAG: declare float @llvm.log.f32(float)
 // CHECK-DAG: declare <8 x float> @llvm.log.v8f32(<8 x float>)
 // CHECK-DAG: declare float @llvm.log10.f32(float)



More information about the Mlir-commits mailing list