When `RegionBranchOpInterface::getSuccessorRegions` is called for anything other than the parent op, it expects the operands of the terminator of the source region to be passed, not the operands of the parent op. This was not always respected. This fixes a bug in integer range inference and ForwardDataFlowSolver and changes `scf.while` to allow narrowing of successors using constant inputs. Fixes #55873 Reviewed By: mehdi_amini, krzysz00 Differential Revision: https://reviews.llvm.org/D127261
575 lines
16 KiB
MLIR
575 lines
16 KiB
MLIR
// RUN: mlir-opt -allow-unregistered-dialect %s -split-input-file -verify-diagnostics
|
|
|
|
func.func @loop_for_lb(%arg0: f32, %arg1: index) {
|
|
// expected-error@+1 {{operand #0 must be index}}
|
|
"scf.for"(%arg0, %arg1, %arg1) ({}) : (f32, index, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_for_ub(%arg0: f32, %arg1: index) {
|
|
// expected-error@+1 {{operand #1 must be index}}
|
|
"scf.for"(%arg1, %arg0, %arg1) ({}) : (index, f32, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_for_step(%arg0: f32, %arg1: index) {
|
|
// expected-error@+1 {{operand #2 must be index}}
|
|
"scf.for"(%arg1, %arg1, %arg0) ({}) : (index, index, f32) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_for_step_positive(%arg0: index) {
|
|
// expected-error@+2 {{constant step operand must be positive}}
|
|
%c0 = arith.constant 0 : index
|
|
"scf.for"(%arg0, %arg0, %c0) ({
|
|
^bb0(%arg1: index):
|
|
scf.yield
|
|
}) : (index, index, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_for_one_region(%arg0: index) {
|
|
// expected-error@+1 {{requires one region}}
|
|
"scf.for"(%arg0, %arg0, %arg0) (
|
|
{scf.yield},
|
|
{scf.yield}
|
|
) : (index, index, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_for_single_block(%arg0: index) {
|
|
// expected-error@+1 {{expects region #0 to have 0 or 1 blocks}}
|
|
"scf.for"(%arg0, %arg0, %arg0) (
|
|
{
|
|
^bb1:
|
|
scf.yield
|
|
^bb2:
|
|
scf.yield
|
|
}
|
|
) : (index, index, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_for_single_index_argument(%arg0: index) {
|
|
// expected-error@+1 {{op expected body first argument to be an index argument for the induction variable}}
|
|
"scf.for"(%arg0, %arg0, %arg0) (
|
|
{
|
|
^bb0(%i0 : f32):
|
|
scf.yield
|
|
}
|
|
) : (index, index, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_if_not_i1(%arg0: index) {
|
|
// expected-error@+1 {{operand #0 must be 1-bit signless integer}}
|
|
"scf.if"(%arg0) ({}, {}) : (index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_if_more_than_2_regions(%arg0: i1) {
|
|
// expected-error@+1 {{expected 2 regions}}
|
|
"scf.if"(%arg0) ({}, {}, {}): (i1) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_if_not_one_block_per_region(%arg0: i1) {
|
|
// expected-error@+1 {{expects region #0 to have 0 or 1 blocks}}
|
|
"scf.if"(%arg0) ({
|
|
^bb0:
|
|
scf.yield
|
|
^bb1:
|
|
scf.yield
|
|
}, {}): (i1) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @loop_if_illegal_block_argument(%arg0: i1) {
|
|
// expected-error@+1 {{region #0 should have no arguments}}
|
|
"scf.if"(%arg0) ({
|
|
^bb0(%0 : index):
|
|
scf.yield
|
|
}, {}): (i1) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_arguments_different_tuple_size(
|
|
%arg0: index, %arg1: index, %arg2: index) {
|
|
// expected-error@+1 {{custom op 'scf.parallel' expected 1 operands}}
|
|
scf.parallel (%i0) = (%arg0) to (%arg1, %arg2) step () {
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_body_arguments_wrong_type(
|
|
%arg0: index, %arg1: index, %arg2: index) {
|
|
// expected-error@+1 {{'scf.parallel' op expects arguments for the induction variable to be of index type}}
|
|
"scf.parallel"(%arg0, %arg1, %arg2) ({
|
|
^bb0(%i0: f32):
|
|
scf.yield
|
|
}) {operand_segment_sizes = dense<[1, 1, 1, 0]>: vector<4xi32>}: (index, index, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_body_wrong_number_of_arguments(
|
|
%arg0: index, %arg1: index, %arg2: index) {
|
|
// expected-error@+1 {{'scf.parallel' op expects the same number of induction variables: 2 as bound and step values: 1}}
|
|
"scf.parallel"(%arg0, %arg1, %arg2) ({
|
|
^bb0(%i0: index, %i1: index):
|
|
scf.yield
|
|
}) {operand_segment_sizes = dense<[1, 1, 1, 0]>: vector<4xi32>}: (index, index, index) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_no_tuple_elements() {
|
|
// expected-error@+1 {{'scf.parallel' op needs at least one tuple element for lowerBound, upperBound and step}}
|
|
scf.parallel () = () to () step () {
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_step_not_positive(
|
|
%arg0: index, %arg1: index, %arg2: index, %arg3: index) {
|
|
// expected-error@+3 {{constant step operand must be positive}}
|
|
%c0 = arith.constant 1 : index
|
|
%c1 = arith.constant 0 : index
|
|
scf.parallel (%i0, %i1) = (%arg0, %arg1) to (%arg2, %arg3) step (%c0, %c1) {
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_fewer_results_than_reduces(
|
|
%arg0 : index, %arg1: index, %arg2: index) {
|
|
// expected-error@+1 {{expects number of results: 0 to be the same as number of reductions: 1}}
|
|
scf.parallel (%i0) = (%arg0) to (%arg1) step (%arg2) {
|
|
%c0 = arith.constant 1.0 : f32
|
|
scf.reduce(%c0) : f32 {
|
|
^bb0(%lhs: f32, %rhs: f32):
|
|
scf.reduce.return %lhs : f32
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_more_results_than_reduces(
|
|
%arg0 : index, %arg1 : index, %arg2 : index) {
|
|
// expected-error@+2 {{expects number of results: 1 to be the same as number of reductions: 0}}
|
|
%zero = arith.constant 1.0 : f32
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg1) step (%arg2) init (%zero) -> f32 {
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_more_results_than_initial_values(
|
|
%arg0 : index, %arg1: index, %arg2: index) {
|
|
// expected-error@+1 {{'scf.parallel' 0 operands present, but expected 1}}
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg1) step (%arg2) -> f32 {
|
|
scf.reduce(%arg0) : index {
|
|
^bb0(%lhs: index, %rhs: index):
|
|
scf.reduce.return %lhs : index
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @parallel_different_types_of_results_and_reduces(
|
|
%arg0 : index, %arg1: index, %arg2: index) {
|
|
%zero = arith.constant 0.0 : f32
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg1)
|
|
step (%arg2) init (%zero) -> f32 {
|
|
// expected-error@+1 {{expects type of reduce: 'index' to be the same as result type: 'f32'}}
|
|
scf.reduce(%arg0) : index {
|
|
^bb0(%lhs: index, %rhs: index):
|
|
scf.reduce.return %lhs : index
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @top_level_reduce(%arg0 : f32) {
|
|
// expected-error@+1 {{expects parent op 'scf.parallel'}}
|
|
scf.reduce(%arg0) : f32 {
|
|
^bb0(%lhs : f32, %rhs : f32):
|
|
scf.reduce.return %lhs : f32
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @reduce_empty_block(%arg0 : index, %arg1 : f32) {
|
|
%zero = arith.constant 0.0 : f32
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg0)
|
|
step (%arg0) init (%zero) -> f32 {
|
|
// expected-error@+1 {{empty block: expect at least a terminator}}
|
|
scf.reduce(%arg1) : f32 {
|
|
^bb0(%lhs : f32, %rhs : f32):
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @reduce_too_many_args(%arg0 : index, %arg1 : f32) {
|
|
%zero = arith.constant 0.0 : f32
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg0)
|
|
step (%arg0) init (%zero) -> f32 {
|
|
// expected-error@+1 {{expects two arguments to reduce block of type 'f32'}}
|
|
scf.reduce(%arg1) : f32 {
|
|
^bb0(%lhs : f32, %rhs : f32, %other : f32):
|
|
scf.reduce.return %lhs : f32
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @reduce_wrong_args(%arg0 : index, %arg1 : f32) {
|
|
%zero = arith.constant 0.0 : f32
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg0)
|
|
step (%arg0) init (%zero) -> f32 {
|
|
// expected-error@+1 {{expects two arguments to reduce block of type 'f32'}}
|
|
scf.reduce(%arg1) : f32 {
|
|
^bb0(%lhs : f32, %rhs : i32):
|
|
scf.reduce.return %lhs : f32
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
|
|
// -----
|
|
|
|
func.func @reduce_wrong_terminator(%arg0 : index, %arg1 : f32) {
|
|
%zero = arith.constant 0.0 : f32
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg0)
|
|
step (%arg0) init (%zero) -> f32 {
|
|
// expected-error@+1 {{the block inside reduce should be terminated with a 'scf.reduce.return' op}}
|
|
scf.reduce(%arg1) : f32 {
|
|
^bb0(%lhs : f32, %rhs : f32):
|
|
"test.finish" () : () -> ()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @reduceReturn_wrong_type(%arg0 : index, %arg1: f32) {
|
|
%zero = arith.constant 0.0 : f32
|
|
%res = scf.parallel (%i0) = (%arg0) to (%arg0)
|
|
step (%arg0) init (%zero) -> f32 {
|
|
scf.reduce(%arg1) : f32 {
|
|
^bb0(%lhs : f32, %rhs : f32):
|
|
%c0 = arith.constant 1 : index
|
|
// expected-error@+1 {{needs to have type 'f32' (the type of the enclosing ReduceOp)}}
|
|
scf.reduce.return %c0 : index
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @reduceReturn_not_inside_reduce(%arg0 : f32) {
|
|
"foo.region"() ({
|
|
// expected-error@+1 {{expects parent op 'scf.reduce'}}
|
|
scf.reduce.return %arg0 : f32
|
|
}): () -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @std_if_incorrect_yield(%arg0: i1, %arg1: f32)
|
|
{
|
|
// expected-error@+1 {{region control flow edge from Region #0 to parent results: source has 1 operands, but target successor needs 2}}
|
|
%x, %y = scf.if %arg0 -> (f32, f32) {
|
|
%0 = arith.addf %arg1, %arg1 : f32
|
|
scf.yield %0 : f32
|
|
} else {
|
|
%0 = arith.subf %arg1, %arg1 : f32
|
|
scf.yield %0, %0 : f32, f32
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @std_if_missing_else(%arg0: i1, %arg1: f32)
|
|
{
|
|
// expected-error@+1 {{must have an else block if defining values}}
|
|
%x = scf.if %arg0 -> (f32) {
|
|
%0 = arith.addf %arg1, %arg1 : f32
|
|
scf.yield %0 : f32
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @std_for_operands_mismatch(%arg0 : index, %arg1 : index, %arg2 : index) {
|
|
%s0 = arith.constant 0.0 : f32
|
|
%t0 = arith.constant 1 : i32
|
|
// expected-error@+1 {{mismatch in number of loop-carried values and defined values}}
|
|
%result1:3 = scf.for %i0 = %arg0 to %arg1 step %arg2
|
|
iter_args(%si = %s0, %ti = %t0) -> (f32, i32, f32) {
|
|
%sn = arith.addf %si, %si : f32
|
|
%tn = arith.addi %ti, %ti : i32
|
|
scf.yield %sn, %tn, %sn : f32, i32, f32
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @std_for_operands_mismatch_2(%arg0 : index, %arg1 : index, %arg2 : index) {
|
|
%s0 = arith.constant 0.0 : f32
|
|
%t0 = arith.constant 1 : i32
|
|
%u0 = arith.constant 1.0 : f32
|
|
// expected-error@+1 {{mismatch in number of loop-carried values and defined values}}
|
|
%result1:2 = scf.for %i0 = %arg0 to %arg1 step %arg2
|
|
iter_args(%si = %s0, %ti = %t0, %ui = %u0) -> (f32, i32) {
|
|
%sn = arith.addf %si, %si : f32
|
|
%tn = arith.addi %ti, %ti : i32
|
|
%un = arith.subf %ui, %ui : f32
|
|
scf.yield %sn, %tn, %un : f32, i32, f32
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @std_for_operands_mismatch_3(%arg0 : index, %arg1 : index, %arg2 : index) {
|
|
// expected-note@+1 {{prior use here}}
|
|
%s0 = arith.constant 0.0 : f32
|
|
%t0 = arith.constant 1.0 : f32
|
|
// expected-error@+2 {{expects different type than prior uses: 'i32' vs 'f32'}}
|
|
%result1:2 = scf.for %i0 = %arg0 to %arg1 step %arg2
|
|
iter_args(%si = %s0, %ti = %t0) -> (i32, i32) {
|
|
%sn = arith.addf %si, %si : i32
|
|
%tn = arith.addf %ti, %ti : i32
|
|
scf.yield %sn, %tn : i32, i32
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @std_for_operands_mismatch_4(%arg0 : index, %arg1 : index, %arg2 : index) {
|
|
%s0 = arith.constant 0.0 : f32
|
|
%t0 = arith.constant 1.0 : f32
|
|
// expected-error @+1 {{along control flow edge from Region #0 to Region #0: source type #1 'i32' should match input type #1 'f32'}}
|
|
%result1:2 = scf.for %i0 = %arg0 to %arg1 step %arg2
|
|
iter_args(%si = %s0, %ti = %t0) -> (f32, f32) {
|
|
%sn = arith.addf %si, %si : f32
|
|
%ic = arith.constant 1 : i32
|
|
scf.yield %sn, %ic : f32, i32
|
|
}
|
|
return
|
|
}
|
|
|
|
|
|
// -----
|
|
|
|
func.func @parallel_invalid_yield(
|
|
%arg0: index, %arg1: index, %arg2: index) {
|
|
scf.parallel (%i0) = (%arg0) to (%arg1) step (%arg2) {
|
|
%c0 = arith.constant 1.0 : f32
|
|
// expected-error@+1 {{'scf.yield' op not allowed to have operands inside 'scf.parallel'}}
|
|
scf.yield %c0 : f32
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @yield_invalid_parent_op() {
|
|
"my.op"() ({
|
|
// expected-error@+1 {{'scf.yield' op expects parent op to be one of 'scf.execute_region, scf.for, scf.if, scf.parallel, scf.while'}}
|
|
scf.yield
|
|
}) : () -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @while_parser_type_mismatch() {
|
|
%true = arith.constant true
|
|
// expected-error@+1 {{expected as many input types as operands (expected 0 got 1)}}
|
|
scf.while : (i32) -> () {
|
|
scf.condition(%true)
|
|
} do {
|
|
scf.yield
|
|
}
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @while_bad_terminator() {
|
|
// expected-error@+1 {{expects the 'before' region to terminate with 'scf.condition'}}
|
|
scf.while : () -> () {
|
|
// expected-note@+1 {{terminator here}}
|
|
"some.other_terminator"() : () -> ()
|
|
} do {
|
|
scf.yield
|
|
}
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @while_cross_region_type_mismatch() {
|
|
%true = arith.constant true
|
|
// expected-error@+1 {{'scf.while' op region control flow edge from Region #0 to Region #1: source has 0 operands, but target successor needs 1}}
|
|
scf.while : () -> () {
|
|
scf.condition(%true)
|
|
} do {
|
|
^bb0(%arg0: i32):
|
|
scf.yield
|
|
}
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @while_cross_region_type_mismatch() {
|
|
%true = arith.constant true
|
|
// expected-error@+1 {{'scf.while' op along control flow edge from Region #0 to Region #1: source type #0 'i1' should match input type #0 'i32'}}
|
|
%0 = scf.while : () -> (i1) {
|
|
scf.condition(%true) %true : i1
|
|
} do {
|
|
^bb0(%arg0: i32):
|
|
scf.yield
|
|
}
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @while_result_type_mismatch() {
|
|
%true = arith.constant true
|
|
// expected-error@+1 {{'scf.while' op region control flow edge from Region #0 to parent results: source has 1 operands, but target successor needs 0}}
|
|
scf.while : () -> () {
|
|
scf.condition(%true) %true : i1
|
|
} do {
|
|
^bb0(%arg0: i1):
|
|
scf.yield
|
|
}
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @while_bad_terminator() {
|
|
%true = arith.constant true
|
|
// expected-error@+1 {{expects the 'after' region to terminate with 'scf.yield'}}
|
|
scf.while : () -> () {
|
|
scf.condition(%true)
|
|
} do {
|
|
// expected-note@+1 {{terminator here}}
|
|
"some.other_terminator"() : () -> ()
|
|
}
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @execute_region() {
|
|
// expected-error @+1 {{region cannot have any arguments}}
|
|
"scf.execute_region"() ({
|
|
^bb0(%i : i32):
|
|
scf.yield
|
|
}) : () -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @wrong_num_results(%in: tensor<100xf32>, %out: tensor<100xf32>) {
|
|
%c1 = arith.constant 1 : index
|
|
%num_threads = arith.constant 100 : index
|
|
|
|
// expected-error @+1 {{produces 2 results, but its terminator yields 1 value(s)}}
|
|
%result:2 = scf.foreach_thread (%thread_idx) in (%num_threads) -> (tensor<100xf32>, tensor<100xf32>) {
|
|
%1 = tensor.extract_slice %in[%thread_idx][1][1] : tensor<100xf32> to tensor<1xf32>
|
|
scf.foreach_thread.perform_concurrently {
|
|
scf.foreach_thread.parallel_insert_slice %1 into %out[%thread_idx][1][1] :
|
|
tensor<1xf32> into tensor<100xf32>
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @wrong_type_result(%in: tensor<100xf32>, %out: tensor<100xf32>) {
|
|
%c1 = arith.constant 1 : index
|
|
%num_threads = arith.constant 100 : index
|
|
|
|
// expected-error @+1 {{type mismatch between result 0 ('tensor<?xf32>') and terminator ('tensor<100xf32>')}}
|
|
%result = scf.foreach_thread (%thread_idx) in (%num_threads) -> (tensor<?xf32>) {
|
|
%1 = tensor.extract_slice %in[%thread_idx][1][1] : tensor<100xf32> to tensor<1xf32>
|
|
scf.foreach_thread.perform_concurrently {
|
|
scf.foreach_thread.parallel_insert_slice %1 into %out[%thread_idx][1][1] :
|
|
tensor<1xf32> into tensor<100xf32>
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
func.func @wrong_terminator_op(%in: tensor<100xf32>, %out: tensor<100xf32>) {
|
|
%c1 = arith.constant 1 : index
|
|
%num_threads = arith.constant 100 : index
|
|
|
|
%result = scf.foreach_thread (%thread_idx) in (%num_threads) -> (tensor<100xf32>) {
|
|
%1 = tensor.extract_slice %in[%thread_idx][1][1] : tensor<100xf32> to tensor<1xf32>
|
|
// expected-error @+1 {{expected only scf.foreach_thread.parallel_insert_slice ops}}
|
|
scf.foreach_thread.perform_concurrently {
|
|
scf.foreach_thread.parallel_insert_slice %1 into %out[%thread_idx][1][1] :
|
|
tensor<1xf32> into tensor<100xf32>
|
|
%0 = arith.constant 1: index
|
|
}
|
|
}
|
|
return
|
|
}
|