; 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 }