[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