[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