[mlir][index] Add folders for `index` ops
authorJeff Niu <jeff@modular.com>
Tue, 11 Oct 2022 16:47:00 +0000 (09:47 -0700)
committerJeff Niu <jeff@modular.com>
Fri, 21 Oct 2022 16:46:12 +0000 (09:46 -0700)
This patch adds folders for `index` dialect ops. Ths folders are
careful to ensure that fold results are valid on both 32-bit and 64-bit
targets.

Depends on D135689

Reviewed By: rriddle

Differential Revision: https://reviews.llvm.org/D135694

mlir/include/mlir/Dialect/Index/IR/IndexDialect.td
mlir/include/mlir/Dialect/Index/IR/IndexOps.td
mlir/include/mlir/Support/LLVM.h
mlir/lib/Dialect/Index/IR/IndexOps.cpp
mlir/test/Dialect/Index/index-canonicalize.mlir [new file with mode: 0644]

index a0b155e..be0fea7 100644 (file)
@@ -81,6 +81,7 @@ def IndexDialect : Dialect {
     void registerOperations();
   }];
 
+  let hasConstantMaterializer = 1;
   let useDefaultAttributePrinterParser = 1;
 }
 
index c8dc007..b72283a 100644 (file)
@@ -34,6 +34,7 @@ class IndexBinaryOp<string mnemonic, list<Trait> traits = []>
   let arguments = (ins Index:$lhs, Index:$rhs);
   let results = (outs Index:$result);
   let assemblyFormat = "$lhs `,` $rhs attr-dict";
+  let hasFolder = 1;
 }
 
 //===----------------------------------------------------------------------===//
@@ -378,6 +379,7 @@ def Index_CmpOp : IndexOp<"cmp"> {
   let arguments = (ins IndexCmpPredicateAttr:$pred, Index:$lhs, Index:$rhs);
   let results = (outs I1:$result);
   let assemblyFormat = "`` $pred `(` $lhs `,` $rhs `)` attr-dict";
+  let hasFolder = 1;
 }
 
 //===----------------------------------------------------------------------===//
@@ -422,6 +424,7 @@ def Index_ConstantOp : IndexOp<"constant", [ConstantLike]> {
   let arguments = (ins IndexAttr:$value);
   let results = (outs Index:$result);
   let assemblyFormat = "attr-dict $value";
+  let hasFolder = 1;
 
   let builders = [OpBuilder<(ins "int64_t":$value)>];
 }
@@ -449,6 +452,7 @@ def Index_BoolConstantOp : IndexOp<"bool.constant", [ConstantLike]> {
   let arguments = (ins BoolAttr:$value);
   let results = (outs I1:$result);
   let assemblyFormat = "attr-dict $value";
+  let hasFolder = 1;
 }
 
 #endif // INDEX_OPS
index bb7e5b9..e442ca5 100644 (file)
@@ -99,6 +99,7 @@ namespace mlir {
 using llvm::cast;
 using llvm::cast_or_null;
 using llvm::dyn_cast;
+using llvm::dyn_cast_if_present;
 using llvm::dyn_cast_or_null;
 using llvm::isa;
 using llvm::isa_and_nonnull;
index 9c513da..fcbb076 100644 (file)
@@ -26,6 +26,264 @@ void IndexDialect::registerOperations() {
       >();
 }
 
+Operation *IndexDialect::materializeConstant(OpBuilder &b, Attribute value,
+                                             Type type, Location loc) {
+  // Materialize bool constants as `i1`.
+  if (auto boolValue = dyn_cast<BoolAttr>(value)) {
+    if (!type.isSignlessInteger(1))
+      return nullptr;
+    return b.create<BoolConstantOp>(loc, type, boolValue);
+  }
+
+  // Materialize integer attributes as `index`.
+  if (auto indexValue = dyn_cast<IntegerAttr>(value)) {
+    if (!indexValue.getType().isa<IndexType>() || !type.isa<IndexType>())
+      return nullptr;
+    assert(indexValue.getValue().getBitWidth() ==
+           IndexType::kInternalStorageBitWidth);
+    return b.create<ConstantOp>(loc, indexValue);
+  }
+
+  return nullptr;
+}
+
+//===----------------------------------------------------------------------===//
+// Fold Utilities
+//===----------------------------------------------------------------------===//
+
+/// Fold an index operation irrespective of the target bitwidth. The
+/// operation must satisfy the property:
+///
+/// ```
+/// trunc(f(a, b)) = f(trunc(a), trunc(b))
+/// ```
+///
+/// For all values of `a` and `b`. The function accepts a lambda that computes
+/// the integer result, which in turn must satisfy the above property.
+static OpFoldResult foldBinaryOpUnchecked(
+    ArrayRef<Attribute> operands,
+    function_ref<APInt(const APInt &, const APInt &)> calculate) {
+  assert(operands.size() == 2 && "binary operation expected 2 operands");
+  auto lhs = dyn_cast_if_present<IntegerAttr>(operands[0]);
+  auto rhs = dyn_cast_if_present<IntegerAttr>(operands[1]);
+  if (!lhs || !rhs)
+    return {};
+
+  APInt result = calculate(lhs.getValue(), rhs.getValue());
+  assert(result.trunc(32) ==
+         calculate(lhs.getValue().trunc(32), rhs.getValue().trunc(32)));
+  return IntegerAttr::get(IndexType::get(lhs.getContext()), std::move(result));
+}
+
+/// Fold an index operation only if the truncated 64-bit result matches the
+/// 32-bit result for operations that don't satisfy the above property. These
+/// are operations where the upper bits of the operands can affect the lower
+/// bits of the results.
+///
+/// The function accepts a lambda that computes the integer result in both
+/// 64-bit and 32-bit. If either call returns `None`, the operation is not
+/// folded.
+static OpFoldResult foldBinaryOpChecked(
+    ArrayRef<Attribute> operands,
+    function_ref<Optional<APInt>(const APInt &, const APInt &lhs)> calculate) {
+  assert(operands.size() == 2 && "binary operation expected 2 operands");
+  auto lhs = dyn_cast_if_present<IntegerAttr>(operands[0]);
+  auto rhs = dyn_cast_if_present<IntegerAttr>(operands[1]);
+  // Only fold index operands.
+  if (!lhs || !rhs)
+    return {};
+
+  // Compute the 64-bit result and the 32-bit result.
+  Optional<APInt> result64 = calculate(lhs.getValue(), rhs.getValue());
+  if (!result64)
+    return {};
+  Optional<APInt> result32 =
+      calculate(lhs.getValue().trunc(32), rhs.getValue().trunc(32));
+  if (!result32)
+    return {};
+  // Compare the truncated 64-bit result to the 32-bit result.
+  if (result64->trunc(32) != *result32)
+    return {};
+  // The operation can be folded for these particular operands.
+  return IntegerAttr::get(IndexType::get(lhs.getContext()),
+                          std::move(*result64));
+}
+
+//===----------------------------------------------------------------------===//
+// AddOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult AddOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpUnchecked(
+      operands, [](const APInt &lhs, const APInt &rhs) { return lhs + rhs; });
+}
+
+//===----------------------------------------------------------------------===//
+// SubOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult SubOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpUnchecked(
+      operands, [](const APInt &lhs, const APInt &rhs) { return lhs - rhs; });
+}
+
+//===----------------------------------------------------------------------===//
+// MulOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult MulOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpUnchecked(
+      operands, [](const APInt &lhs, const APInt &rhs) { return lhs * rhs; });
+}
+
+//===----------------------------------------------------------------------===//
+// DivSOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult DivSOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(
+      operands, [](const APInt &lhs, const APInt &rhs) -> Optional<APInt> {
+        // Don't fold division by zero.
+        if (rhs.isZero())
+          return None;
+        return lhs.sdiv(rhs);
+      });
+}
+
+//===----------------------------------------------------------------------===//
+// DivUOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult DivUOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(
+      operands, [](const APInt &lhs, const APInt &rhs) -> Optional<APInt> {
+        // Don't fold division by zero.
+        if (rhs.isZero())
+          return None;
+        return lhs.udiv(rhs);
+      });
+}
+
+//===----------------------------------------------------------------------===//
+// CeilDivSOp
+//===----------------------------------------------------------------------===//
+
+/// Compute `ceildivs(n, m)` as `x = m > 0 ? -1 : 1` and then
+/// `n*m > 0 ? (n+x)/m + 1 : -(-n/m)`.
+static Optional<APInt> calculateCeilDivS(const APInt &n, const APInt &m) {
+  // Don't fold division by zero.
+  if (m.isZero())
+    return None;
+  // Short-circuit the zero case.
+  if (n.isZero())
+    return n;
+
+  bool mGtZ = m.sgt(0);
+  if (n.sgt(0) != mGtZ) {
+    // If the operands have different signs, compute the negative result. Signed
+    // division overflow is not possible, since if `m == -1`, `n` can be at most
+    // `INT_MAX`, and `-INT_MAX != INT_MIN` in two's complement.
+    return -(-n).sdiv(m);
+  }
+  // Otherwise, compute the positive result. Signed division overflow is not
+  // possible since if `m == -1`, `x` will be `1`.
+  int64_t x = mGtZ ? -1 : 1;
+  return (n + x).sdiv(m) + 1;
+}
+
+OpFoldResult CeilDivSOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(operands, calculateCeilDivS);
+}
+
+//===----------------------------------------------------------------------===//
+// CeilDivUOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult CeilDivUOp::fold(ArrayRef<Attribute> operands) {
+  // Compute `ceildivu(n, m)` as `n == 0 ? 0 : (n-1)/m + 1`.
+  return foldBinaryOpChecked(
+      operands, [](const APInt &n, const APInt &m) -> Optional<APInt> {
+        // Don't fold division by zero.
+        if (m.isZero())
+          return None;
+        // Short-circuit the zero case.
+        if (n.isZero())
+          return n;
+
+        return (n - 1).udiv(m) + 1;
+      });
+}
+
+//===----------------------------------------------------------------------===//
+// FloorDivSOp
+//===----------------------------------------------------------------------===//
+
+/// Compute `floordivs(n, m)` as `x = m < 0 ? 1 : -1` and then
+/// `n*m < 0 ? -1 - (x-n)/m : n/m`.
+static Optional<APInt> calculateFloorDivS(const APInt &n, const APInt &m) {
+  // Don't fold division by zero.
+  if (m.isZero())
+    return None;
+  // Short-circuit the zero case.
+  if (n.isZero())
+    return n;
+
+  bool mLtZ = m.slt(0);
+  if (n.slt(0) == mLtZ) {
+    // If the operands have the same sign, compute the positive result.
+    return n.sdiv(m);
+  }
+  // If the operands have different signs, compute the negative result. Signed
+  // division overflow is not possible since if `m == -1`, `x` will be 1 and
+  // `n` can be at most `INT_MAX`.
+  int64_t x = mLtZ ? 1 : -1;
+  return -1 - (x - n).sdiv(m);
+}
+
+OpFoldResult FloorDivSOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(operands, calculateFloorDivS);
+}
+
+//===----------------------------------------------------------------------===//
+// RemSOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult RemSOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(operands, [](const APInt &lhs, const APInt &rhs) {
+    return lhs.srem(rhs);
+  });
+}
+
+//===----------------------------------------------------------------------===//
+// RemUOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult RemUOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(operands, [](const APInt &lhs, const APInt &rhs) {
+    return lhs.urem(rhs);
+  });
+}
+
+//===----------------------------------------------------------------------===//
+// MaxSOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult MaxSOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(operands, [](const APInt &lhs, const APInt &rhs) {
+    return lhs.sgt(rhs) ? lhs : rhs;
+  });
+}
+
+//===----------------------------------------------------------------------===//
+// MaxUOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult MaxUOp::fold(ArrayRef<Attribute> operands) {
+  return foldBinaryOpChecked(operands, [](const APInt &lhs, const APInt &rhs) {
+    return lhs.ugt(rhs) ? lhs : rhs;
+  });
+}
+
 //===----------------------------------------------------------------------===//
 // CastSOp
 //===----------------------------------------------------------------------===//
@@ -43,6 +301,74 @@ bool CastUOp::areCastCompatible(TypeRange lhsTypes, TypeRange rhsTypes) {
 }
 
 //===----------------------------------------------------------------------===//
+// CmpOp
+//===----------------------------------------------------------------------===//
+
+/// Compare two integers according to the comparison predicate.
+bool compareIndices(const APInt &lhs, const APInt &rhs,
+                    IndexCmpPredicate pred) {
+  switch (pred) {
+  case IndexCmpPredicate::EQ:
+    return lhs.eq(rhs);
+  case IndexCmpPredicate::NE:
+    return lhs.ne(rhs);
+  case IndexCmpPredicate::SGE:
+    return lhs.sge(rhs);
+  case IndexCmpPredicate::SGT:
+    return lhs.sgt(rhs);
+  case IndexCmpPredicate::SLE:
+    return lhs.sle(rhs);
+  case IndexCmpPredicate::SLT:
+    return lhs.slt(rhs);
+  case IndexCmpPredicate::UGE:
+    return lhs.uge(rhs);
+  case IndexCmpPredicate::UGT:
+    return lhs.ugt(rhs);
+  case IndexCmpPredicate::ULE:
+    return lhs.ule(rhs);
+  case IndexCmpPredicate::ULT:
+    return lhs.ult(rhs);
+  }
+  llvm_unreachable("unhandled IndexCmpPredicate predicate");
+}
+
+OpFoldResult CmpOp::fold(ArrayRef<Attribute> operands) {
+  assert(operands.size() == 2 && "compare expected 2 operands");
+  auto lhs = dyn_cast_if_present<IntegerAttr>(operands[0]);
+  auto rhs = dyn_cast_if_present<IntegerAttr>(operands[1]);
+  if (!lhs || !rhs)
+    return {};
+
+  // Perform the comparison in 64-bit and 32-bit.
+  bool result64 = compareIndices(lhs.getValue(), rhs.getValue(), getPred());
+  bool result32 = compareIndices(lhs.getValue().trunc(32),
+                                 rhs.getValue().trunc(32), getPred());
+  if (result64 != result32)
+    return {};
+  return BoolAttr::get(getContext(), result64);
+}
+
+//===----------------------------------------------------------------------===//
+// ConstantOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult ConstantOp::fold(ArrayRef<Attribute> operands) {
+  return getValueAttr();
+}
+
+void ConstantOp::build(OpBuilder &b, OperationState &state, int64_t value) {
+  build(b, state, b.getIndexType(), b.getIndexAttr(value));
+}
+
+//===----------------------------------------------------------------------===//
+// BoolConstantOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult BoolConstantOp::fold(ArrayRef<Attribute> operands) {
+  return getValueAttr();
+}
+
+//===----------------------------------------------------------------------===//
 // ODS-Generated Definitions
 //===----------------------------------------------------------------------===//
 
diff --git a/mlir/test/Dialect/Index/index-canonicalize.mlir b/mlir/test/Dialect/Index/index-canonicalize.mlir
new file mode 100644 (file)
index 0000000..f9b33f8
--- /dev/null
@@ -0,0 +1,319 @@
+// RUN: mlir-opt %s -canonicalize | FileCheck %s
+
+// CHECK-LABEL: @add
+func.func @add() -> (index, index) {
+  %0 = index.constant 1
+  %1 = index.constant 2100
+  %2 = index.constant 3000000001
+  %3 = index.constant 4000002100
+  // Folds normally.
+  %4 = index.add %0, %1
+  // Folds even though values exceed INT32_MAX.
+  %5 = index.add %2, %3
+
+  // CHECK-DAG: %[[A:.*]] = index.constant 2101
+  // CHECK-DAG: %[[B:.*]] = index.constant 7000002101
+  // CHECK: return %[[A]], %[[B]]
+  return %4, %5 : index, index
+}
+
+// CHECK-LABEL: @add_overflow
+func.func @add_overflow() -> (index, index) {
+  %0 = index.constant 2000000000
+  %1 = index.constant 8000000000000000000
+  // Folds normally.
+  %2 = index.add %0, %0
+  // Folds and overflows.
+  %3 = index.add %1, %1
+
+  // CHECK-DAG: %[[A:.*]] = index.constant 4{{0+}}
+  // CHECK-DAG: %[[B:.*]] = index.constant -2446{{[0-9]+}}
+  // CHECK: return %[[A]], %[[B]]
+  return %2, %3 : index, index
+}
+
+// CHECK-LABEL: @sub
+func.func @sub() -> index {
+  %0 = index.constant -2000000000
+  %1 = index.constant 3000000000
+  %2 = index.sub %0, %1
+  // CHECK: %[[A:.*]] = index.constant -5{{0+}}
+  // CHECK: return %[[A]]
+  return %2 : index
+}
+
+// CHECK-LABEL: @mul
+func.func @mul() -> index {
+  %0 = index.constant 8000000002000000000
+  %1 = index.constant 2
+  %2 = index.mul %0, %1
+  // CHECK: %[[A:.*]] = index.constant -2446{{[0-9]+}}
+  // CHECK: return %[[A]]
+  return %2 : index
+}
+
+// CHECK-LABEL: @divs
+func.func @divs() -> index {
+  %0 = index.constant -2
+  %1 = index.constant 0x200000000
+  %2 = index.divs %1, %0
+  // CHECK: %[[A:.*]] = index.constant -429{{[0-9]+}}
+  // CHECK: return %[[A]]
+  return %2 : index
+}
+
+// CHECK-LABEL: @divs_nofold
+func.func @divs_nofold() -> (index, index) {
+  %0 = index.constant 0
+  %1 = index.constant 0x100000000
+  %2 = index.constant 2
+
+  // Divide by zero.
+  // CHECK: index.divs
+  %3 = index.divs %2, %0
+  // 32-bit result differs from 64-bit.
+  // CHECK: index.divs
+  %4 = index.divs %1, %2
+
+  return %3, %4 : index, index
+}
+
+// CHECK-LABEL: @divu
+func.func @divu() -> index {
+  %0 = index.constant -2
+  %1 = index.constant 0x200000000
+  %2 = index.divu %1, %0
+  // CHECK: %[[A:.*]] = index.constant 0
+  // CHECK: return %[[A]]
+  return %2 : index
+}
+
+// CHECK-LABEL: @divu_nofold
+func.func @divu_nofold() -> (index, index) {
+  %0 = index.constant 0
+  %1 = index.constant 0x100000000
+  %2 = index.constant 2
+
+  // Divide by zero.
+  // CHECK: index.divu
+  %3 = index.divu %2, %0
+  // 32-bit result differs from 64-bit.
+  // CHECK: index.divu
+  %4 = index.divu %1, %2
+
+  return %3, %4 : index, index
+}
+
+// CHECK-LABEL: @ceildivs
+func.func @ceildivs() -> (index, index, index) {
+  %c0 = index.constant 0
+  %c2 = index.constant 2
+  %c5 = index.constant 5
+
+  // CHECK-DAG: %[[A:.*]] = index.constant 0
+  %0 = index.ceildivs %c0, %c5
+
+  // CHECK-DAG: %[[B:.*]] = index.constant 1
+  %1 = index.ceildivs %c2, %c5
+
+  // CHECK-DAG: %[[C:.*]] = index.constant 3
+  %2 = index.ceildivs %c5, %c2
+
+  // CHECK: return %[[A]], %[[B]], %[[C]]
+  return %0, %1, %2 : index, index, index
+}
+
+// CHECK-LABEL: @ceildivs_neg
+func.func @ceildivs_neg() -> index {
+  %c5 = index.constant -5
+  %c2 = index.constant 2
+  // CHECK: %[[A:.*]] = index.constant -2
+  %0 = index.ceildivs %c5, %c2
+  // CHECK: return %[[A]]
+  return %0 : index
+}
+
+// CHECK-LABEL: @ceildivs_edge
+func.func @ceildivs_edge() -> (index, index) {
+  %cn1 = index.constant -1
+  %cIntMin = index.constant -2147483648
+  %cIntMax = index.constant 2147483647
+
+  // The result is 0 on 32-bit.
+  // CHECK-DAG: %[[A:.*]] = index.constant 2147483648
+  %0 = index.ceildivs %cIntMin, %cn1
+
+  // CHECK-DAG: %[[B:.*]] = index.constant -2147483647
+  %1 = index.ceildivs %cIntMax, %cn1
+
+  // CHECK: return %[[A]], %[[B]]
+  return %0, %1 : index, index
+}
+
+// CHECK-LABEL: @ceildivu
+func.func @ceildivu() -> index {
+  %0 = index.constant 0x200000001
+  %1 = index.constant 2
+  // CHECK: %[[A:.*]] = index.constant 429{{[0-9]+}}7
+  %2 = index.ceildivu %0, %1
+  // CHECK: return %[[A]]
+  return %2 : index
+}
+
+// CHECK-LABEL: @floordivs
+func.func @floordivs() -> index {
+  %0 = index.constant -5
+  %1 = index.constant 2
+  // CHECK: %[[A:.*]] = index.constant -3
+  %2 = index.floordivs %0, %1
+  // CHECK: return %[[A]]
+  return %2 : index
+}
+
+// CHECK-LABEL: @floordivs_edge
+func.func @floordivs_edge() -> (index, index) {
+  %cIntMin = index.constant -2147483648
+  %cIntMax = index.constant 2147483647
+  %n1 = index.constant -1
+  %p1 = index.constant 1
+
+  // CHECK-DAG: %[[A:.*]] = index.constant -2147483648
+  // CHECK-DAG: %[[B:.*]] = index.constant -2147483647
+  %0 = index.floordivs %cIntMin, %p1
+  %1 = index.floordivs %cIntMax, %n1
+
+  // CHECK: return %[[A]], %[[B]]
+  return %0, %1 : index, index
+}
+
+// CHECK-LABEL: @floordivs_nofold
+func.func @floordivs_nofold() -> index {
+  %lhs = index.constant 0x100000000
+  %c2 = index.constant 2
+
+  // 32-bit result differs from 64-bit.
+  // CHECK: index.floordivs
+  %0 = index.floordivs %lhs, %c2
+
+  return %0 : index
+}
+
+// CHECK-LABEL: @rems
+func.func @rems() -> index {
+  %lhs = index.constant -5
+  %rhs = index.constant 2
+  // CHECK: %[[A:.*]] = index.constant -1
+  %0 = index.rems %lhs, %rhs
+  // CHECK: return %[[A]]
+  return %0 : index
+}
+
+// CHECK-LABEL: @rems_nofold
+func.func @rems_nofold() -> index {
+  %lhs = index.constant 2
+  %rhs = index.constant 0x100000001
+  // 32-bit result differs from 64-bit.
+  // CHECK: index.rems
+  %0 = index.rems %lhs, %rhs
+  return %0 : index
+}
+
+// CHECK-LABEL: @remu
+func.func @remu() -> index {
+  %lhs = index.constant 2
+  %rhs = index.constant -1
+  // CHECK: %[[A:.*]] = index.constant 2
+  %0 = index.remu %lhs, %rhs
+  // CHECK: return %[[A]]
+  return %0 : index
+}
+
+// CHECK-LABEL: @remu_nofold
+func.func @remu_nofold() -> index {
+  %lhs = index.constant 2
+  %rhs = index.constant 0x100000001
+  // 32-bit result differs from 64-bit.
+  // CHECK: index.remu
+  %0 = index.remu %lhs, %rhs
+  return %0 : index
+}
+
+// CHECK-LABEL: @maxs
+func.func @maxs() -> index {
+  %lhs = index.constant -4
+  %rhs = index.constant 2
+  // CHECK: %[[A:.*]] = index.constant 2
+  %0 = index.maxs %lhs, %rhs
+  // CHECK: return %[[A]]
+  return %0 : index
+}
+
+// CHECK-LABEL: @maxs_nofold
+func.func @maxs_nofold() -> index {
+  %lhs = index.constant 1
+  %rhs = index.constant 0x100000000
+  // 32-bit result differs from 64-bit.
+  // CHECK: index.maxs
+  %0 = index.maxs %lhs, %rhs
+  return %0 : index
+}
+
+// CHECK-LABEL: @maxs_edge
+func.func @maxs_edge() -> index {
+  %lhs = index.constant 1
+  %rhs = index.constant 0x100000001
+  // Truncated 64-bit result is the same as 32-bit.
+  // CHECK: %[[A:.*]] = index.constant 429{{[0-9]+}}
+  %0 = index.maxs %lhs, %rhs
+  // CHECK: return %[[A]]
+  return %0 : index
+}
+
+// CHECK-LABEL: @maxu
+func.func @maxu() -> index {
+  %lhs = index.constant -1
+  %rhs = index.constant 1
+  // CHECK: %[[A:.*]] = index.constant -1
+  %0 = index.maxu %lhs, %rhs
+  // CHECK: return %[[A]]
+  return %0 : index
+}
+
+// CHECK-LABEL: @cmp
+func.func @cmp() -> (i1, i1, i1, i1) {
+  %a = index.constant 0
+  %b = index.constant -1
+  %c = index.constant -2
+  %d = index.constant 4
+
+  %0 = index.cmp slt(%a, %b)
+  %1 = index.cmp ugt(%b, %a)
+  %2 = index.cmp ne(%d, %a)
+  %3 = index.cmp sgt(%b, %a)
+
+  // CHECK-DAG: %[[TRUE:.*]] = index.bool.constant true
+  // CHECK-DAG: %[[FALSE:.*]] = index.bool.constant false
+  // CHECK: return %[[FALSE]], %[[TRUE]], %[[TRUE]], %[[FALSE]]
+  return %0, %1, %2, %3 : i1, i1, i1, i1
+}
+
+// CHECK-LABEL: @cmp_nofold
+func.func @cmp_nofold() -> i1 {
+  %lhs = index.constant 1
+  %rhs = index.constant 0x100000000
+  // 32-bit result differs from 64-bit.
+  // CHECK: index.cmp slt
+  %0 = index.cmp slt(%lhs, %rhs)
+  return %0 : i1
+}
+
+// CHECK-LABEL: @cmp_edge
+func.func @cmp_edge() -> i1 {
+  %lhs = index.constant 1
+  %rhs = index.constant 0x100000002
+  // 64-bit result is the same as 32-bit.
+  // CHECK: %[[TRUE:.*]] = index.bool.constant true
+  %0 = index.cmp slt(%lhs, %rhs)
+  // CHECK: return %[[TRUE]]
+  return %0 : i1
+}