[Mlir-commits] [mlir] [MLIR][LLVM] Support inrange in GEP (PR #220424)
Letu Ren
llvmlistbot at llvm.org
Tue Sep 22 03:02:01 PDT 2026
https://github.com/FantasqueX updated https://github.com/llvm/llvm-project/pull/220424
>From 3b10ffd2d0661c32d6007072bee75f11b72f5a34 Mon Sep 17 00:00:00 2001
From: Letu Ren <fantasquex at gmail.com>
Date: Mon, 31 Aug 2026 16:28:43 +0800
Subject: [PATCH] [MLIR][LLVM] Support inrange in GEP
LLVM IR allows inrange(Start, End) on constant GEP expressions.
Import dropped it because getAsInstruction() does not preserve it.
Model it as an index-width ConstantRangeAttr on GEPOp, reject empty
ranges, and emit ConstantExpr::getGetElementPtr. Fold keeps the
attribute. mem2reg and SROA bail out, because an inrange GEP on an
alloca is not valid LLVM IR.
Fixes #128031
Assisted-by: gpt-5.6-sol
Assisted-by: grok-4.7
Signed-off-by: Letu Ren <fantasquex at gmail.com>
---
mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td | 37 ++++++++--------
mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 32 ++++++++++++--
mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp | 8 +++-
.../LLVMIR/LLVMToLLVMIRTranslation.cpp | 44 +++++++++++++++++++
mlir/lib/Target/LLVMIR/ModuleImport.cpp | 14 +++++-
mlir/test/Dialect/LLVMIR/canonicalize.mlir | 11 +++++
mlir/test/Dialect/LLVMIR/invalid.mlir | 36 +++++++++++++++
mlir/test/Dialect/LLVMIR/mem2reg.mlir | 16 +++++++
mlir/test/Dialect/LLVMIR/roundtrip.mlir | 4 ++
mlir/test/Dialect/LLVMIR/sroa.mlir | 17 +++++++
mlir/test/Target/LLVMIR/Import/constant.ll | 12 +++++
.../Target/LLVMIR/Import/global-variables.ll | 5 +++
mlir/test/Target/LLVMIR/llvmir-invalid.mlir | 21 +++++++++
mlir/test/Target/LLVMIR/llvmir.mlir | 9 ++++
14 files changed, 242 insertions(+), 24 deletions(-)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 5998bd01f9bdf..9766529851193 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -301,7 +301,8 @@ def LLVM_GEPOp : LLVM_Op<"getelementptr", [Pure,
Variadic<LLVM_ScalarOrVectorOf<AnySignlessInteger>>:$dynamicIndices,
DenseI32ArrayAttr:$rawConstantIndices,
TypeAttr:$elem_type,
- GEPNoWrapFlagsProp:$noWrapFlags);
+ GEPNoWrapFlagsProp:$noWrapFlags,
+ OptionalAttr<LLVM_ConstantRangeAttr>:$inrange);
let results = (outs LLVM_ScalarOrVectorOf<LLVM_AnyPointer>:$res);
let skipDefaultBuilders = 1;
@@ -320,6 +321,14 @@ def LLVM_GEPOp : LLVM_Op<"getelementptr", [Pure,
Note that 'inbounds' implies 'nusw' which is ensured by the enum
definition. The flags can be set individually or in combination.
+ An optional `inrange` attribute corresponds to the LLVM IR `inrange(Start,
+ End)` qualifier on constant GEP expressions. Loading from or storing to any
+ pointer derived from the GEP has undefined behavior if the access would fall
+ outside the half-open range `[Start, End)` from the GEP result. The bounds
+ use the pointer index type: their bitwidth must match the index bitwidth of
+ the base pointer in the data layout. LLVM IR permits `inrange` only on
+ constant GEP expressions.
+
Examples:
```mlir
@@ -332,6 +341,10 @@ def LLVM_GEPOp : LLVM_Op<"getelementptr", [Pure,
// GEP with constant offsets into a structure
%0 = llvm.getelementptr %1[0, 1]
: (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(i32, f32)>
+
+ // Constant GEP with an inrange qualifier
+ %0 = llvm.getelementptr inbounds inrange <i64, -16, 8> %1[0, 0, 2]
+ : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
```
}];
@@ -339,33 +352,19 @@ def LLVM_GEPOp : LLVM_Op<"getelementptr", [Pure,
OpBuilder<(ins "Type":$resultType, "Type":$elementType, "Value":$basePtr,
"ValueRange":$indices,
CArg<"GEPNoWrapFlags", "GEPNoWrapFlags::none">:$noWrapFlags,
+ CArg<"ConstantRangeAttr", "{}">:$inrange,
CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>,
OpBuilder<(ins "Type":$resultType, "Type":$elementType, "Value":$basePtr,
"ArrayRef<GEPArg>":$indices,
CArg<"GEPNoWrapFlags", "GEPNoWrapFlags::none">:$noWrapFlags,
+ CArg<"ConstantRangeAttr", "{}">:$inrange,
CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>,
];
let llvmBuilder = [{
- SmallVector<llvm::Value *> indices;
- indices.reserve($rawConstantIndices.size());
- GEPIndicesAdaptor<decltype($dynamicIndices)>
- gepIndices(op.getRawConstantIndicesAttr(), $dynamicIndices);
- for (PointerUnion<IntegerAttr, llvm::Value*> valueOrAttr : gepIndices) {
- if (llvm::Value* value = ::llvm::dyn_cast<llvm::Value*>(valueOrAttr))
- indices.push_back(value);
- else
- indices.push_back(
- builder.getInt32(cast<IntegerAttr>(valueOrAttr).getInt()));
- }
- Type baseElementType = op.getElemType();
- llvm::Type *elementType = moduleTranslation.convertType(baseElementType);
- $res = builder.CreateGEP(elementType, $base, indices, "",
- llvm::GEPNoWrapFlags::fromRaw(
- static_cast<unsigned>(
- op.getNoWrapFlags())));
+ return convertGEPOp(op, builder, moduleTranslation);
}];
let assemblyFormat = [{
- ($noWrapFlags^)?
+ ($noWrapFlags^)? (`inrange` $inrange^)?
$base `[` custom<GEPIndices>($dynamicIndices, $rawConstantIndices) `]` attr-dict
`:` functional-type(operands, results) `,` $elem_type
}];
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index e6b31fc3f9f8b..b931631369e6f 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -697,7 +697,7 @@ static void destructureIndices(Type currType, ArrayRef<GEPArg> indices,
void GEPOp::build(OpBuilder &builder, OperationState &result, Type resultType,
Type elementType, Value basePtr, ArrayRef<GEPArg> indices,
- GEPNoWrapFlags noWrapFlags,
+ GEPNoWrapFlags noWrapFlags, ConstantRangeAttr inrange,
ArrayRef<NamedAttribute> attributes) {
SmallVector<int32_t> rawConstantIndices;
SmallVector<Value> dynamicIndices;
@@ -710,16 +710,17 @@ void GEPOp::build(OpBuilder &builder, OperationState &result, Type resultType,
result.getOrAddProperties<Properties>().noWrapFlags = noWrapFlags;
result.getOrAddProperties<Properties>().elem_type =
TypeAttr::get(elementType);
+ result.getOrAddProperties<Properties>().inrange = inrange;
result.addOperands(basePtr);
result.addOperands(dynamicIndices);
}
void GEPOp::build(OpBuilder &builder, OperationState &result, Type resultType,
Type elementType, Value basePtr, ValueRange indices,
- GEPNoWrapFlags noWrapFlags,
+ GEPNoWrapFlags noWrapFlags, ConstantRangeAttr inrange,
ArrayRef<NamedAttribute> attributes) {
build(builder, result, resultType, elementType, basePtr,
- SmallVector<GEPArg>(indices), noWrapFlags, attributes);
+ SmallVector<GEPArg>(indices), noWrapFlags, inrange, attributes);
}
ParseResult mlir::LLVM::parseGEPIndices(
@@ -821,6 +822,27 @@ LogicalResult LLVM::GEPOp::verify() {
if (getNoWrapFlags() == GEPNoWrapFlags::inboundsFlag)
return emitOpError("'inbounds_flag' cannot be used directly.");
+ // LLVM stores `inrange` only on GetElementPtrConstantExpr, whose operands
+ // are already Constants. This dialect has one GEP operation, so proving the
+ // base and indices are constant expressions means walking SSA. A local check
+ // rejects valid cases such as a nested constant GEP base or a `ptrtoint`
+ // index.
+ if (auto inrange = getInrangeAttr()) {
+ auto pointerType =
+ cast<LLVMPointerType>(extractVectorElementType(getBase().getType()));
+ DataLayout dataLayout = DataLayout::closest(*this);
+ std::optional<uint64_t> indexWidth =
+ dataLayout.getTypeIndexBitwidth(pointerType);
+ assert(indexWidth && "pointers always return an index bitwidth");
+ if (inrange.getLower().getBitWidth() != *indexWidth)
+ return emitOpError("'inrange' bitwidth ")
+ << inrange.getLower().getBitWidth()
+ << " must match the pointer index bitwidth (" << *indexWidth
+ << ") specified in the datalayout";
+ if (inrange.getLower().sge(inrange.getUpper()))
+ return emitOpError("expected 'inrange' end to be larger than start");
+ }
+
return verifyStructIndices(getElemType(), getIndices(),
[&] { return emitOpError(); });
}
@@ -4053,6 +4075,10 @@ OpFoldResult LLVM::GEPOp::fold(FoldAdaptor adaptor) {
GEPIndicesAdaptor<ArrayRef<Attribute>> indices(getRawConstantIndicesAttr(),
adaptor.getDynamicIndices());
+ // Avoid losing inrange information.
+ if (getInrangeAttr())
+ return {};
+
// gep %x:T, 0 -> %x
if (getBase().getType() == getType() && indices.size() == 1)
if (auto integer = llvm::dyn_cast_or_null<IntegerAttr>(indices[0]))
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp
index 2416e01a67955..6d42e977780d9 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp
@@ -695,7 +695,9 @@ bool LLVM::GEPOp::canUsesBeRemoved(
SmallVectorImpl<OpOperand *> &newBlockingUses,
const DataLayout &dataLayout) {
// GEP can be removed as long as it is a no-op and its users can be removed.
- if (!hasAllZeroIndices(*this))
+ // `inrange` is only valid on constant GEP expressions, so an inrange GEP on
+ // an alloca is illegal and we bail out.
+ if (getInrangeAttr() || !hasAllZeroIndices(*this))
return false;
return forwardToUsers(*this, newBlockingUses);
}
@@ -875,6 +877,10 @@ bool LLVM::GEPOp::canRewire(const DestructurableMemorySlot &slot,
if (getBase() != slot.ptr)
return false;
+ // `inrange` is only valid on constant GEP expressions, so an inrange GEP on
+ // an alloca is illegal and SROA bails out.
+ if (getInrangeAttr())
+ return false;
std::optional<SubslotAccessInfo> accessInfo =
getSubslotAccessInfo(slot, dataLayout, *this);
if (!accessInfo)
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
index 0c715da6ba954..98ddf0a7c51ea 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
@@ -19,6 +19,8 @@
#include "mlir/Target/LLVMIR/ModuleTranslation.h"
#include "llvm/ADT/TypeSwitch.h"
+#include "llvm/IR/ConstantRange.h"
+#include "llvm/IR/Constants.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InlineAsm.h"
@@ -234,6 +236,48 @@ convertNamedMetadataOp(NamedMetadataOp op,
return success();
}
+/// Translate `llvm.getelementptr`. `inrange` is only representable on LLVM
+/// constant GEP expressions.
+static LogicalResult convertGEPOp(GEPOp op, llvm::IRBuilderBase &builder,
+ LLVM::ModuleTranslation &moduleTranslation) {
+ SmallVector<llvm::Value *> indices;
+ indices.reserve(op.getRawConstantIndices().size());
+ for (PointerUnion<IntegerAttr, Value> valueOrAttr : op.getIndices()) {
+ if (Value value = dyn_cast_if_present<Value>(valueOrAttr))
+ indices.push_back(moduleTranslation.lookupValue(value));
+ else
+ indices.push_back(
+ builder.getInt32(cast<IntegerAttr>(valueOrAttr).getInt()));
+ }
+
+ llvm::Type *elementType = moduleTranslation.convertType(op.getElemType());
+ llvm::GEPNoWrapFlags nwFlags =
+ llvm::GEPNoWrapFlags::fromRaw(static_cast<unsigned>(op.getNoWrapFlags()));
+ llvm::Value *base = moduleTranslation.lookupValue(op.getBase());
+ llvm::Value *res;
+ if (ConstantRangeAttr inrangeAttr = op.getInrangeAttr()) {
+ auto *baseConst = dyn_cast<llvm::Constant>(base);
+ if (!baseConst || !llvm::all_of(indices, [](llvm::Value *value) {
+ return isa<llvm::Constant>(value);
+ }))
+ return op.emitError("'inrange' requires the base and indices to "
+ "translate to LLVM constants");
+
+ SmallVector<llvm::Constant *> constIndices;
+ constIndices.reserve(indices.size());
+ for (llvm::Value *value : indices)
+ constIndices.push_back(cast<llvm::Constant>(value));
+ res = llvm::ConstantExpr::getGetElementPtr(
+ elementType, baseConst, constIndices, nwFlags,
+ llvm::ConstantRange::getNonEmpty(inrangeAttr.getLower(),
+ inrangeAttr.getUpper()));
+ } else {
+ res = builder.CreateGEP(elementType, base, indices, "", nwFlags);
+ }
+ moduleTranslation.mapValue(op.getRes()) = res;
+ return success();
+}
+
static void convertLinkerOptionsOp(ArrayAttr options,
llvm::IRBuilderBase &builder,
LLVM::ModuleTranslation &moduleTranslation) {
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index bfecde8b664a9..9e815f2b7ade0 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1973,7 +1973,19 @@ FailureOr<Value> ModuleImport::convertConstant(llvm::Constant *constant) {
}));
if (failed(processInstruction(inst)))
return failure();
- return lookupValue(inst);
+ Value result = lookupValue(inst);
+ // getAsInstruction() does not preserve GEP `inrange`, which exists only on
+ // constant expressions. Reattach it to the imported GEPOp.
+ if (constExpr->getOpcode() == llvm::Instruction::GetElementPtr) {
+ if (std::optional<llvm::ConstantRange> inRange =
+ llvm::cast<llvm::GEPOperator>(constExpr)->getInRange()) {
+ auto gepOp = result.getDefiningOp<GEPOp>();
+ assert(gepOp && "expected GEPOp for getelementptr constexpr");
+ gepOp.setInrangeAttr(LLVM::ConstantRangeAttr::get(
+ context, inRange->getLower(), inRange->getUpper()));
+ }
+ }
+ return result;
}
// Convert zero-initialized aggregates to ZeroOp.
diff --git a/mlir/test/Dialect/LLVMIR/canonicalize.mlir b/mlir/test/Dialect/LLVMIR/canonicalize.mlir
index 3a7e514ca8f8a..2dd0bbfe4922b 100644
--- a/mlir/test/Dialect/LLVMIR/canonicalize.mlir
+++ b/mlir/test/Dialect/LLVMIR/canonicalize.mlir
@@ -415,6 +415,17 @@ llvm.func @fold_gep(%x : !llvm.ptr) -> !llvm.ptr {
// -----
+// CHECK-LABEL: fold_gep_inrange
+// CHECK-SAME: %[[ARG:[[:alnum:]]+]]
+// CHECK-NEXT: %[[GEP:.*]] = llvm.getelementptr inrange <i64, -4, 4> %[[ARG]][0]
+// CHECK-NEXT: llvm.return %[[GEP]]
+llvm.func @fold_gep_inrange(%x : !llvm.ptr) -> !llvm.ptr {
+ %0 = llvm.getelementptr inrange <i64, -4, 4> %x[0] : (!llvm.ptr) -> !llvm.ptr, i8
+ llvm.return %0 : !llvm.ptr
+}
+
+// -----
+
// CHECK-LABEL: fold_gep_neg
// CHECK-SAME: %[[ARG:[[:alnum:]]+]]
// CHECK-NEXT: %[[RES:.*]] = llvm.getelementptr inbounds %[[ARG]][0, 1]
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index c2d59c1564a42..8b652b04d88b2 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -2064,6 +2064,42 @@ llvm.func @gep_inbounds_flag_usage(%ptr: !llvm.ptr, %idx: i64) {
// -----
+llvm.func @gep_inrange_reversed(%ptr: !llvm.ptr) {
+ // expected-error at +1 {{expected 'inrange' end to be larger than start}}
+ llvm.getelementptr inrange <i64, 4, -4> %ptr[0] : (!llvm.ptr) -> !llvm.ptr, i8
+ llvm.return
+}
+
+// -----
+
+llvm.func @gep_inrange_empty(%ptr: !llvm.ptr) {
+ // expected-error at +1 {{expected 'inrange' end to be larger than start}}
+ llvm.getelementptr inrange <i64, 1, 1> %ptr[0] : (!llvm.ptr) -> !llvm.ptr, i8
+ llvm.return
+}
+
+// -----
+
+llvm.func @gep_inrange_wrong_width(%ptr: !llvm.ptr) {
+ // expected-error at +1 {{'inrange' bitwidth 32 must match the pointer index bitwidth (64) specified in the datalayout}}
+ llvm.getelementptr inrange <i32, -4, 4> %ptr[0] : (!llvm.ptr) -> !llvm.ptr, i8
+ llvm.return
+}
+
+// -----
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<
+ #dlti.dl_entry<!llvm.ptr, dense<[32, 32, 64]> : vector<3xi64>>
+>} {
+ llvm.func @gep_inrange_wrong_width_32(%ptr: !llvm.ptr) {
+ // expected-error at +1 {{'inrange' bitwidth 64 must match the pointer index bitwidth (32) specified in the datalayout}}
+ llvm.getelementptr inrange <i64, -4, 4> %ptr[0] : (!llvm.ptr) -> !llvm.ptr, i8
+ llvm.return
+ }
+}
+
+// -----
+
llvm.mlir.global @bad_struct_array_init_size() : !llvm.array<2x!llvm.struct<(i32, f32)>> {
// expected-error at below {{'llvm.mlir.constant' op array attribute size does not match array type size in dimension 0: 1 vs. 2}}
%0 = llvm.mlir.constant([[42 : i32, 1.000000e+00 : f32]]) : !llvm.array<2x!llvm.struct<(i32, f32)>>
diff --git a/mlir/test/Dialect/LLVMIR/mem2reg.mlir b/mlir/test/Dialect/LLVMIR/mem2reg.mlir
index 67a543768f4ef..b0138f575831f 100644
--- a/mlir/test/Dialect/LLVMIR/mem2reg.mlir
+++ b/mlir/test/Dialect/LLVMIR/mem2reg.mlir
@@ -557,6 +557,22 @@ llvm.func @trivial_get_element_ptr() {
// -----
+// `inrange` is only valid on constant GEP expressions, so an inrange GEP on an
+// alloca is illegal. mem2reg should bail out and leave the GEP in place.
+// CHECK-LABEL: llvm.func @inrange_get_element_ptr
+llvm.func @inrange_get_element_ptr() {
+ %0 = llvm.mlir.constant(1 : i32) : i32
+ // CHECK: = llvm.alloca
+ %2 = llvm.alloca %0 x i8 {alignment = 8 : i64} : (i32) -> !llvm.ptr
+ // CHECK: llvm.getelementptr inrange <i64, -4, 4>
+ %4 = llvm.getelementptr inrange <i64, -4, 4> %2[0] : (!llvm.ptr) -> !llvm.ptr, i8
+ llvm.intr.lifetime.start %2 : !llvm.ptr
+ llvm.intr.lifetime.start %4 : !llvm.ptr
+ llvm.return
+}
+
+// -----
+
// CHECK-LABEL: llvm.func @nontrivial_get_element_ptr
llvm.func @nontrivial_get_element_ptr() {
%0 = llvm.mlir.constant(1 : i32) : i32
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index 02e0bdaf3b694..f5783e988c001 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -316,6 +316,10 @@ llvm.func @gep(%ptr: !llvm.ptr, %idx: i64, %ptr2: !llvm.ptr) {
llvm.getelementptr nusw | nuw %ptr2[%idx, 0, %idx] : (!llvm.ptr, i64, i64) -> !llvm.ptr, !llvm.struct<(array<10 x f32>)>
// CHECK: llvm.getelementptr nuw %{{.*}}[%{{.*}}, 0, %{{.*}}] : (!llvm.ptr, i64, i64) -> !llvm.ptr, !llvm.struct<(array<10 x f32>)>
llvm.getelementptr nuw %ptr2[%idx, 0, %idx] : (!llvm.ptr, i64, i64) -> !llvm.ptr, !llvm.struct<(array<10 x f32>)>
+ // CHECK: llvm.getelementptr inrange <i64, -16, 8> %{{.*}}[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
+ llvm.getelementptr inrange <i64, -16, 8> %ptr[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
+ // CHECK: llvm.getelementptr inbounds inrange <i64, -16, 8> %{{.*}}[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
+ llvm.getelementptr inbounds inrange <i64, -16, 8> %ptr[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
llvm.return
}
diff --git a/mlir/test/Dialect/LLVMIR/sroa.mlir b/mlir/test/Dialect/LLVMIR/sroa.mlir
index b019e66d5a430..f3c8e9c2667c7 100644
--- a/mlir/test/Dialect/LLVMIR/sroa.mlir
+++ b/mlir/test/Dialect/LLVMIR/sroa.mlir
@@ -463,3 +463,20 @@ llvm.func @empty_struct_not_destructured() -> !llvm.struct<()> {
%2 = llvm.load %1 : !llvm.ptr -> !llvm.struct<()>
llvm.return %2 : !llvm.struct<()>
}
+
+// -----
+
+// `inrange` is only valid on constant GEP expressions, so an inrange GEP on an
+// alloca is illegal. SROA should bail out and leave the alloca unchanged.
+// CHECK-LABEL: llvm.func @sroa_bails_on_inrange
+llvm.func @sroa_bails_on_inrange() -> i32 {
+ %0 = llvm.mlir.constant(1 : i32) : i32
+ // CHECK: %[[ALLOCA:.*]] = llvm.alloca %{{.*}} x !llvm.struct<"foo", (i32, f64, i32)>
+ %1 = llvm.alloca %0 x !llvm.struct<"foo", (i32, f64, i32)> {alignment = 8 : i64} : (i32) -> !llvm.ptr
+ // CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds inrange <i64, -4, 4> %[[ALLOCA]][0, 2]
+ %2 = llvm.getelementptr inbounds inrange <i64, -4, 4> %1[0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<"foo", (i32, f64, i32)>
+ // CHECK: %[[RES:.*]] = llvm.load %[[GEP]]
+ %3 = llvm.load %2 : !llvm.ptr -> i32
+ // CHECK: llvm.return %[[RES]] : i32
+ llvm.return %3 : i32
+}
diff --git a/mlir/test/Target/LLVMIR/Import/constant.ll b/mlir/test/Target/LLVMIR/Import/constant.ll
index cd4f79ce741bc..d43306d1f4b20 100644
--- a/mlir/test/Target/LLVMIR/Import/constant.ll
+++ b/mlir/test/Target/LLVMIR/Import/constant.ll
@@ -80,6 +80,18 @@ define ptr @gep_const_expr() {
; // -----
+ at vt = external constant [3 x ptr]
+
+; CHECK-LABEL: @gep_const_expr_inrange
+define ptr @gep_const_expr_inrange() {
+ ; CHECK-DAG: %[[ADDR:[0-9]+]] = llvm.mlir.addressof @vt : !llvm.ptr
+ ; CHECK-DAG: %[[GEP:[0-9]+]] = llvm.getelementptr inbounds inrange <i{{[0-9]+}}, -16, 8> %[[ADDR]][{{.*}}] : (!llvm.ptr{{.*}}) -> !llvm.ptr
+ ; CHECK-DAG: llvm.return %[[GEP]] : !llvm.ptr
+ ret ptr getelementptr inbounds inrange(-16, 8) ([3 x ptr], ptr @vt, i64 0, i64 2)
+}
+
+; // -----
+
@global = external global i32, align 8
; CHECK-LABEL: @const_expr_with_duplicate
diff --git a/mlir/test/Target/LLVMIR/Import/global-variables.ll b/mlir/test/Target/LLVMIR/Import/global-variables.ll
index 905e9cf3982e5..d57c92db7253b 100644
--- a/mlir/test/Target/LLVMIR/Import/global-variables.ll
+++ b/mlir/test/Target/LLVMIR/Import/global-variables.ll
@@ -40,6 +40,11 @@
; CHECK-DAG: llvm.return %[[GEP]] : !llvm.ptr
@global_gep_const_expr = internal constant ptr getelementptr (i32, ptr @global_int, i32 2)
+; CHECK: llvm.mlir.global internal constant @global_gep_inrange
+; CHECK: llvm.getelementptr inbounds inrange <i{{[0-9]+}}, -16, 8>
+ at vt = external constant [3 x ptr]
+ at global_gep_inrange = internal constant ptr getelementptr inbounds inrange(-16, 8) ([3 x ptr], ptr @vt, i64 0, i64 2)
+
; // -----
; Verifies that converting a reference to a global does not convert the global
diff --git a/mlir/test/Target/LLVMIR/llvmir-invalid.mlir b/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
index 6e8301bb626e3..47678734e81ac 100644
--- a/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
@@ -1,5 +1,26 @@
// RUN: mlir-translate -verify-diagnostics -split-input-file -mlir-to-llvmir %s
+llvm.func @gep_inrange_nonconstant_base(%ptr: !llvm.ptr) -> !llvm.ptr {
+ // expected-error @below{{'inrange' requires the base and indices to translate to LLVM constants}}
+ // expected-error @below{{LLVM Translation failed for operation: llvm.getelementptr}}
+ %0 = llvm.getelementptr inrange <i64, -4, 4> %ptr[0] : (!llvm.ptr) -> !llvm.ptr, i8
+ llvm.return %0 : !llvm.ptr
+}
+
+// -----
+
+llvm.mlir.global external @gep_base() : i8
+
+llvm.func @gep_inrange_nonconstant_index(%idx: i64) -> !llvm.ptr {
+ %addr = llvm.mlir.addressof @gep_base : !llvm.ptr
+ // expected-error @below{{'inrange' requires the base and indices to translate to LLVM constants}}
+ // expected-error @below{{LLVM Translation failed for operation: llvm.getelementptr}}
+ %0 = llvm.getelementptr inrange <i64, -4, 4> %addr[%idx] : (!llvm.ptr, i64) -> !llvm.ptr, i8
+ llvm.return %0 : !llvm.ptr
+}
+
+// -----
+
// expected-error @below{{cannot be converted to LLVM IR}}
func.func @foo() {
llvm.return
diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir
index d9bc379ade0a4..fb0cf9b493af4 100644
--- a/mlir/test/Target/LLVMIR/llvmir.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir.mlir
@@ -122,6 +122,15 @@ llvm.mlir.global internal constant @int_gep() : !llvm.ptr {
llvm.return %gepinit : !llvm.ptr
}
+// CHECK: @vt = external constant { [3 x ptr] }
+llvm.mlir.global external constant @vt() : !llvm.struct<(array<3 x ptr>)>
+// CHECK: @int_gep_inrange = internal constant ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr] }, ptr @vt, i32 0, i32 0, i32 2)
+llvm.mlir.global internal constant @int_gep_inrange() : !llvm.ptr {
+ %addr = llvm.mlir.addressof @vt : !llvm.ptr
+ %gepinit = llvm.getelementptr inbounds inrange <i64, -16, 8> %addr[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
+ llvm.return %gepinit : !llvm.ptr
+}
+
// CHECK{LITERAL}: @dense_float_vector = internal global <3 x float> <float 1.000000e+00, float 2.000000e+00, float 3.000000e+00>
llvm.mlir.global internal @dense_float_vector(dense<[1.0, 2.0, 3.0]> : vector<3xf32>) : vector<3xf32>
More information about the Mlir-commits
mailing list