| ; Test for a subtle bug when computing analyses during inlining and mutating | |
| ; the SCC structure. Without care, this can fail to invalidate analyses. | |
| ; | |
| ; RUN: opt < %s -aa-pipeline= -passes='cgscc(inline,function(verify<domtree>))' -debug-pass-manager -inline-deferral -S 2>&1 | FileCheck %s | |
| ; First we check that the passes run in the way we expect. Otherwise this test | |
| ; may stop testing anything. | |
| ; | |
| ; CHECK: Running pass: InlinerPass on (test1_f, test1_g, test1_h) | |
| ; CHECK: Running analysis: DominatorTreeAnalysis on test1_f | |
| ; CHECK: Invalidating analysis: CycleAnalysis on test1_f | |
| ; CHECK: Invalidating analysis: DominatorTreeAnalysis on test1_f | |
| ; CHECK: Invalidating analysis: BranchProbabilityAnalysis on test1_f | |
| ; CHECK: Invalidating analysis: BlockFrequencyAnalysis on test1_f | |
| ; CHECK: Invalidating analysis: EphemeralValuesAnalysis on test1_f | |
| ; CHECK: Running analysis: DominatorTreeAnalysis on test1_g | |
| ; CHECK: Invalidating analysis: CycleAnalysis on test1_g | |
| ; CHECK: Invalidating analysis: DominatorTreeAnalysis on test1_g | |
| ; CHECK: Invalidating analysis: BranchProbabilityAnalysis on test1_g | |
| ; CHECK: Invalidating analysis: BlockFrequencyAnalysis on test1_g | |
| ; CHECK: Invalidating analysis: EphemeralValuesAnalysis on test1_g | |
| ; CHECK: Invalidating analysis: CycleAnalysis on test1_h | |
| ; CHECK: Invalidating analysis: DominatorTreeAnalysis on test1_h | |
| ; CHECK: Invalidating analysis: BranchProbabilityAnalysis on test1_h | |
| ; CHECK: Invalidating analysis: BlockFrequencyAnalysis on test1_h | |
| ; CHECK: Invalidating analysis: EphemeralValuesAnalysis on test1_h | |
| ; CHECK-NOT: Invalidating analysis: | |
| ; CHECK: Running pass: DominatorTreeVerifierPass on test1_g | |
| ; CHECK-NEXT: Running analysis: DominatorTreeAnalysis on test1_g | |
| ; CHECK-NOT: Invalidating analysis: | |
| ; CHECK: Running pass: DominatorTreeVerifierPass on test1_h | |
| ; CHECK-NEXT: Running analysis: DominatorTreeAnalysis on test1_h | |
| ; CHECK-NOT: Invalidating analysis: | |
| ; CHECK: Running pass: DominatorTreeVerifierPass on test1_f | |
| ; An external function used to control branches. | |
| declare i1 @flag() | |
| ; CHECK-LABEL: declare i1 @flag() | |
| ; The utility function with interesting control flow that gets inlined below to | |
| ; perturb the dominator tree. | |
| define internal void @callee() { | |
| entry: | |
| %ptr = alloca i8 | |
| %flag = call i1 @flag() | |
| br i1 %flag, label %then, label %else | |
| then: | |
| store volatile i8 42, ptr %ptr | |
| br label %return | |
| else: | |
| store volatile i8 -42, ptr %ptr | |
| br label %return | |
| return: | |
| ret void | |
| } | |
| ; The 'test1_' prefixed functions work to carefully test that incrementally | |
| ; reducing an SCC in the inliner cannot accidentially leave stale function | |
| ; analysis results due to failing to invalidate them for all the functions. | |
| ; We visit this function first in the inliner, and while we inline callee | |
| ; perturbing the CFG, we don't inline anything else and the SCC structure | |
| ; remains in tact. | |
| define void @test1_f() { | |
| ; CHECK-LABEL: define void @test1_f() | |
| entry: | |
| ; We force this edge to survive inlining. | |
| call void @test1_g() noinline | |
| ; CHECK: call void @test1_g() | |
| ; Pull interesting CFG into this function. | |
| call void @callee() | |
| ; CHECK-NOT: call void @callee() | |
| ret void | |
| ; CHECK: ret void | |
| } | |
| ; We visit this function second and here we inline the edge to 'test1_f' | |
| ; separating it into its own SCC. The current SCC is now just 'test1_g' and | |
| ; 'test1_h'. | |
| define void @test1_g() { | |
| ; CHECK-LABEL: define void @test1_g() | |
| entry: | |
| ; This edge gets inlined away. | |
| call void @test1_f() | |
| ; CHECK-NOT: call void @test1_f() | |
| ; CHECK: call void @test1_g() | |
| ; We force this edge to survive inlining. | |
| call void @test1_h() noinline | |
| ; CHECK: call void @test1_h() | |
| ; Pull interesting CFG into this function. | |
| call void @callee() | |
| ; CHECK-NOT: call void @callee() | |
| ret void | |
| ; CHECK: ret void | |
| } | |
| ; The inliner visits this last function. It can't actually break any cycles | |
| ; here, but because we visit this function we compute fresh analyses for it. | |
| ; These analyses are then invalidated when we inline callee disrupting the | |
| ; CFG, and it is important that they be freed. | |
| define void @test1_h() { | |
| ; CHECK-LABEL: define void @test1_h() | |
| entry: | |
| call void @test1_g() | |
| ; CHECK: call void @test1_g() | |
| ; Pull interesting CFG into this function. | |
| call void @callee() | |
| ; CHECK-NOT: call void @callee() | |
| ret void | |
| ; CHECK: ret void | |
| } | |
| ; The 'test2_' prefixed code works to carefully trigger forming an SCC with | |
| ; a dominator tree for one of the functions but not the other and without even | |
| ; a function analysis manager proxy for the SCC that things get merged into. | |
| ; Without proper handling when updating the call graph this will find a stale | |
| ; dominator tree. | |
| @test2_global = external global i32, align 4 | |
| define void @test2_hoge(ptr %arg) { | |
| ; CHECK-LABEL: define void @test2_hoge( | |
| bb: | |
| %tmp2 = call zeroext i1 %arg(ptr @test2_global) | |
| ; CHECK: call zeroext i1 %arg( | |
| br label %bb3 | |
| bb3: | |
| %tmp5 = call zeroext i1 %arg(ptr @test2_global) | |
| ; CHECK: call zeroext i1 %arg( | |
| br i1 %tmp5, label %bb3, label %bb6 | |
| bb6: | |
| ret void | |
| } | |
| define zeroext i1 @test2_widget(ptr %arg) { | |
| ; CHECK-LABEL: define zeroext i1 @test2_widget( | |
| bb: | |
| %tmp1 = alloca i8, align 1 | |
| %tmp2 = alloca i32, align 4 | |
| call void @test2_quux() | |
| ; CHECK-NOT: call | |
| ; | |
| ; CHECK: call zeroext i1 @test2_widget(ptr @test2_global) | |
| ; CHECK-NEXT: br label %[[NEW_BB:.*]] | |
| ; | |
| ; CHECK: [[NEW_BB]]: | |
| ; CHECK-NEXT: call zeroext i1 @test2_widget(ptr @test2_global) | |
| ; | |
| ; CHECK: {{.*}}: | |
| call void @test2_hoge.1(ptr %arg) | |
| ; CHECK-NEXT: call void @test2_hoge.1( | |
| %tmp4 = call zeroext i1 @test2_barney(ptr %tmp2) | |
| %tmp5 = zext i1 %tmp4 to i32 | |
| store i32 %tmp5, ptr %tmp2, align 4 | |
| %tmp6 = call zeroext i1 @test2_barney(ptr null) | |
| call void @test2_ham(ptr %tmp1) | |
| ; CHECK: call void @test2_ham( | |
| call void @test2_quux() | |
| ; CHECK-NOT: call | |
| ; | |
| ; CHECK: call zeroext i1 @test2_widget(ptr @test2_global) | |
| ; CHECK-NEXT: br label %[[NEW_BB:.*]] | |
| ; | |
| ; CHECK: [[NEW_BB]]: | |
| ; CHECK-NEXT: call zeroext i1 @test2_widget(ptr @test2_global) | |
| ; | |
| ; CHECK: {{.*}}: | |
| ret i1 true | |
| ; CHECK-NEXT: ret i1 true | |
| } | |
| define internal void @test2_quux() { | |
| ; CHECK-NOT: @test2_quux | |
| bb: | |
| call void @test2_hoge(ptr @test2_widget) | |
| ret void | |
| } | |
| declare void @test2_hoge.1(ptr) | |
| declare zeroext i1 @test2_barney(ptr) | |
| declare void @test2_ham(ptr) |