| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 |
| ; RUN: opt -S -passes=structurizecfg %s -o - | FileCheck %s |
| |
| ; Structurize as usual, but don't tear callbr and its destination blocks apart. |
| ; |
| ; Note: currently, callbr blocks and their corresponding target blocks |
| ; themselves are not handled by the structurizer.* If the CFG turns out to be |
| ; unstructured at the end, the CFG lowering (si-annotate-control-flow) will |
| ; detect this. For the currently intended use cases of callbr in the context of |
| ; the AMDGPU backend, this is not a limitation (cf. |
| ; https://discourse.llvm.org/t/rfc-add-callbr-intrinsic-support/86087). |
| ; |
| ; Note 2: while callbr and its targets remain untouched, everything else is |
| ; handled as usual, even if it is nested in a callbr region. |
| ; |
| ; *FIXME: this will be fixed in the future. Callbr can be handled as follows: |
| ; Input IR: |
| ; ``` |
| ; define void @foo_callbr() { |
| ; callbr void asm "", "!i"() to label %fallthrough [label %indirect, ...] |
| ; fallthrough: |
| ; br label %exit |
| ; indirect: |
| ; br label %exit |
| ; ... |
| ; exit: |
| ; ret void |
| ; } |
| ; ``` |
| ; |
| ; Output IR: |
| ; ``` |
| ; define void @foo_callbr() { |
| ; callbr void asm "", "!i"() |
| ; to label %fallthrough [label %fake.indirect, label %fake.indirect1, label %fake.indirect2, ...] |
| ; fake.indirect: ; preds = %0 |
| ; br label %Flow |
| ; fake.indirect1: ; preds = %0 |
| ; br label %Flow |
| ; fake.indirect2: ; preds = %0 |
| ; br label %Flow |
| ; ... |
| ; Flow: ; preds = %fallthrough, %fake.indirect[0-N] |
| ; %1 = phi i1 [ false, %fallthrough ], [ true, %fake.indirect ], [ false, %fake.indirect[1-N] ] |
| ; br i1 %1, label %indirect, label %Flow1 |
| ; Flow1: ; preds = %Flow, %indirect |
| ; %2 = phi i1 [ false, %Flow], [ true, %fake.indirect1 ], [ false, %indirect ] |
| ; br i1 %2, label %indirect1, label %Flow2 |
| ; Flow2: ; preds = %Flow, %indirect1 |
| ; %2 = phi i1 [ false, %Flow], [ true, %fake.indirect2 ], [ false, %indirect1 ] |
| ; br i1 %2, label %indirect2, label %Flow3 |
| ; ... |
| ; fallthrough: ; preds = %0 |
| ; br label %Flow |
| ; indirect: ; preds = %Flow |
| ; br label %Flow1 |
| ; indirect1: ; preds = %Flow1 |
| ; br label %Flow2 |
| ; indirect2: : preds = %Flow2 |
| ; br label %Flow3 |
| ; ... |
| ; exit: ; preds = %indirectN, %FlowN |
| ; ret void |
| ; } |
| ; ``` |
| ; |
| ; Output IR as ASCII-art: |
| ; %0 |
| ; --------------------- |
| ; | | | | |
| ; v v v v |
| ; f f.i f.i1 f.i2 |
| ; | | | | |
| ; v v v v |
| ; --------------------- |
| ; %Flow |
| ; | \ |
| ; | %indirect |
| ; | / |
| ; %Flow1 |
| ; | \ |
| ; | %indirect1 |
| ; | / |
| ; %Flow2 |
| ; | \ |
| ; | %indirect2 |
| ; | / |
| ; %exit |
| ; |
| |
| ; Only callbr, nothing to do. |
| define void @callbr_simple() { |
| ; CHECK-LABEL: define void @callbr_simple() { |
| ; CHECK-NEXT: [[CALLBR:.*:]] |
| ; CHECK-NEXT: callbr void asm "", "!i"() |
| ; CHECK-NEXT: to label %[[INDIRECT:.*]] [label %indirect] |
| ; CHECK: [[INDIRECT]]: |
| ; CHECK-NEXT: br label %[[EXIT:.*]] |
| ; CHECK: [[INDIRECT1:.*:]] |
| ; CHECK-NEXT: br label %[[EXIT]] |
| ; CHECK: [[EXIT]]: |
| ; CHECK-NEXT: ret void |
| ; |
| callbr: |
| callbr void asm "", "!i"() to label %fallthrough [label %indirect] |
| fallthrough: |
| br label %exit |
| indirect: |
| br label %exit |
| exit: |
| ret void |
| } |
| |
| ; Callbr nested in non-callbr: non-callbr is transformed |
| define void @callbr_in_non_callbr(i1 %c) { |
| ; CHECK-LABEL: define void @callbr_in_non_callbr( |
| ; CHECK-SAME: i1 [[C:%.*]]) { |
| ; CHECK-NEXT: [[C_INV:%.*]] = xor i1 [[C]], true |
| ; CHECK-NEXT: br i1 [[C_INV]], label %[[NOCALLBR:.*]], label %[[FLOW:.*]] |
| ; CHECK: [[FLOW]]: |
| ; CHECK-NEXT: [[TMP1:%.*]] = phi i1 [ false, %[[NOCALLBR]] ], [ true, [[TMP0:%.*]] ] |
| ; CHECK-NEXT: br i1 [[TMP1]], label %[[CALLBR:.*]], label %[[EXIT:.*]] |
| ; CHECK: [[CALLBR]]: |
| ; CHECK-NEXT: callbr void asm "", "!i"() |
| ; CHECK-NEXT: to label %[[INDIRECT:.*]] [label %indirect] |
| ; CHECK: [[INDIRECT]]: |
| ; CHECK-NEXT: br label %[[EXIT]] |
| ; CHECK: [[INDIRECT1:.*:]] |
| ; CHECK-NEXT: br label %[[EXIT]] |
| ; CHECK: [[NOCALLBR]]: |
| ; CHECK-NEXT: br label %[[FLOW]] |
| ; CHECK: [[EXIT]]: |
| ; CHECK-NEXT: ret void |
| ; |
| br i1 %c, label %callbr, label %nocallbr |
| callbr: |
| callbr void asm "", "!i"() to label %fallthrough [label %indirect] |
| fallthrough: |
| br label %exit |
| indirect: |
| br label %exit |
| nocallbr: |
| br label %exit |
| exit: |
| ret void |
| } |
| |
| ; Callbr parent of non-callbr: non-callbr is transformed |
| define void @non_callbr_in_callbr(i1 %c) { |
| ; CHECK-LABEL: define void @non_callbr_in_callbr( |
| ; CHECK-SAME: i1 [[C:%.*]]) { |
| ; CHECK-NEXT: [[C_INV:%.*]] = xor i1 [[C]], true |
| ; CHECK-NEXT: callbr void asm "", "!i"() |
| ; CHECK-NEXT: to label %[[INDIRECT:.*]] [label %indirect] |
| ; CHECK: [[INDIRECT]]: |
| ; CHECK-NEXT: br i1 [[C_INV]], label %[[FALLTHROUGH2:.*]], label %[[FLOW:.*]] |
| ; CHECK: [[FLOW]]: |
| ; CHECK-NEXT: [[TMP1:%.*]] = phi i1 [ false, %[[FALLTHROUGH2]] ], [ true, %[[INDIRECT]] ] |
| ; CHECK-NEXT: br i1 [[TMP1]], label %[[FALLTHROUGH1:.*]], label %[[FLOW1:.*]] |
| ; CHECK: [[FALLTHROUGH1]]: |
| ; CHECK-NEXT: br label %[[FLOW1]] |
| ; CHECK: [[FALLTHROUGH2]]: |
| ; CHECK-NEXT: br label %[[FLOW]] |
| ; CHECK: [[INDIRECT1:.*:]] |
| ; CHECK-NEXT: br label %[[EXIT:.*]] |
| ; CHECK: [[FLOW1]]: |
| ; CHECK-NEXT: br label %[[EXIT]] |
| ; CHECK: [[EXIT]]: |
| ; CHECK-NEXT: ret void |
| ; |
| callbr void asm "", "!i"() to label %fallthrough [label %indirect] |
| fallthrough: |
| br i1 %c, label %fallthrough1, label %fallthrough2 |
| fallthrough1: |
| br label %exit |
| fallthrough2: |
| br label %exit |
| indirect: |
| br label %exit |
| exit: |
| ret void |
| } |
| |
| ; Callbr surrounded by non-callbr: all three regular branches are handled |
| ; correctly |
| define void @callbr_nested_in_non_callbr(i1 %c, i1 %d, i1 %e, i1 %f) { |
| ; CHECK-LABEL: define void @callbr_nested_in_non_callbr( |
| ; CHECK-SAME: i1 [[C:%.*]], i1 [[D:%.*]], i1 [[E:%.*]], i1 [[F:%.*]]) { |
| ; CHECK-NEXT: [[C_INV:%.*]] = xor i1 [[C]], true |
| ; CHECK-NEXT: br i1 [[C_INV]], label %[[NOCALLBR:.*]], label %[[FLOW3:.*]] |
| ; CHECK: [[FLOW3]]: |
| ; CHECK-NEXT: [[TMP1:%.*]] = phi i1 [ false, %[[FLOW:.*]] ], [ true, [[TMP0:%.*]] ] |
| ; CHECK-NEXT: br i1 [[TMP1]], label %[[CALLBR:.*]], label %[[RET:.*]] |
| ; CHECK: [[CALLBR]]: |
| ; CHECK-NEXT: callbr void asm "", "!i"() |
| ; CHECK-NEXT: to label %[[INDIRECT:.*]] [label %indirect] |
| ; CHECK: [[INDIRECT]]: |
| ; CHECK-NEXT: br i1 [[D]], label %[[FALLTHROUGH1:.*]], label %[[FLOW2:.*]] |
| ; CHECK: [[FALLTHROUGH1]]: |
| ; CHECK-NEXT: br label %[[FLOW2]] |
| ; CHECK: [[INDIRECT2:.*:]] |
| ; CHECK-NEXT: br i1 [[E]], label %[[INDIRECT1:.*]], label %[[FLOW1:.*]] |
| ; CHECK: [[INDIRECT1]]: |
| ; CHECK-NEXT: br label %[[FLOW1]] |
| ; CHECK: [[NOCALLBR]]: |
| ; CHECK-NEXT: br i1 [[F]], label %[[NOCALLBR1:.*]], label %[[FLOW]] |
| ; CHECK: [[NOCALLBR1]]: |
| ; CHECK-NEXT: br label %[[FLOW]] |
| ; CHECK: [[FLOW]]: |
| ; CHECK-NEXT: br label %[[FLOW3]] |
| ; CHECK: [[FLOW1]]: |
| ; CHECK-NEXT: br label %[[RET]] |
| ; CHECK: [[FLOW2]]: |
| ; CHECK-NEXT: br label %[[RET]] |
| ; CHECK: [[RET]]: |
| ; CHECK-NEXT: ret void |
| ; |
| br i1 %c, label %callbr, label %nocallbr |
| callbr: |
| callbr void asm "", "!i"() to label %fallthrough [label %indirect] |
| fallthrough: |
| br i1 %d, label %fallthrough1, label %ret |
| fallthrough1: |
| br label %ret |
| indirect: |
| br i1 %e, label %indirect1, label %ret |
| indirect1: |
| br label %ret |
| nocallbr: |
| br i1 %f, label %nocallbr1, label %ret |
| nocallbr1: |
| br label %ret |
| ret: |
| ret void |
| } |