Files
clang-p2996/mlir/test/Transforms/inlining.mlir
Christian Ulmann 6f092e501b [MLIR][Transforms] Update block arg locations during inlining (#106064)
This commit changes the inlining to also update the locations of block
arguments. Not updating these locations leads to LLVM IR verification
issues when exporting converted block arguments to phi nodes. This lack
of location update was not visible due to ignoring the argument
locations until recently.
Relevant change: https://github.com/llvm/llvm-project/pull/105534
2024-08-26 15:23:39 +02:00

320 lines
11 KiB
MLIR

// RUN: mlir-opt %s -inline='default-pipeline=''' | FileCheck %s
// RUN: mlir-opt %s --mlir-disable-threading -inline='default-pipeline=''' | FileCheck %s
// RUN: mlir-opt %s -inline='default-pipeline=''' -mlir-print-debuginfo -mlir-print-local-scope | FileCheck %s --check-prefix INLINE-LOC
// RUN: mlir-opt %s -inline | FileCheck %s --check-prefix INLINE_SIMPLIFY
// RUN: mlir-opt %s -inline='op-pipelines=func.func(canonicalize,cse)' | FileCheck %s --check-prefix INLINE_SIMPLIFY
// Inline a function that takes an argument.
func.func @func_with_arg(%c : i32) -> i32 {
%b = arith.addi %c, %c : i32
return %b : i32
}
// CHECK-LABEL: func @inline_with_arg
func.func @inline_with_arg(%arg0 : i32) -> i32 {
// CHECK-NEXT: arith.addi
// CHECK-NEXT: return
%0 = call @func_with_arg(%arg0) : (i32) -> i32
return %0 : i32
}
// Inline a function that has multiple return operations.
func.func @func_with_multi_return(%a : i1) -> (i32) {
cf.cond_br %a, ^bb1, ^bb2
^bb1:
%const_0 = arith.constant 0 : i32
return %const_0 : i32
^bb2:
%const_55 = arith.constant 55 : i32
return %const_55 : i32
}
// CHECK-LABEL: func @inline_with_multi_return() -> i32
func.func @inline_with_multi_return() -> i32 {
// CHECK-NEXT: [[VAL_7:%.*]] = arith.constant false
// CHECK-NEXT: cf.cond_br [[VAL_7]], ^bb1, ^bb2
// CHECK: ^bb1:
// CHECK-NEXT: [[VAL_8:%.*]] = arith.constant 0 : i32
// CHECK-NEXT: cf.br ^bb3([[VAL_8]] : i32)
// CHECK: ^bb2:
// CHECK-NEXT: [[VAL_9:%.*]] = arith.constant 55 : i32
// CHECK-NEXT: cf.br ^bb3([[VAL_9]] : i32)
// CHECK: ^bb3([[VAL_10:%.*]]: i32):
// CHECK-NEXT: return [[VAL_10]] : i32
%false = arith.constant false
%x = call @func_with_multi_return(%false) : (i1) -> i32
return %x : i32
}
// Check that location information is updated for inlined instructions.
func.func @func_with_locations(%c : i32) -> i32 {
%b = arith.addi %c, %c : i32 loc("mysource.cc":10:8)
return %b : i32 loc("mysource.cc":11:2)
}
// INLINE-LOC-LABEL: func @inline_with_locations
func.func @inline_with_locations(%arg0 : i32) -> i32 {
// INLINE-LOC-NEXT: arith.addi %{{.*}}, %{{.*}} : i32 loc(callsite("mysource.cc":10:8 at "mysource.cc":55:14))
// INLINE-LOC-NEXT: return
%0 = call @func_with_locations(%arg0) : (i32) -> i32 loc("mysource.cc":55:14)
return %0 : i32
}
// Check that external function declarations are not inlined.
func.func private @func_external()
// CHECK-LABEL: func @no_inline_external
func.func @no_inline_external() {
// CHECK-NEXT: call @func_external()
call @func_external() : () -> ()
return
}
// Check that multiple levels of calls will be inlined.
func.func @multilevel_func_a() {
return
}
func.func @multilevel_func_b() {
call @multilevel_func_a() : () -> ()
return
}
// CHECK-LABEL: func @inline_multilevel
func.func @inline_multilevel() {
// CHECK-NOT: call
%fn = "test.functional_region_op"() ({
call @multilevel_func_b() : () -> ()
"test.return"() : () -> ()
}) : () -> (() -> ())
call_indirect %fn() : () -> ()
return
}
// Check that recursive calls are not inlined.
// CHECK-LABEL: func @no_inline_recursive
func.func @no_inline_recursive() {
// CHECK: test.functional_region_op
// CHECK-NOT: test.functional_region_op
%fn = "test.functional_region_op"() ({
call @no_inline_recursive() : () -> ()
"test.return"() : () -> ()
}) : () -> (() -> ())
return
}
// Check that we can convert types for inputs and results as necessary.
func.func @convert_callee_fn(%arg : i32) -> i32 {
return %arg : i32
}
func.func @convert_callee_fn_multi_arg(%a : i32, %b : i32) -> () {
return
}
func.func @convert_callee_fn_multi_res() -> (i32, i32) {
%res = arith.constant 0 : i32
return %res, %res : i32, i32
}
// CHECK-LABEL: func @inline_convert_call
func.func @inline_convert_call() -> i16 {
// CHECK: %[[INPUT:.*]] = arith.constant
%test_input = arith.constant 0 : i16
// CHECK: %[[CAST_INPUT:.*]] = "test.cast"(%[[INPUT]]) : (i16) -> i32
// CHECK: %[[CAST_RESULT:.*]] = "test.cast"(%[[CAST_INPUT]]) : (i32) -> i16
// CHECK-NEXT: return %[[CAST_RESULT]]
%res = "test.conversion_call_op"(%test_input) { callee=@convert_callee_fn } : (i16) -> (i16)
return %res : i16
}
func.func @convert_callee_fn_multiblock() -> i32 {
cf.br ^bb0
^bb0:
%0 = arith.constant 0 : i32
return %0 : i32
}
// CHECK-LABEL: func @inline_convert_result_multiblock
func.func @inline_convert_result_multiblock() -> i16 {
// CHECK: cf.br ^bb1 {inlined_conversion}
// CHECK: ^bb1:
// CHECK: %[[C:.+]] = arith.constant {inlined_conversion} 0 : i32
// CHECK: cf.br ^bb2(%[[C]] : i32)
// CHECK: ^bb2(%[[BBARG:.+]]: i32):
// CHECK: %[[CAST_RESULT:.+]] = "test.cast"(%[[BBARG]]) : (i32) -> i16
// CHECK: return %[[CAST_RESULT]] : i16
%res = "test.conversion_call_op"() { callee=@convert_callee_fn_multiblock } : () -> (i16)
return %res : i16
}
// CHECK-LABEL: func @no_inline_convert_call
func.func @no_inline_convert_call() {
// CHECK: "test.conversion_call_op"
%test_input_i16 = arith.constant 0 : i16
%test_input_i64 = arith.constant 0 : i64
"test.conversion_call_op"(%test_input_i16, %test_input_i64) { callee=@convert_callee_fn_multi_arg } : (i16, i64) -> ()
// CHECK: "test.conversion_call_op"
%res_2:2 = "test.conversion_call_op"() { callee=@convert_callee_fn_multi_res } : () -> (i16, i64)
return
}
// Check that we properly simplify when inlining.
func.func @simplify_return_constant() -> i32 {
%res = arith.constant 0 : i32
return %res : i32
}
func.func @simplify_return_reference() -> (() -> i32) {
%res = constant @simplify_return_constant : () -> i32
return %res : () -> i32
}
// INLINE_SIMPLIFY-LABEL: func @inline_simplify
func.func @inline_simplify() -> i32 {
// INLINE_SIMPLIFY-NEXT: %[[CST:.*]] = arith.constant 0 : i32
// INLINE_SIMPLIFY-NEXT: return %[[CST]]
%fn = call @simplify_return_reference() : () -> (() -> i32)
%res = call_indirect %fn() : () -> i32
return %res : i32
}
// CHECK-LABEL: func @no_inline_invalid_call
func.func @no_inline_invalid_call() -> i32 {
%res = "test.conversion_call_op"() { callee=@convert_callee_fn_multiblock, noinline } : () -> (i32)
return %res : i32
}
func.func @gpu_alloc() -> memref<1024xf32> {
%m = gpu.alloc [] () : memref<1024xf32>
return %m : memref<1024xf32>
}
// CHECK-LABEL: func @inline_gpu_ops
func.func @inline_gpu_ops() -> memref<1024xf32> {
// CHECK-NEXT: gpu.alloc
%m = call @gpu_alloc() : () -> memref<1024xf32>
return %m : memref<1024xf32>
}
// Test block arguments location propagation.
// Use two call-sites to force cloning.
func.func @func_with_block_args_location(%arg0 : i32) {
cf.br ^bb1(%arg0 : i32)
^bb1(%x : i32 loc("foo")):
"test.foo" (%x) : (i32) -> () loc("bar")
return
}
// INLINE-LOC-LABEL: func @func_with_block_args_location_callee1
// INLINE-LOC: cf.br
// INLINE-LOC: ^bb{{[0-9]+}}(%{{.*}}: i32 loc(callsite("foo" at "bar"))
func.func @func_with_block_args_location_callee1(%arg0 : i32) {
call @func_with_block_args_location(%arg0) : (i32) -> () loc("bar")
return
}
// CHECK-LABEL: func @func_with_block_args_location_callee2
func.func @func_with_block_args_location_callee2(%arg0 : i32) {
call @func_with_block_args_location(%arg0) : (i32) -> ()
return
}
func.func @func_with_multiple_blocks(%arg0 : i32) {
cf.br ^bb1(%arg0 : i32)
^bb1(%x : i32):
"test.foo" (%x) : (i32) -> () loc("bar")
return
}
// CHECK-LABEL: func @func_with_multiple_blocks_callee1
func.func @func_with_multiple_blocks_callee1(%arg0 : i32) {
"test.dummy_op"() ({
// Call cannot be inlined because "test.dummy" may not support unstructured
// control flow in its body.
// CHECK: call @func_with_multiple_blocks
call @func_with_multiple_blocks(%arg0) : (i32) -> ()
"test.terminator"() : () -> ()
}) : () -> ()
return
}
// CHECK-LABEL: func @func_with_multiple_blocks_callee2
func.func @func_with_multiple_blocks_callee2(%arg0 : i32, %c : i1) {
%0 = scf.while (%arg1 = %arg0) : (i32) -> (i32) {
// Call cannot be inlined because scf.while does not support unstructured
// control flow in its body.
// CHECK: call @func_with_multiple_blocks
func.call @func_with_multiple_blocks(%arg0) : (i32) -> ()
scf.condition(%c) %arg1 : i32
} do {
^bb0(%arg1: i32):
scf.yield %arg1 : i32
}
return
}
// Check that we can handle argument and result attributes.
test.conversion_func_op @handle_attr_callee_fn_multi_arg(%arg0 : i16, %arg1 : i16 {"test.handle_argument"}) -> (i16 {"test.handle_result"}, i16) {
%0 = arith.addi %arg0, %arg1 : i16
%1 = arith.subi %arg0, %arg1 : i16
"test.return"(%0, %1) : (i16, i16) -> ()
}
test.conversion_func_op @handle_attr_callee_fn(%arg0 : i32 {"test.handle_argument"}) -> (i32 {"test.handle_result"}) {
"test.return"(%arg0) : (i32) -> ()
}
// CHECK-LABEL: func @inline_handle_attr_call
// CHECK-SAME: %[[ARG0:[a-zA-Z0-9]+]]
// CHECK-SAME: %[[ARG1:[a-zA-Z0-9]+]]
func.func @inline_handle_attr_call(%arg0 : i16, %arg1 : i16) -> (i16, i16) {
// CHECK: %[[CHANGE_INPUT:.*]] = "test.type_changer"(%[[ARG1]]) : (i16) -> i16
// CHECK: %[[SUM:.*]] = arith.addi %[[ARG0]], %[[CHANGE_INPUT]]
// CHECK: %[[DIFF:.*]] = arith.subi %[[ARG0]], %[[CHANGE_INPUT]]
// CHECK: %[[CHANGE_RESULT:.*]] = "test.type_changer"(%[[SUM]]) : (i16) -> i16
// CHECK-NEXT: return %[[CHANGE_RESULT]], %[[DIFF]]
%res0, %res1 = "test.conversion_call_op"(%arg0, %arg1) { callee=@handle_attr_callee_fn_multi_arg } : (i16, i16) -> (i16, i16)
return %res0, %res1 : i16, i16
}
// CHECK-LABEL: func @inline_convert_and_handle_attr_call
// CHECK-SAME: %[[ARG0:[a-zA-Z0-9]+]]
func.func @inline_convert_and_handle_attr_call(%arg0 : i16) -> (i16) {
// CHECK: %[[CAST_INPUT:.*]] = "test.cast"(%[[ARG0]]) : (i16) -> i32
// CHECK: %[[CHANGE_INPUT:.*]] = "test.type_changer"(%[[CAST_INPUT]]) : (i32) -> i32
// CHECK: %[[CHANGE_RESULT:.*]] = "test.type_changer"(%[[CHANGE_INPUT]]) : (i32) -> i32
// CHECK: %[[CAST_RESULT:.*]] = "test.cast"(%[[CHANGE_RESULT]]) : (i32) -> i16
// CHECK: return %[[CAST_RESULT]]
%res = "test.conversion_call_op"(%arg0) { callee=@handle_attr_callee_fn } : (i16) -> (i16)
return %res : i16
}
// Check a function with complex ops is inlined.
func.func @double_square_complex(%cplx: complex<f32>) -> complex<f32> {
%double = complex.add %cplx, %cplx : complex<f32>
%square = complex.mul %double, %double : complex<f32>
return %square : complex<f32>
}
// CHECK-LABEL: func @inline_with_complex_ops
func.func @inline_with_complex_ops() -> complex<f32> {
%c1 = arith.constant 1.0 : f32
%c2 = arith.constant 2.0 : f32
%c = complex.create %c1, %c2 : complex<f32>
// CHECK: complex.add
// CHECK: complex.mul
// CHECK-NOT: call
%r = call @double_square_complex(%c) : (complex<f32>) -> (complex<f32>)
return %r : complex<f32>
}