Skip to content

Commit

Permalink
[mlir][std] Add SignedCeilDivIOp and SignedFloorDivIOp with std to st…
Browse files Browse the repository at this point in the history
…d lowering triggered by -std-expand-divs option. The new operations support positive/negative nominator/denominator numbers.

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

Signed-off-by: Alexandre Eichenberger <alexe@us.ibm.com>
  • Loading branch information
AlexandreEichenberger committed Nov 4, 2020
1 parent d47300f commit 0795715
Show file tree
Hide file tree
Showing 11 changed files with 613 additions and 0 deletions.
57 changes: 57 additions & 0 deletions mlir/include/mlir/Dialect/StandardOps/IR/Ops.td
Expand Up @@ -2823,6 +2823,63 @@ def SignedDivIOp : IntArithmeticOp<"divi_signed"> {
let hasFolder = 1;
}

//===----------------------------------------------------------------------===//
// SignedFloorDivIOp
//===----------------------------------------------------------------------===//

def SignedFloorDivIOp : IntArithmeticOp<"floordivi_signed"> {
let summary = "signed floor integer division operation";
let description = [{
Syntax:

```
operation ::= ssa-id `=` `floordivi_signed` ssa-use `,` ssa-use `:` type
```

Signed integer division. Rounds towards negative infinity, i.e. `5 / -2 = -3`.

Note: the semantics of division by zero or signed division overflow (minimum
value divided by -1) is TBD; do NOT assume any specific behavior.

Example:

```mlir
// Scalar signed integer division.
%a = floordivi_signed %b, %c : i64

```
}];
let hasFolder = 1;
}

//===----------------------------------------------------------------------===//
// SignedCeilDivIOp
//===----------------------------------------------------------------------===//

def SignedCeilDivIOp : IntArithmeticOp<"ceildivi_signed"> {
let summary = "signed ceil integer division operation";
let description = [{
Syntax:

```
operation ::= ssa-id `=` `ceildivi_signed` ssa-use `,` ssa-use `:` type
```

Signed integer division. Rounds towards positive infinity, i.e. `7 / -2 = -3`.

Note: the semantics of division by zero or signed division overflow (minimum
value divided by -1) is TBD; do NOT assume any specific behavior.

Example:

```mlir
// Scalar signed integer division.
%a = ceildivi_signed %b, %c : i64
```
}];
let hasFolder = 1;
}

//===----------------------------------------------------------------------===//
// SignedRemIOp
//===----------------------------------------------------------------------===//
Expand Down
10 changes: 10 additions & 0 deletions mlir/include/mlir/Dialect/StandardOps/Transforms/Passes.h
Expand Up @@ -41,6 +41,16 @@ std::unique_ptr<Pass> createStdBufferizePass();
/// Creates an instance of func bufferization pass.
std::unique_ptr<Pass> createFuncBufferizePass();

/// Creates an instance of the StdExpandDivs pass that legalizes Std
/// dialect Divs to be convertible to StaLLVMndard. For example,
/// `std.ceildivi_signed` get transformed to a number of std operations,
/// which can be lowered to LLVM.
std::unique_ptr<Pass> createStdExpandDivsPass();

/// Collects a set of patterns to rewrite ops within the Std dialect.
void populateStdExpandDivsRewritePatterns(MLIRContext *context,
OwningRewritePatternList &patterns);

//===----------------------------------------------------------------------===//
// Registration
//===----------------------------------------------------------------------===//
Expand Down
5 changes: 5 additions & 0 deletions mlir/include/mlir/Dialect/StandardOps/Transforms/Passes.td
Expand Up @@ -22,6 +22,11 @@ def StdBufferize : FunctionPass<"std-bufferize"> {
let dependentDialects = ["scf::SCFDialect"];
}

def StdExpandDivs : FunctionPass<"std-expand-divs"> {
let summary = "Legalize div std dialect operations to be convertible to LLVM.";
let constructor = "mlir::createStdExpandDivsPass()";
}

def FuncBufferize : Pass<"func-bufferize", "ModuleOp"> {
let summary = "Bufferize func/call/return ops";
let description = [{
Expand Down
@@ -0,0 +1,82 @@
// RUN: mlir-opt %s -convert-vector-to-scf -lower-affine -convert-scf-to-std -std-expand-divs -convert-vector-to-llvm | \
// RUN: mlir-cpu-runner -e entry -entry-point-result=void \
// RUN: -shared-libs=%mlir_integration_test_dir/libmlir_c_runner_utils%shlibext | \
// RUN: FileCheck %s

func @transfer_read_2d(%A : memref<40xi32>, %base1: index) {
%i42 = constant -42: i32
%f = vector.transfer_read %A[%base1], %i42
{permutation_map = affine_map<(d0) -> (d0)>} :
memref<40xi32>, vector<40xi32>
vector.print %f: vector<40xi32>
return
}

func @entry() {
%c0 = constant 0: index
%c20 = constant 20: i32
%c10 = constant 10: i32
%cmin10 = constant -10: i32
%A = alloc() : memref<40xi32>

// print numerator
affine.for %i = 0 to 40 {
%ii = index_cast %i: index to i32
%ii30 = subi %ii, %c20 : i32
store %ii30, %A[%i] : memref<40xi32>
}
call @transfer_read_2d(%A, %c0) : (memref<40xi32>, index) -> ()

// test with ceil(*, 10)
affine.for %i = 0 to 40 {
%ii = index_cast %i: index to i32
%ii30 = subi %ii, %c20 : i32
%val = ceildivi_signed %ii30, %c10 : i32
store %val, %A[%i] : memref<40xi32>
}
call @transfer_read_2d(%A, %c0) : (memref<40xi32>, index) -> ()

// test with floor(*, 10)
affine.for %i = 0 to 40 {
%ii = index_cast %i: index to i32
%ii30 = subi %ii, %c20 : i32
%val = floordivi_signed %ii30, %c10 : i32
store %val, %A[%i] : memref<40xi32>
}
call @transfer_read_2d(%A, %c0) : (memref<40xi32>, index) -> ()


// test with ceil(*, -10)
affine.for %i = 0 to 40 {
%ii = index_cast %i: index to i32
%ii30 = subi %ii, %c20 : i32
%val = ceildivi_signed %ii30, %cmin10 : i32
store %val, %A[%i] : memref<40xi32>
}
call @transfer_read_2d(%A, %c0) : (memref<40xi32>, index) -> ()

// test with floor(*, -10)
affine.for %i = 0 to 40 {
%ii = index_cast %i: index to i32
%ii30 = subi %ii, %c20 : i32
%val = floordivi_signed %ii30, %cmin10 : i32
store %val, %A[%i] : memref<40xi32>
}
call @transfer_read_2d(%A, %c0) : (memref<40xi32>, index) -> ()

return
}

// List below is aligned for easy manual check
// legend: num, ceil(num, 10), floor(num, 10), ceil(num, -10), floor(num, -10)
// ( -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 )
// ( -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 )
// ( -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
// ( 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 )
// ( 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2 )

// CHECK:( -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 )
// CHECK:( -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 )
// CHECK:( -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
// CHECK:( 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 )
// CHECK:( 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2 )
107 changes: 107 additions & 0 deletions mlir/lib/Dialect/StandardOps/IR/Ops.cpp
Expand Up @@ -2893,6 +2893,113 @@ OpFoldResult SignedDivIOp::fold(ArrayRef<Attribute> operands) {
return overflowOrDiv0 ? Attribute() : result;
}

//===----------------------------------------------------------------------===//
// SignedFloorDivIOp
//===----------------------------------------------------------------------===//

static APInt signedCeilNonnegInputs(APInt a, APInt b, bool &overflow) {
// Returns (a-1)/b + 1
APInt one(a.getBitWidth(), 1, true); // Signed value 1.
APInt val = a.ssub_ov(one, overflow).sdiv_ov(b, overflow);
return val.sadd_ov(one, overflow);
}

OpFoldResult SignedFloorDivIOp::fold(ArrayRef<Attribute> operands) {
assert(operands.size() == 2 && "binary operation takes two operands");

// Don't fold if it would overflow or if it requires a division by zero.
bool overflowOrDiv0 = false;
auto result = constFoldBinaryOp<IntegerAttr>(operands, [&](APInt a, APInt b) {
if (overflowOrDiv0 || !b) {
overflowOrDiv0 = true;
return a;
}
unsigned bits = a.getBitWidth();
APInt zero = APInt::getNullValue(bits);
if (a.sge(zero) && b.sgt(zero)) {
// Both positive (or a is zero), return a / b.
return a.sdiv_ov(b, overflowOrDiv0);
} else if (a.sle(zero) && b.slt(zero)) {
// Both negative (or a is zero), return -a / -b.
APInt posA = zero.ssub_ov(a, overflowOrDiv0);
APInt posB = zero.ssub_ov(b, overflowOrDiv0);
return posA.sdiv_ov(posB, overflowOrDiv0);
} else if (a.slt(zero) && b.sgt(zero)) {
// A is negative, b is positive, return - ceil(-a, b).
APInt posA = zero.ssub_ov(a, overflowOrDiv0);
APInt ceil = signedCeilNonnegInputs(posA, b, overflowOrDiv0);
return zero.ssub_ov(ceil, overflowOrDiv0);
} else {
// A is positive, b is negative, return - ceil(a, -b).
APInt posB = zero.ssub_ov(b, overflowOrDiv0);
APInt ceil = signedCeilNonnegInputs(a, posB, overflowOrDiv0);
return zero.ssub_ov(ceil, overflowOrDiv0);
}
});

// Fold out floor division by one. Assumes all tensors of all ones are
// splats.
if (auto rhs = operands[1].dyn_cast_or_null<IntegerAttr>()) {
if (rhs.getValue() == 1)
return lhs();
} else if (auto rhs = operands[1].dyn_cast_or_null<SplatElementsAttr>()) {
if (rhs.getSplatValue<IntegerAttr>().getValue() == 1)
return lhs();
}

return overflowOrDiv0 ? Attribute() : result;
}

//===----------------------------------------------------------------------===//
// SignedCeilDivIOp
//===----------------------------------------------------------------------===//

OpFoldResult SignedCeilDivIOp::fold(ArrayRef<Attribute> operands) {
assert(operands.size() == 2 && "binary operation takes two operands");

// Don't fold if it would overflow or if it requires a division by zero.
bool overflowOrDiv0 = false;
auto result = constFoldBinaryOp<IntegerAttr>(operands, [&](APInt a, APInt b) {
if (overflowOrDiv0 || !b) {
overflowOrDiv0 = true;
return a;
}
unsigned bits = a.getBitWidth();
APInt zero = APInt::getNullValue(bits);
if (a.sgt(zero) && b.sgt(zero)) {
// Both positive, return ceil(a, b).
return signedCeilNonnegInputs(a, b, overflowOrDiv0);
} else if (a.slt(zero) && b.slt(zero)) {
// Both negative, return ceil(-a, -b).
APInt posA = zero.ssub_ov(a, overflowOrDiv0);
APInt posB = zero.ssub_ov(b, overflowOrDiv0);
return signedCeilNonnegInputs(posA, posB, overflowOrDiv0);
} else if (a.slt(zero) && b.sgt(zero)) {
// A is negative, b is positive, return - ( -a / b).
APInt posA = zero.ssub_ov(a, overflowOrDiv0);
APInt div = posA.sdiv_ov(b, overflowOrDiv0);
return zero.ssub_ov(div, overflowOrDiv0);
} else {
// A is positive (or zero), b is negative, return - (a / -b).
APInt posB = zero.ssub_ov(b, overflowOrDiv0);
APInt div = a.sdiv_ov(posB, overflowOrDiv0);
return zero.ssub_ov(div, overflowOrDiv0);
}
});

// Fold out floor division by one. Assumes all tensors of all ones are
// splats.
if (auto rhs = operands[1].dyn_cast_or_null<IntegerAttr>()) {
if (rhs.getValue() == 1)
return lhs();
} else if (auto rhs = operands[1].dyn_cast_or_null<SplatElementsAttr>()) {
if (rhs.getSplatValue<IntegerAttr>().getValue() == 1)
return lhs();
}

return overflowOrDiv0 ? Attribute() : result;
}

//===----------------------------------------------------------------------===//
// SignedRemIOp
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions mlir/lib/Dialect/StandardOps/Transforms/CMakeLists.txt
Expand Up @@ -5,6 +5,7 @@ add_mlir_dialect_library(MLIRStandardOpsTransforms
ExpandTanh.cpp
FuncBufferize.cpp
FuncConversions.cpp
StdExpandDivs.cpp

ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/StandardOps/Transforms
Expand Down

0 comments on commit 0795715

Please sign in to comment.