[Mlir-commits] [mlir] [mlir][LLVM] Add canonicalization pattern for IntToPtrOp(PtrToIntOp(x)) (PR #234302)

Alexander Shaposhnikov llvmlistbot at llvm.org
Wed Oct 21 11:54:48 PDT 2026


https://github.com/alexander-shaposhnikov updated https://github.com/llvm/llvm-project/pull/234302

>From 9193474bdd9975628a55c33c9b7ae3f2f778ae4c Mon Sep 17 00:00:00 2001
From: Alexander Shaposhnikov <ashaposhnikov at google.com>
Date: Wed, 21 Oct 2026 04:42:04 -0700
Subject: [PATCH] [mlir][LLVM] Canonicalize IntToPtrOp(PtrToIntOp(x)) to x

Add a canonicalization pattern to llvm.inttoptr that eliminates
round-trip inttoptr(ptrtoint(x)) casts when the intermediate integer is
at least as wide as the pointer (per element for vectors), so that the
round trip is lossless. This mirrors the ptrtoint/inttoptr case of
llvm::CastInst::isEliminableCastPair.

The pattern bails out when the cast carries a dereferenceable attribute,
when the source and result pointer types differ (e.g. different address
spaces), and when no data layout scope (module or DataLayoutOpInterface)
is reachable from the operation, since the pointer width cannot be
determined reliably in that case.
---
 mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td |   4 +-
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp  |  61 ++++++++++
 mlir/test/Dialect/LLVMIR/canonicalize.mlir  | 127 ++++++++++++++++++++
 3 files changed, 191 insertions(+), 1 deletion(-)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 9ff3b2d75e3f67..3d641e5a5721d4 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -672,7 +672,9 @@ def LLVM_AddrSpaceCastOp : LLVM_CastOp<"addrspacecast", "AddrSpaceCast",
 }
 def LLVM_IntToPtrOp : LLVM_DereferenceableCastOp<"inttoptr", "IntToPtr",
                                   LLVM_ScalarOrVectorOf<AnySignlessInteger>,
-                                  LLVM_ScalarOrVectorOf<LLVM_AnyPointer>>;
+                                  LLVM_ScalarOrVectorOf<LLVM_AnyPointer>> {
+  let hasCanonicalizer = 1;
+}
 def LLVM_PtrToIntOp : LLVM_CastOp<"ptrtoint", "PtrToInt",
                                   LLVM_ScalarOrVectorOf<LLVM_AnyPointer>,
                                   LLVM_ScalarOrVectorOf<AnySignlessInteger>>;
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 1c8d2e229b7d5c..f5ff0848fbd295 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -4106,6 +4106,67 @@ OpFoldResult LLVM::AddrSpaceCastOp::fold(FoldAdaptor adaptor) {
 
 Value LLVM::AddrSpaceCastOp::getViewSource() { return getArg(); }
 
+//===----------------------------------------------------------------------===//
+// Canonicalization for LLVM::IntToPtrOp
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// Replaces `inttoptr(ptrtoint(x))` with `x` when the intermediate integer is
+/// wide enough to hold every bit of the pointer, so that the round trip is
+/// lossless. This mirrors the ptrtoint/inttoptr case of
+/// `llvm::CastInst::isEliminableCastPair`.
+struct FoldIntToPtrOfPtrToInt : public OpRewritePattern<IntToPtrOp> {
+  using OpRewritePattern::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(IntToPtrOp intToPtr,
+                                PatternRewriter &rewriter) const override {
+    // Folding would drop the dereferenceability information attached to the
+    // cast; be conservative and keep the cast in that case.
+    if (intToPtr.getDereferenceableAttr())
+      return failure();
+
+    auto ptrToInt = intToPtr.getArg().getDefiningOp<PtrToIntOp>();
+    if (!ptrToInt)
+      return failure();
+
+    // Requiring identical types guarantees that the address spaces and the
+    // vector shapes (if any) match.
+    Value ptr = ptrToInt.getArg();
+    if (ptr.getType() != intToPtr.getType())
+      return failure();
+
+    // The pointer bit-width can only be determined from a data layout. Do not
+    // rewrite if no data layout scope is reachable (e.g., the operation is
+    // not nested in a module yet), as the default layout would be used
+    // otherwise, which may disagree with the layout the operation eventually
+    // ends up in.
+    Operation *scope = intToPtr->getParentOp();
+    while (scope && !isa<ModuleOp, DataLayoutOpInterface>(scope))
+      scope = scope->getParentOp();
+    if (!scope)
+      return failure();
+
+    // Compare per-element widths so that fixed and scalable vectors are
+    // handled uniformly.
+    DataLayout dataLayout = DataLayout::closest(intToPtr);
+    llvm::TypeSize ptrSize = dataLayout.getTypeSizeInBits(
+        extractVectorElementType(intToPtr.getType()));
+    auto intType = cast<IntegerType>(
+        extractVectorElementType(intToPtr.getArg().getType()));
+    if (ptrSize.isScalable() || intType.getWidth() < ptrSize.getFixedValue())
+      return failure();
+
+    rewriter.replaceOp(intToPtr, ptr);
+    return success();
+  }
+};
+} // namespace
+
+void IntToPtrOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
+                                             MLIRContext *context) {
+  patterns.add<FoldIntToPtrOfPtrToInt>(context);
+}
+
 //===----------------------------------------------------------------------===//
 // Folder for LLVM::GEPOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/LLVMIR/canonicalize.mlir b/mlir/test/Dialect/LLVMIR/canonicalize.mlir
index 2dd0bbfe4922bd..874883c42fba1d 100644
--- a/mlir/test/Dialect/LLVMIR/canonicalize.mlir
+++ b/mlir/test/Dialect/LLVMIR/canonicalize.mlir
@@ -549,3 +549,130 @@ llvm.func @metadata_as_value_dedup() -> i32 {
   %4 = llvm.add %2, %3 : i32
   llvm.return %4 : i32
 }
+
+// -----
+
+// CHECK-LABEL: llvm.func @fold_inttoptr_ptrtoint
+// CHECK-SAME: (%[[PTR:.*]]: !llvm.ptr)
+llvm.func @fold_inttoptr_ptrtoint(%ptr: !llvm.ptr) -> !llvm.ptr {
+  // CHECK-NOT: llvm.ptrtoint
+  // CHECK-NOT: llvm.inttoptr
+  // CHECK: llvm.return %[[PTR]] : !llvm.ptr
+  %0 = llvm.ptrtoint %ptr : !llvm.ptr to i64
+  %1 = llvm.inttoptr %0 : i64 to !llvm.ptr
+  llvm.return %1 : !llvm.ptr
+}
+
+// -----
+
+// CHECK-LABEL: llvm.func @fold_inttoptr_ptrtoint_wider_int
+// CHECK-SAME: (%[[PTR:.*]]: !llvm.ptr)
+llvm.func @fold_inttoptr_ptrtoint_wider_int(%ptr: !llvm.ptr) -> !llvm.ptr {
+  // CHECK-NOT: llvm.ptrtoint
+  // CHECK-NOT: llvm.inttoptr
+  // CHECK: llvm.return %[[PTR]] : !llvm.ptr
+  %0 = llvm.ptrtoint %ptr : !llvm.ptr to i128
+  %1 = llvm.inttoptr %0 : i128 to !llvm.ptr
+  llvm.return %1 : !llvm.ptr
+}
+
+// -----
+
+// CHECK-LABEL: llvm.func @fold_inttoptr_ptrtoint_vector
+// CHECK-SAME: (%[[PTR:.*]]: vector<2x!llvm.ptr>)
+llvm.func @fold_inttoptr_ptrtoint_vector(%ptr: vector<2x!llvm.ptr>) -> vector<2x!llvm.ptr> {
+  // CHECK-NOT: llvm.ptrtoint
+  // CHECK-NOT: llvm.inttoptr
+  // CHECK: llvm.return %[[PTR]] : vector<2x!llvm.ptr>
+  %0 = llvm.ptrtoint %ptr : vector<2x!llvm.ptr> to vector<2xi64>
+  %1 = llvm.inttoptr %0 : vector<2xi64> to vector<2x!llvm.ptr>
+  llvm.return %1 : vector<2x!llvm.ptr>
+}
+
+// -----
+
+// CHECK-LABEL: llvm.func @fold_inttoptr_ptrtoint_scalable_vector
+// CHECK-SAME: (%[[PTR:.*]]: vector<[2]x!llvm.ptr>)
+llvm.func @fold_inttoptr_ptrtoint_scalable_vector(%ptr: vector<[2]x!llvm.ptr>) -> vector<[2]x!llvm.ptr> {
+  // CHECK-NOT: llvm.ptrtoint
+  // CHECK-NOT: llvm.inttoptr
+  // CHECK: llvm.return %[[PTR]] : vector<[2]x!llvm.ptr>
+  %0 = llvm.ptrtoint %ptr : vector<[2]x!llvm.ptr> to vector<[2]xi64>
+  %1 = llvm.inttoptr %0 : vector<[2]xi64> to vector<[2]x!llvm.ptr>
+  llvm.return %1 : vector<[2]x!llvm.ptr>
+}
+
+// -----
+
+// The intermediate integer is narrower than the (default, 64-bit) pointer.
+
+// CHECK-LABEL: llvm.func @no_fold_inttoptr_ptrtoint_narrow_int
+llvm.func @no_fold_inttoptr_ptrtoint_narrow_int(%ptr: !llvm.ptr) -> !llvm.ptr {
+  // CHECK: %[[INT:.*]] = llvm.ptrtoint %{{.*}} : !llvm.ptr to i32
+  // CHECK: %[[RES:.*]] = llvm.inttoptr %[[INT]] : i32 to !llvm.ptr
+  // CHECK: llvm.return %[[RES]]
+  %0 = llvm.ptrtoint %ptr : !llvm.ptr to i32
+  %1 = llvm.inttoptr %0 : i32 to !llvm.ptr
+  llvm.return %1 : !llvm.ptr
+}
+
+// -----
+
+// CHECK-LABEL: llvm.func @no_fold_inttoptr_ptrtoint_dereferenceable
+llvm.func @no_fold_inttoptr_ptrtoint_dereferenceable(%ptr: !llvm.ptr) -> !llvm.ptr {
+  // CHECK: %[[INT:.*]] = llvm.ptrtoint %{{.*}} : !llvm.ptr to i64
+  // CHECK: %[[RES:.*]] = llvm.inttoptr %[[INT]] dereferenceable<bytes = 8> : i64 to !llvm.ptr
+  // CHECK: llvm.return %[[RES]]
+  %0 = llvm.ptrtoint %ptr : !llvm.ptr to i64
+  %1 = llvm.inttoptr %0 dereferenceable<bytes = 8> : i64 to !llvm.ptr
+  llvm.return %1 : !llvm.ptr
+}
+
+// -----
+
+// CHECK-LABEL: llvm.func @no_fold_inttoptr_ptrtoint_addrspace
+llvm.func @no_fold_inttoptr_ptrtoint_addrspace(%ptr: !llvm.ptr<1>) -> !llvm.ptr {
+  // CHECK: %[[INT:.*]] = llvm.ptrtoint %{{.*}} : !llvm.ptr<1> to i64
+  // CHECK: %[[RES:.*]] = llvm.inttoptr %[[INT]] : i64 to !llvm.ptr
+  // CHECK: llvm.return %[[RES]]
+  %0 = llvm.ptrtoint %ptr : !llvm.ptr<1> to i64
+  %1 = llvm.inttoptr %0 : i64 to !llvm.ptr
+  llvm.return %1 : !llvm.ptr
+}
+
+// -----
+
+// With 32-bit pointers, a 32-bit intermediate integer is sufficient.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<
+  #dlti.dl_entry<!llvm.ptr, dense<[32, 32, 32]> : vector<3xi64>>
+>} {
+  // CHECK-LABEL: llvm.func @fold_inttoptr_ptrtoint_32bit_ptr
+  // CHECK-SAME: (%[[PTR:.*]]: !llvm.ptr)
+  llvm.func @fold_inttoptr_ptrtoint_32bit_ptr(%ptr: !llvm.ptr) -> !llvm.ptr {
+    // CHECK-NOT: llvm.ptrtoint
+    // CHECK-NOT: llvm.inttoptr
+    // CHECK: llvm.return %[[PTR]] : !llvm.ptr
+    %0 = llvm.ptrtoint %ptr : !llvm.ptr to i32
+    %1 = llvm.inttoptr %0 : i32 to !llvm.ptr
+    llvm.return %1 : !llvm.ptr
+  }
+}
+
+// -----
+
+// With 128-bit pointers, a 64-bit intermediate integer truncates the pointer.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<
+  #dlti.dl_entry<!llvm.ptr, dense<[128, 128, 128]> : vector<3xi64>>
+>} {
+  // CHECK-LABEL: llvm.func @no_fold_inttoptr_ptrtoint_128bit_ptr
+  llvm.func @no_fold_inttoptr_ptrtoint_128bit_ptr(%ptr: !llvm.ptr) -> !llvm.ptr {
+    // CHECK: %[[INT:.*]] = llvm.ptrtoint %{{.*}} : !llvm.ptr to i64
+    // CHECK: %[[RES:.*]] = llvm.inttoptr %[[INT]] : i64 to !llvm.ptr
+    // CHECK: llvm.return %[[RES]]
+    %0 = llvm.ptrtoint %ptr : !llvm.ptr to i64
+    %1 = llvm.inttoptr %0 : i64 to !llvm.ptr
+    llvm.return %1 : !llvm.ptr
+  }
+}



More information about the Mlir-commits mailing list