[Mlir-commits] [mlir] [MLIR][LLVMIR] Add an op for the llvm.modf intrinsic (PR #223224)
Berke Ates
llvmlistbot at llvm.org
Sun Sep 13 02:38:17 PDT 2026
https://github.com/Berke-Ates created https://github.com/llvm/llvm-project/pull/223224
`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`.
>From fb62d2fb5ee7c294707bfe1531ac9e2002aae008 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 | 9 ++++++++
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, 71 insertions(+)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
index 464779e006d7f..970fea90dee40 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
@@ -137,6 +137,15 @@ 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 `)` 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