[Mlir-commits] [mlir] [MLIR][LLVM] Support inrange in GEP (PR #220424)

Letu Ren llvmlistbot at llvm.org
Sat Sep 12 19:52:44 PDT 2026


https://github.com/FantasqueX updated https://github.com/llvm/llvm-project/pull/220424

>From f81c9e928fc96e923f36b1767c1157e7f0647b4e 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 LLVM_ConstantRangeAttr on GEPOp, reject empty ranges,
keep it through fold/SROA, and emit ConstantExpr::getGetElementPtr.
Fixes #128031

Assisted-by: gpt-5.6-sol
Assisted-by: grok-4.6
Signed-off-by: Letu Ren <fantasquex at gmail.com>
---
 mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td   | 35 ++++++------
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    | 16 ++++--
 mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp |  7 ++-
 .../LLVMIR/LLVMToLLVMIRTranslation.cpp        | 54 +++++++++++++++++++
 mlir/lib/Target/LLVMIR/ModuleImport.cpp       | 14 ++++-
 mlir/test/Dialect/LLVMIR/canonicalize.mlir    | 11 ++++
 mlir/test/Dialect/LLVMIR/invalid.mlir         | 16 ++++++
 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   | 35 ++++++++++++
 mlir/test/Target/LLVMIR/llvmir.mlir           |  9 ++++
 14 files changed, 226 insertions(+), 25 deletions(-)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index b769e3fd0e6f1..f2252ac109f0d 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,12 @@ 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. LLVM IR
+    permits `inrange` only on constant GEP expressions.
+
     Examples:
 
     ```mlir
@@ -332,6 +339,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 <i32, -16, 8> %1[0, 0, 2]
+       : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
     ```
   }];
 
@@ -339,33 +350,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 52cd73f521326..4ceea572f3b11 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -698,7 +698,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;
@@ -711,16 +711,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(
@@ -822,6 +823,11 @@ LogicalResult LLVM::GEPOp::verify() {
   if (getNoWrapFlags() == GEPNoWrapFlags::inboundsFlag)
     return emitOpError("'inbounds_flag' cannot be used directly.");
 
+  if (auto inrange = getInrangeAttr()) {
+    if (inrange.getLower().sge(inrange.getUpper()))
+      return emitOpError("expected 'inrange' end to be larger than start");
+  }
+
   return verifyStructIndices(getElemType(), getIndices(),
                              [&] { return emitOpError(); });
 }
@@ -4050,6 +4056,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..4398c678b2cd6 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 defined relative to the GEP result, so a zero-index GEP is
+  // not a no-op when that would drop the qualifier.
+  if (getInrangeAttr() || !hasAllZeroIndices(*this))
     return false;
   return forwardToUsers(*this, newBlockingUses);
 }
@@ -909,7 +911,8 @@ DeletionKind LLVM::GEPOp::rewire(const DestructurableMemorySlot &slot,
   auto byteType = IntegerType::get(builder.getContext(), 8);
   auto newPtr = builder.createOrFold<LLVM::GEPOp>(
       getLoc(), getResult().getType(), byteType, newSlot.ptr,
-      ArrayRef<GEPArg>(accessInfo->subslotOffset), getNoWrapFlags());
+      ArrayRef<GEPArg>(accessInfo->subslotOffset), getNoWrapFlags(),
+      getInrangeAttr());
   getResult().replaceAllUsesWith(newPtr);
   return DeletionKind::Delete;
 }
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
index 0c715da6ba954..0447615850250 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,58 @@ 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");
+
+    unsigned indexBitWidth = moduleTranslation.getLLVMModule()
+                                 ->getDataLayout()
+                                 .getIndexTypeSizeInBits(base->getType());
+    llvm::APInt lower = inrangeAttr.getLower().sextOrTrunc(indexBitWidth);
+    llvm::APInt upper = inrangeAttr.getUpper().sextOrTrunc(indexBitWidth);
+    // The dialect verifier checks the range at the attribute bitwidth.
+    // Recheck after converting to the pointer index type.
+    if (lower.sge(upper))
+      return op.emitError("expected 'inrange' end to be larger than start "
+                          "after conversion to the pointer index type");
+
+    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(std::move(lower), std::move(upper)));
+  } 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 ce6da7b1b6c3d..5d8682d91879c 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 4ac160aa9f0fb..4d16e4c7df64d 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -2064,6 +2064,22 @@ 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 <i32, 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 <i32, 1, 1> %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..4c0b1e70664bb 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 defined relative to the GEP result, so a zero-index GEP is not a
+// no-op. mem2reg must not forward the GEP to the alloca.
+// 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 <i32, -4, 4>
+  %4 = llvm.getelementptr inrange <i32, -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 59a810116a040..b18e590c68780 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 <i32, -16, 8> %{{.*}}[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
+  llvm.getelementptr inrange <i32, -16, 8> %ptr[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
+  // CHECK: llvm.getelementptr inbounds inrange <i32, -16, 8> %{{.*}}[0, 0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<(array<3 x ptr>)>
+  llvm.getelementptr inbounds inrange <i32, -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..9b35a8a2515c5 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 defined relative to the GEP result, so SROA must keep it on the
+// rewritten GEP even when the new GEP would otherwise fold (offset 0).
+// CHECK-LABEL: llvm.func @sroa_preserves_inrange
+llvm.func @sroa_preserves_inrange() -> i32 {
+  %0 = llvm.mlir.constant(1 : i32) : i32
+  // CHECK: %[[ALLOCA:.*]] = llvm.alloca %{{.*}} x i32
+  %1 = llvm.alloca %0 x !llvm.struct<"foo", (i32, f64, i32)> {alignment = 8 : i64} : (i32) -> !llvm.ptr
+  %2 = llvm.getelementptr inbounds inrange <i32, -4, 4> %1[0, 2] : (!llvm.ptr) -> !llvm.ptr, !llvm.struct<"foo", (i32, f64, i32)>
+  // CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds inrange <i32, -4, 4> %[[ALLOCA]][0] : (!llvm.ptr) -> !llvm.ptr, i8
+  // 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..bba8d6248beab 100644
--- a/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-invalid.mlir
@@ -1,5 +1,40 @@
 // 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
+}
+
+// -----
+
+module attributes {llvm.data_layout = "e-p:32:32"} {
+  llvm.mlir.global external @gep_base() : i8
+  llvm.mlir.global internal @gep_inrange_truncated() : !llvm.ptr {
+    %addr = llvm.mlir.addressof @gep_base : !llvm.ptr
+    // expected-error @below{{expected 'inrange' end to be larger than start after conversion to the pointer index type}}
+    // expected-error @below{{LLVM Translation failed for operation: llvm.getelementptr}}
+    // expected-error @below{{fail to convert global initializer}}
+    %0 = llvm.getelementptr inrange <i64, 2147483647, 2147483648> %addr[0] : (!llvm.ptr) -> !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..fed1a6bee6bf4 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 <i32, -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