llvm/mlir/test/Transforms/canonicalize-dce.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
}