Files
clang-p2996/mlir/test/Transforms/canonicalize-dce.mlir
Giuseppe Rossini 441b672bbd [mlir] Fix block merging (#102038)
With this PR I am trying to address:
https://github.com/llvm/llvm-project/issues/63230.

What changed:
- While merging identical blocks, don't add a block argument if it is
"identical" to another block argument. I.e., if the two block arguments
refer to the same `Value`. The operations operands in the block will
point to the argument we already inserted. This needs to happen to all
the arguments we pass to the different successors of the parent block
- After merged the blocks, get rid of "unnecessary" arguments. I.e., if
all the predecessors pass the same block argument, there is no need to
pass it as an argument.
- This last simplification clashed with
`BufferDeallocationSimplification`. The reason, I think, is that the two
simplifications are clashing. I.e., `BufferDeallocationSimplification`
contains an analysis based on the block structure. If we simplify the
block structure (by merging and/or dropping block arguments) the
analysis is invalid . The solution I found is to do a more prudent
simplification when running that pass.

**Note-1**: I ran all the integration tests
(`-DMLIR_INCLUDE_INTEGRATION_TESTS=ON`) and they passed.
**Note-2**: I fixed a bug found by @Dinistro in #97697 . The issue was
that, when looking for redundant arguments, I was not considering that
the block might have already some arguments. So the index (in the block
args list) of the i-th `newArgument` is `i+numOfOldArguments`.
2024-08-07 09:10:01 +01:00

190 lines
4.2 KiB
MLIR

// RUN: mlir-opt -allow-unregistered-dialect %s -split-input-file -pass-pipeline='builtin.module(func.func(canonicalize{region-simplify=aggressive}))' | FileCheck %s
// Test case: Simple case of deleting a dead pure op.
// CHECK: func @f(%arg0: f32) {
// CHECK-NEXT: return
func.func @f(%arg0: f32) {
%0 = "arith.addf"(%arg0, %arg0) : (f32, f32) -> f32
return
}
// -----
// Test case: Simple case of deleting a block argument.
// CHECK: func @f(%arg0: f32)
// CHECK-NEXT: "test.br"()[^bb1]
// CHECK-NEXT: ^bb1:
// CHECK-NEXT: return
func.func @f(%arg0: f32) {
"test.br"(%arg0)[^succ] : (f32) -> ()
^succ(%0: f32):
return
}
// -----
// Test case: Deleting recursively dead block arguments.
// CHECK: func @f(%arg0: f32)
// CHECK-NEXT: cf.br ^bb1
// CHECK-NEXT: ^bb1:
// CHECK-NEXT: cf.br ^bb1
func.func @f(%arg0: f32) {
cf.br ^loop(%arg0: f32)
^loop(%loop: f32):
cf.br ^loop(%loop: f32)
}
// -----
// Test case: Deleting recursively dead block arguments with pure ops in between.
// CHECK: func @f(%arg0: f32)
// CHECK-NEXT: cf.br ^bb1
// CHECK-NEXT: ^bb1:
// CHECK-NEXT: cf.br ^bb1
func.func @f(%arg0: f32) {
cf.br ^loop(%arg0: f32)
^loop(%0: f32):
%1 = "math.exp"(%0) : (f32) -> f32
cf.br ^loop(%1: f32)
}
// -----
// Test case: Delete block arguments for cf.cond_br.
// CHECK: func @f(%arg0: f32, %arg1: i1)
// CHECK-NEXT: return
func.func @f(%arg0: f32, %pred: i1) {
%exp = "math.exp"(%arg0) : (f32) -> f32
cf.cond_br %pred, ^true(%exp: f32), ^false(%exp: f32)
^true(%0: f32):
return
^false(%1: f32):
return
}
// -----
// Test case: Recursively DCE into enclosed regions.
// CHECK: func.func @f(%arg0: f32)
// CHECK-NOT: arith.addf
func.func @f(%arg0: f32) {
"test.region"() (
{
%0 = "arith.addf"(%arg0, %arg0) : (f32, f32) -> f32
}
) : () -> ()
return
}
// -----
// Test case: Don't delete pure ops that feed into returns.
// CHECK: func @f(%arg0: f32) -> f32
// CHECK-NEXT: [[VAL0:%.+]] = arith.addf %arg0, %arg0 : f32
// CHECK-NEXT: return [[VAL0]] : f32
func.func @f(%arg0: f32) -> f32 {
%0 = "arith.addf"(%arg0, %arg0) : (f32, f32) -> f32
return %0 : f32
}
// -----
// Test case: Don't delete potentially side-effecting ops.
// CHECK: func @f(%arg0: f32)
// CHECK-NEXT: "foo.print"(%arg0) : (f32) -> ()
// CHECK-NEXT: return
func.func @f(%arg0: f32) {
"foo.print"(%arg0) : (f32) -> ()
return
}
// -----
// Test case: Uses in nested regions are deleted correctly.
// CHECK: func @f(%arg0: f32)
// CHECK-NEXT: "foo.has_region"
// CHECK-NEXT: "foo.return"
func.func @f(%arg0: f32) {
%0 = "math.exp"(%arg0) : (f32) -> f32
"foo.has_region"() ({
%1 = "math.exp"(%0) : (f32) -> f32
"foo.return"() : () -> ()
}) : () -> ()
return
}
// -----
// Test case: Test the mechanics of deleting multiple block arguments.
// CHECK: func @f(%arg0: tensor<1xf32>, %arg1: tensor<2xf32>, %arg2: tensor<3xf32>, %arg3: tensor<4xf32>, %arg4: tensor<5xf32>)
// CHECK-NEXT: "test.br"()[^bb1]
// CHECK-NEXT: ^bb1:
// CHECK-NEXT: "foo.print"(%arg1)
// CHECK-NEXT: "foo.print"(%arg3)
// CHECK-NEXT: return
func.func @f(
%arg0: tensor<1xf32>,
%arg1: tensor<2xf32>,
%arg2: tensor<3xf32>,
%arg3: tensor<4xf32>,
%arg4: tensor<5xf32>) {
"test.br"(%arg0, %arg1, %arg2, %arg3, %arg4)[^succ] : (tensor<1xf32>, tensor<2xf32>, tensor<3xf32>, tensor<4xf32>, tensor<5xf32>) -> ()
^succ(%t1: tensor<1xf32>, %t2: tensor<2xf32>, %t3: tensor<3xf32>, %t4: tensor<4xf32>, %t5: tensor<5xf32>):
"foo.print"(%t2) : (tensor<2xf32>) -> ()
"foo.print"(%t4) : (tensor<4xf32>) -> ()
return
}
// -----
// Test case: Test values with use-def cycles are deleted properly.
// CHECK: func @f()
// CHECK-NEXT: test.graph_region
// CHECK-NEXT: "test.terminator"() : () -> ()
func.func @f() {
test.graph_region {
%0 = "math.exp"(%1) : (f32) -> f32
%1 = "math.exp"(%0) : (f32) -> f32
"test.terminator"() : ()->()
}
return
}
// -----
// Test case: Delete ops that only have side-effects on an allocated result.
// CHECK: func @f()
// CHECK-NOT: test_effects_result
// CHECK-NEXT: return
func.func @f() {
%0 = "test.test_effects_result"() : () -> i32
return
}