[Mlir-commits] [mlir] [MLIR][LLVM] Import GEP inrange at the index width of its base pointer (PR #229253)

William Moses llvmlistbot at llvm.org
Tue Oct 6 05:30:15 PDT 2026


https://github.com/wsmoses updated https://github.com/llvm/llvm-project/pull/229253

>From 400b3a6399a031a6362deead5301e07770c4ba2d Mon Sep 17 00:00:00 2001
From: William Moses <gh at wsmoses.com>
Date: Mon, 5 Oct 2026 17:42:31 -0500
Subject: [PATCH 1/2] [MLIR][LLVM] Import GEP inrange at the index width of its
 base pointer

The textual IR parser builds a constant GEP's `inrange` range at the index
width of the GEP's base pointer, but the C++ API keeps whatever width it is
given, and clang builds the range of a vtable address point with 32 bits
regardless of the index width (ItaniumCXXABI::getVTableAddressPoint).
ModuleImport carried that range over to llvm.getelementptr as is, so
importing C++ code with a vtable on a 64-bit target failed the GEP verifier
added in #220424:

  'llvm.getelementptr' op 'inrange' bitwidth 32 must match the pointer
  index bitwidth (64) specified in the datalayout

Sign-extend (or truncate) the range to the base pointer's index width on
import. Since the parser always produces the index width, the test builds
the narrow range through the C++ API, as clang does.

Assisted-by: Claude Code
---
 mlir/lib/Target/LLVMIR/ModuleImport.cpp       | 12 ++-
 mlir/unittests/Target/LLVM/CMakeLists.txt     |  5 +-
 .../Target/LLVM/ImportGEPInRange.cpp          | 79 +++++++++++++++++++
 3 files changed, 93 insertions(+), 3 deletions(-)
 create mode 100644 mlir/unittests/Target/LLVM/ImportGEPInRange.cpp

diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index cadcd22dd2412..c51d03fca1753 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1978,12 +1978,20 @@ FailureOr<Value> ModuleImport::convertConstant(llvm::Constant *constant) {
     // getAsInstruction() does not preserve GEP `inrange`, which exists only on
     // constant expressions. Reattach it to the imported GEPOp.
     if (constExpr->getOpcode() == llvm::Instruction::GetElementPtr) {
+      auto *gepOperator = llvm::cast<llvm::GEPOperator>(constExpr);
       if (std::optional<llvm::ConstantRange> inRange =
-              llvm::cast<llvm::GEPOperator>(constExpr)->getInRange()) {
+              gepOperator->getInRange()) {
         auto gepOp = result.getDefiningOp<GEPOp>();
         assert(gepOp && "expected GEPOp for getelementptr constexpr");
+        // The range is not always built at the index width of the base
+        // pointer (clang uses 32 bits for vtable address points); bring it to
+        // that width, as the textual IR parser does.
+        unsigned indexWidth =
+            llvmModule->getDataLayout().getIndexTypeSizeInBits(
+                gepOperator->getPointerOperandType());
         gepOp.setInrangeAttr(LLVM::ConstantRangeAttr::get(
-            context, inRange->getLower(), inRange->getUpper()));
+            context, inRange->getLower().sextOrTrunc(indexWidth),
+            inRange->getUpper().sextOrTrunc(indexWidth)));
       }
     }
     return result;
diff --git a/mlir/unittests/Target/LLVM/CMakeLists.txt b/mlir/unittests/Target/LLVM/CMakeLists.txt
index 0a77deb9ee9ea..ec4619061648d 100644
--- a/mlir/unittests/Target/LLVM/CMakeLists.txt
+++ b/mlir/unittests/Target/LLVM/CMakeLists.txt
@@ -1,6 +1,7 @@
-set(LLVM_LINK_COMPONENTS nativecodegen BitReader)
+set(LLVM_LINK_COMPONENTS nativecodegen AsmParser BitReader Core)
 
 add_mlir_unittest(MLIRTargetLLVMTests
+  ImportGEPInRange.cpp
   SerializeNVVMTarget.cpp
   SerializeROCDLTarget.cpp
   SerializeToLLVMBitcode.cpp
@@ -11,6 +12,8 @@ DEPENDS
 mlir_target_link_libraries(MLIRTargetLLVMTests
   PRIVATE
   MLIRTargetLLVM
+  MLIRTargetLLVMIRImport
+  MLIRDLTIDialect
   MLIRNVVMTarget
   MLIRROCDLTarget
   MLIRGPUDialect
diff --git a/mlir/unittests/Target/LLVM/ImportGEPInRange.cpp b/mlir/unittests/Target/LLVM/ImportGEPInRange.cpp
new file mode 100644
index 0000000000000..d5bcbea9c5e44
--- /dev/null
+++ b/mlir/unittests/Target/LLVM/ImportGEPInRange.cpp
@@ -0,0 +1,79 @@
+//===- ImportGEPInRange.cpp -----------------------------------------------===//
+//
+// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/DLTI/DLTI.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/Verifier.h"
+#include "mlir/Target/LLVMIR/Import.h"
+
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Operator.h"
+#include "llvm/Support/SourceMgr.h"
+
+#include "gtest/gtest.h"
+
+using namespace mlir;
+
+// The textual IR parser builds a GEP's `inrange` at the index width of its
+// base pointer, but the C++ API keeps whatever width it is given: clang builds
+// vtable address points with a 32-bit range regardless of the index width.
+// Importing such a GEP must still produce an `inrange` that verifies.
+TEST(ImportGEPInRange, NarrowRangeIsWidenedToIndexWidth) {
+  static constexpr const char *ir = R"(
+target datalayout = "e-m:e-i64:64-n8:16:32:64-S128"
+ at vtable = external constant { [7 x ptr] }
+define ptr @f() {
+  ret ptr getelementptr inbounds inrange(-16, 40) ({ [7 x ptr] }, ptr @vtable, i32 0, i32 0, i32 2)
+}
+)";
+  llvm::LLVMContext llvmContext;
+  llvm::SMDiagnostic diag;
+  std::unique_ptr<llvm::Module> llvmModule =
+      llvm::parseAssemblyString(ir, diag, llvmContext);
+  ASSERT_TRUE(llvmModule);
+
+  // Rebuild the GEP with a 32-bit range, as clang's ItaniumCXXABI does.
+  auto *ret = llvm::cast<llvm::ReturnInst>(
+      llvmModule->getFunction("f")->getEntryBlock().getTerminator());
+  auto *gep = llvm::cast<llvm::GEPOperator>(ret->getReturnValue());
+  SmallVector<llvm::Constant *> indices;
+  for (llvm::Use &index : gep->indices())
+    indices.push_back(llvm::cast<llvm::Constant>(index.get()));
+  llvm::ConstantRange narrowRange(llvm::APInt(32, -16, /*isSigned=*/true),
+                                  llvm::APInt(32, 40, /*isSigned=*/true));
+  ret->setOperand(0, llvm::ConstantExpr::getGetElementPtr(
+                         gep->getSourceElementType(),
+                         llvm::cast<llvm::Constant>(gep->getPointerOperand()),
+                         indices, gep->getNoWrapFlags(), narrowRange));
+
+  MLIRContext context;
+  context.loadDialect<LLVM::LLVMDialect, DLTIDialect>();
+  OwningOpRef<ModuleOp> module =
+      translateLLVMIRToModule(std::move(llvmModule), &context);
+  ASSERT_TRUE(module);
+  EXPECT_TRUE(succeeded(verify(*module)));
+
+  unsigned numGEPs = 0;
+  module->walk([&](LLVM::GEPOp op) {
+    LLVM::ConstantRangeAttr inrange = op.getInrangeAttr();
+    if (!inrange)
+      return;
+    ++numGEPs;
+    EXPECT_EQ(inrange.getLower().getBitWidth(), 64u);
+    EXPECT_EQ(inrange.getLower().getSExtValue(), -16);
+    EXPECT_EQ(inrange.getUpper().getSExtValue(), 40);
+  });
+  EXPECT_EQ(numGEPs, 1u);
+}

>From 4746c69994e2ebda15472154d84e4eaada3c26c8 Mon Sep 17 00:00:00 2001
From: William Moses <gh at wsmoses.com>
Date: Tue, 6 Oct 2026 07:28:42 -0500
Subject: [PATCH 2/2] Use the DataLayout overload of getGetElementPtr in the
 test

The other overloads are deprecated, which fails the -Werror build. This is
also the overload clang uses for vtable address points, and it keeps the
32-bit inrange the test relies on.

Assisted-by: Claude Code
---
 mlir/unittests/Target/LLVM/ImportGEPInRange.cpp | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/mlir/unittests/Target/LLVM/ImportGEPInRange.cpp b/mlir/unittests/Target/LLVM/ImportGEPInRange.cpp
index d5bcbea9c5e44..1c1793ac238e7 100644
--- a/mlir/unittests/Target/LLVM/ImportGEPInRange.cpp
+++ b/mlir/unittests/Target/LLVM/ImportGEPInRange.cpp
@@ -53,10 +53,12 @@ define ptr @f() {
     indices.push_back(llvm::cast<llvm::Constant>(index.get()));
   llvm::ConstantRange narrowRange(llvm::APInt(32, -16, /*isSigned=*/true),
                                   llvm::APInt(32, 40, /*isSigned=*/true));
-  ret->setOperand(0, llvm::ConstantExpr::getGetElementPtr(
-                         gep->getSourceElementType(),
-                         llvm::cast<llvm::Constant>(gep->getPointerOperand()),
-                         indices, gep->getNoWrapFlags(), narrowRange));
+  llvm::Constant *narrowGEP = llvm::ConstantExpr::getGetElementPtr(
+      llvmModule->getDataLayout(), gep->getSourceElementType(),
+      llvm::cast<llvm::Constant>(gep->getPointerOperand()), indices,
+      gep->getNoWrapFlags(), narrowRange);
+  ASSERT_TRUE(narrowGEP);
+  ret->setOperand(0, narrowGEP);
 
   MLIRContext context;
   context.loadDialect<LLVM::LLVMDialect, DLTIDialect>();



More information about the Mlir-commits mailing list