diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index d50218aaa3b6cc..603a1565e48c45 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -82,6 +82,7 @@ STATISTIC(NumNoUnwind, "Number of functions marked as nounwind"); STATISTIC(NumNoFree, "Number of functions marked as nofree"); STATISTIC(NumWillReturn, "Number of functions marked as willreturn"); STATISTIC(NumNoSync, "Number of functions marked as nosync"); +STATISTIC(NumCold, "Number of functions marked as cold"); STATISTIC(NumThinLinkNoRecurse, "Number of functions marked as norecurse during thinlink"); @@ -1745,6 +1746,7 @@ static bool canReturn(Function &F) { return false; } + // Set the noreturn function attribute if possible. static void addNoReturnAttrs(const SCCNodeSet &SCCNodes, SmallSet &Changed) { @@ -1760,6 +1762,72 @@ static void addNoReturnAttrs(const SCCNodeSet &SCCNodes, } } +static bool +allBBPathsGoThroughCold(BasicBlock *BB, + SmallDenseMap &Visited) { + // If BB contains a cold callsite this path through the CG is cold. + // Ignore whether the instructions actually are guranteed to transfer + // execution. Divergent behavior is considered unlikely. + if (any_of(*BB, [](Instruction &I) { + if (auto *CB = dyn_cast(&I)) + return CB->hasFnAttr(Attribute::Cold); + return false; + })) { + Visited[BB] = true; + return true; + } + + auto Succs = successors(BB); + // We found a path that doesn't go through any cold callsite. + if (Succs.empty()) + return false; + + // We didn't find a cold callsite in this BB, so check that all successors + // contain a cold callsite (or that their successors do). + // Potential TODO: We could use static branch hints to assume certain + // successor paths are inherently cold, irrespective of if they contain a cold + // callsite. + for (auto *Succ : Succs) { + // Start with false, this is necessary to ensure we don't turn loops into + // cold. + auto R = Visited.try_emplace(Succ, false); + if (!R.second) { + if (R.first->second) + continue; + return false; + } + if (!allBBPathsGoThroughCold(Succ, Visited)) + return false; + Visited[Succ] = true; + } + + return true; +} + +static bool allPathsGoThroughCold(Function &F) { + SmallDenseMap Visited; + Visited[&F.front()] = false; + return allBBPathsGoThroughCold(&F.front(), Visited); +} + +// Set the cold function attribute if possible. +static void addColdAttrs(const SCCNodeSet &SCCNodes, + SmallSet &Changed) { + for (Function *F : SCCNodes) { + if (!F || !F->hasExactDefinition() || F->hasFnAttribute(Attribute::Naked) || + F->hasFnAttribute(Attribute::Cold) || F->hasFnAttribute(Attribute::Hot)) + continue; + + // Potential TODO: We could add attribute `cold` on functions with `coldcc`. + if (allPathsGoThroughCold(*F)) { + F->addFnAttr(Attribute::Cold); + ++NumCold; + Changed.insert(F); + continue; + } + } +} + static bool functionWillReturn(const Function &F) { // We can infer and propagate function attributes only when we know that the // definition we'll get at link time is *exactly* the definition we see now. @@ -1853,6 +1921,7 @@ deriveAttrsInPostOrder(ArrayRef Functions, AARGetterT &&AARGetter, addArgumentAttrs(Nodes.SCCNodes, Changed); inferConvergent(Nodes.SCCNodes, Changed); addNoReturnAttrs(Nodes.SCCNodes, Changed); + addColdAttrs(Nodes.SCCNodes, Changed); addWillReturn(Nodes.SCCNodes, Changed); addNoUndefAttrs(Nodes.SCCNodes, Changed); diff --git a/llvm/test/Transforms/FunctionAttrs/cold.ll b/llvm/test/Transforms/FunctionAttrs/cold.ll new file mode 100644 index 00000000000000..a205fbda062121 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/cold.ll @@ -0,0 +1,880 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals --version 2 +; RUN: opt -passes=function-attrs -S < %s | FileCheck --check-prefixes=COMMON,FNATTRS %s +; RUN: opt -passes=attributor-light -S < %s | FileCheck --check-prefixes=COMMON,ATTRIBUTOR %s + +declare i32 @get_val() + +declare void @cold0() cold +declare void @cold1() cold +declare void @cold_at_cb() + +declare void @not_cold0() +declare void @not_cold1() +declare void @not_cold2() + +define void @test_no_exit_fail() { +; COMMON: Function Attrs: nofree norecurse noreturn nosync nounwind memory(none) +; COMMON-LABEL: define void @test_no_exit_fail +; COMMON-SAME: () #[[ATTR1:[0-9]+]] { +; COMMON-NEXT: entry: +; COMMON-NEXT: br label [[WHILE_BODY:%.*]] +; COMMON: while.body: +; COMMON-NEXT: br label [[WHILE_BODY]] +; +entry: + br label %while.body + +while.body: + br label %while.body +} + +define void @test_no_exit_fail2() { +; COMMON: Function Attrs: noreturn +; COMMON-LABEL: define void @test_no_exit_fail2 +; COMMON-SAME: () #[[ATTR2:[0-9]+]] { +; COMMON-NEXT: entry: +; COMMON-NEXT: br label [[WHILE_BODY:%.*]] +; COMMON: while.body: +; COMMON-NEXT: call void @not_cold0() +; COMMON-NEXT: br label [[WHILE_BODY2:%.*]] +; COMMON: while.body2: +; COMMON-NEXT: call void @not_cold1() +; COMMON-NEXT: br label [[WHILE_BODY]] +; +entry: + br label %while.body + +while.body: + call void @not_cold0() + br label %while.body2 + +while.body2: + call void @not_cold1() + br label %while.body +} + +define void @test_no_exit() { +; FNATTRS: Function Attrs: cold noreturn +; FNATTRS-LABEL: define void @test_no_exit +; FNATTRS-SAME: () #[[ATTR3:[0-9]+]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: br label [[WHILE_BODY:%.*]] +; FNATTRS: while.body: +; FNATTRS-NEXT: call void @cold0() +; FNATTRS-NEXT: br label [[WHILE_BODY]] +; +; ATTRIBUTOR: Function Attrs: noreturn +; ATTRIBUTOR-LABEL: define void @test_no_exit +; ATTRIBUTOR-SAME: () #[[ATTR2]] { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: br label [[WHILE_BODY:%.*]] +; ATTRIBUTOR: while.body: +; ATTRIBUTOR-NEXT: call void @cold0() +; ATTRIBUTOR-NEXT: br label [[WHILE_BODY]] +; +entry: + br label %while.body + +while.body: + call void @cold0() + br label %while.body +} + +define void @test_no_exit2() { +; FNATTRS: Function Attrs: cold noreturn +; FNATTRS-LABEL: define void @test_no_exit2 +; FNATTRS-SAME: () #[[ATTR3]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: br label [[WHILE_BODY:%.*]] +; FNATTRS: while.body: +; FNATTRS-NEXT: call void @not_cold0() +; FNATTRS-NEXT: br label [[WHILE_BODY2:%.*]] +; FNATTRS: while.body2: +; FNATTRS-NEXT: call void @cold1() +; FNATTRS-NEXT: br label [[WHILE_BODY]] +; +; ATTRIBUTOR: Function Attrs: noreturn +; ATTRIBUTOR-LABEL: define void @test_no_exit2 +; ATTRIBUTOR-SAME: () #[[ATTR2]] { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: br label [[WHILE_BODY:%.*]] +; ATTRIBUTOR: while.body: +; ATTRIBUTOR-NEXT: call void @not_cold0() +; ATTRIBUTOR-NEXT: br label [[WHILE_BODY2:%.*]] +; ATTRIBUTOR: while.body2: +; ATTRIBUTOR-NEXT: call void @cold1() +; ATTRIBUTOR-NEXT: br label [[WHILE_BODY]] +; +entry: + br label %while.body + +while.body: + call void @not_cold0() + br label %while.body2 + +while.body2: + call void @cold1() + br label %while.body +} + +define dso_local void @test_entry(i32 noundef %x) { +; FNATTRS: Function Attrs: cold +; FNATTRS-LABEL: define dso_local void @test_entry +; FNATTRS-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label [[IF_THEN:%.*]] +; FNATTRS: if.then: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: br label [[IF_END]] +; FNATTRS: if.end: +; FNATTRS-NEXT: tail call void @not_cold1() +; FNATTRS-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define dso_local void @test_entry +; ATTRIBUTOR-SAME: (i32 noundef [[X:%.*]]) { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label [[IF_THEN:%.*]] +; ATTRIBUTOR: if.then: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: br label [[IF_END]] +; ATTRIBUTOR: if.end: +; ATTRIBUTOR-NEXT: tail call void @not_cold1() +; ATTRIBUTOR-NEXT: ret void +; +entry: + tail call void @cold0() + %tobool.not = icmp eq i32 %x, 0 + br i1 %tobool.not, label %if.end, label %if.then + +if.then: + tail call void @not_cold0() + br label %if.end + +if.end: + tail call void @not_cold1() + ret void +} + +define dso_local void @test_hot_fail(i32 noundef %x) hot { +; FNATTRS: Function Attrs: hot +; FNATTRS-LABEL: define dso_local void @test_hot_fail +; FNATTRS-SAME: (i32 noundef [[X:%.*]]) #[[ATTR4:[0-9]+]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: hot +; ATTRIBUTOR-LABEL: define dso_local void @test_hot_fail +; ATTRIBUTOR-SAME: (i32 noundef [[X:%.*]]) #[[ATTR3:[0-9]+]] { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: ret void +; +entry: + tail call void @cold0() + ret void +} + +define dso_local void @test_br2(i32 noundef %x) { +; FNATTRS: Function Attrs: cold +; FNATTRS-LABEL: define dso_local void @test_br2 +; FNATTRS-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN:%.*]] +; FNATTRS: if.then: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: br label [[IF_END:%.*]] +; FNATTRS: if.else: +; FNATTRS-NEXT: tail call void @cold1() +; FNATTRS-NEXT: br label [[IF_END]] +; FNATTRS: if.end: +; FNATTRS-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define dso_local void @test_br2 +; ATTRIBUTOR-SAME: (i32 noundef [[X:%.*]]) { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN:%.*]] +; ATTRIBUTOR: if.then: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: br label [[IF_END:%.*]] +; ATTRIBUTOR: if.else: +; ATTRIBUTOR-NEXT: tail call void @cold1() +; ATTRIBUTOR-NEXT: br label [[IF_END]] +; ATTRIBUTOR: if.end: +; ATTRIBUTOR-NEXT: ret void +; +entry: + %tobool.not = icmp eq i32 %x, 0 + br i1 %tobool.not, label %if.else, label %if.then + +if.then: + tail call void @cold0() + br label %if.end + +if.else: + tail call void @cold1() + br label %if.end + +if.end: + ret void +} + +define dso_local void @test_exit(i32 noundef %x) { +; FNATTRS: Function Attrs: cold +; FNATTRS-LABEL: define dso_local void @test_exit +; FNATTRS-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN:%.*]] +; FNATTRS: if.then: +; FNATTRS-NEXT: tail call void @not_cold1() +; FNATTRS-NEXT: br label [[IF_END:%.*]] +; FNATTRS: if.else: +; FNATTRS-NEXT: tail call void @not_cold2() +; FNATTRS-NEXT: br label [[IF_END]] +; FNATTRS: if.end: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define dso_local void @test_exit +; ATTRIBUTOR-SAME: (i32 noundef [[X:%.*]]) { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN:%.*]] +; ATTRIBUTOR: if.then: +; ATTRIBUTOR-NEXT: tail call void @not_cold1() +; ATTRIBUTOR-NEXT: br label [[IF_END:%.*]] +; ATTRIBUTOR: if.else: +; ATTRIBUTOR-NEXT: tail call void @not_cold2() +; ATTRIBUTOR-NEXT: br label [[IF_END]] +; ATTRIBUTOR: if.end: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: ret void +; +entry: + tail call void @not_cold0() + %tobool.not = icmp eq i32 %x, 0 + br i1 %tobool.not, label %if.else, label %if.then + +if.then: + tail call void @not_cold1() + br label %if.end + +if.else: + tail call void @not_cold2() + br label %if.end + +if.end: + tail call void @cold0() + ret void +} + +define dso_local void @test_complex(i32 noundef %x) { +; FNATTRS: Function Attrs: cold +; FNATTRS-LABEL: define dso_local void @test_complex +; FNATTRS-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE11:%.*]], label [[IF_THEN:%.*]] +; FNATTRS: if.then: +; FNATTRS-NEXT: [[CALL:%.*]] = tail call i32 @get_val() +; FNATTRS-NEXT: [[TOBOOL1_NOT:%.*]] = icmp eq i32 [[CALL]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL1_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN2:%.*]] +; FNATTRS: if.then2: +; FNATTRS-NEXT: tail call void @cold1() +; FNATTRS-NEXT: br label [[IF_END12:%.*]] +; FNATTRS: if.else: +; FNATTRS-NEXT: [[CALL3:%.*]] = tail call i32 @get_val() +; FNATTRS-NEXT: [[TOBOOL4_NOT:%.*]] = icmp eq i32 [[CALL3]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL4_NOT]], label [[IF_ELSE6:%.*]], label [[IF_THEN5:%.*]] +; FNATTRS: if.then5: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: br label [[IF_END12]] +; FNATTRS: if.else6: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: [[CALL7:%.*]] = tail call i32 @get_val() +; FNATTRS-NEXT: switch i32 [[CALL7]], label [[SW_DEFAULT:%.*]] [ +; FNATTRS-NEXT: i32 0, label [[SW_BB:%.*]] +; FNATTRS-NEXT: i32 1, label [[SW_BB8:%.*]] +; FNATTRS-NEXT: i32 2, label [[SW_BB9:%.*]] +; FNATTRS-NEXT: ] +; FNATTRS: sw.bb: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: br label [[CALL_COLD:%.*]] +; FNATTRS: sw.bb8: +; FNATTRS-NEXT: tail call void @not_cold1() +; FNATTRS-NEXT: br label [[CALL_COLD]] +; FNATTRS: sw.bb9: +; FNATTRS-NEXT: tail call void @not_cold2() +; FNATTRS-NEXT: br label [[CALL_COLD]] +; FNATTRS: sw.default: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: br label [[IF_END12]] +; FNATTRS: call_cold: +; FNATTRS-NEXT: tail call void @cold_at_cb() #[[ATTR0]] +; FNATTRS-NEXT: br label [[IF_END12]] +; FNATTRS: if.else11: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: br label [[IF_END12]] +; FNATTRS: if.end12: +; FNATTRS-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define dso_local void @test_complex +; ATTRIBUTOR-SAME: (i32 noundef [[X:%.*]]) { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE11:%.*]], label [[IF_THEN:%.*]] +; ATTRIBUTOR: if.then: +; ATTRIBUTOR-NEXT: [[CALL:%.*]] = tail call i32 @get_val() +; ATTRIBUTOR-NEXT: [[TOBOOL1_NOT:%.*]] = icmp eq i32 [[CALL]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL1_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN2:%.*]] +; ATTRIBUTOR: if.then2: +; ATTRIBUTOR-NEXT: tail call void @cold1() +; ATTRIBUTOR-NEXT: br label [[IF_END12:%.*]] +; ATTRIBUTOR: if.else: +; ATTRIBUTOR-NEXT: [[CALL3:%.*]] = tail call i32 @get_val() +; ATTRIBUTOR-NEXT: [[TOBOOL4_NOT:%.*]] = icmp eq i32 [[CALL3]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL4_NOT]], label [[IF_ELSE6:%.*]], label [[IF_THEN5:%.*]] +; ATTRIBUTOR: if.then5: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: br label [[IF_END12]] +; ATTRIBUTOR: if.else6: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: [[CALL7:%.*]] = tail call i32 @get_val() +; ATTRIBUTOR-NEXT: switch i32 [[CALL7]], label [[SW_DEFAULT:%.*]] [ +; ATTRIBUTOR-NEXT: i32 0, label [[SW_BB:%.*]] +; ATTRIBUTOR-NEXT: i32 1, label [[SW_BB8:%.*]] +; ATTRIBUTOR-NEXT: i32 2, label [[SW_BB9:%.*]] +; ATTRIBUTOR-NEXT: ] +; ATTRIBUTOR: sw.bb: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: br label [[CALL_COLD:%.*]] +; ATTRIBUTOR: sw.bb8: +; ATTRIBUTOR-NEXT: tail call void @not_cold1() +; ATTRIBUTOR-NEXT: br label [[CALL_COLD]] +; ATTRIBUTOR: sw.bb9: +; ATTRIBUTOR-NEXT: tail call void @not_cold2() +; ATTRIBUTOR-NEXT: br label [[CALL_COLD]] +; ATTRIBUTOR: sw.default: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: br label [[IF_END12]] +; ATTRIBUTOR: call_cold: +; ATTRIBUTOR-NEXT: tail call void @cold_at_cb() #[[ATTR0:[0-9]+]] +; ATTRIBUTOR-NEXT: br label [[IF_END12]] +; ATTRIBUTOR: if.else11: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: br label [[IF_END12]] +; ATTRIBUTOR: if.end12: +; ATTRIBUTOR-NEXT: ret void +; +entry: + tail call void @not_cold0() + %tobool.not = icmp eq i32 %x, 0 + br i1 %tobool.not, label %if.else11, label %if.then + +if.then: + %call = tail call i32 @get_val() + %tobool1.not = icmp eq i32 %call, 0 + br i1 %tobool1.not, label %if.else, label %if.then2 + +if.then2: + tail call void @cold1() + br label %if.end12 + +if.else: + %call3 = tail call i32 @get_val() + %tobool4.not = icmp eq i32 %call3, 0 + br i1 %tobool4.not, label %if.else6, label %if.then5 + +if.then5: + tail call void @cold0() + br label %if.end12 + +if.else6: + tail call void @not_cold0() + %call7 = tail call i32 @get_val() + switch i32 %call7, label %sw.default [ + i32 0, label %sw.bb + i32 1, label %sw.bb8 + i32 2, label %sw.bb9 + ] + +sw.bb: + tail call void @not_cold0() + br label %call_cold + +sw.bb8: + tail call void @not_cold1() + br label %call_cold + +sw.bb9: + tail call void @not_cold2() + br label %call_cold + +sw.default: + tail call void @cold0() + br label %if.end12 + +call_cold: + tail call void @cold_at_cb() cold + br label %if.end12 + +if.else11: + tail call void @cold0() + br label %if.end12 + +if.end12: + ret void +} + +define dso_local void @test_complex2(i32 noundef %x) { +; FNATTRS: Function Attrs: cold +; FNATTRS-LABEL: define dso_local void @test_complex2 +; FNATTRS-SAME: (i32 noundef [[X:%.*]]) #[[ATTR0]] { +; FNATTRS-NEXT: entry: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; FNATTRS-NEXT: [[CALL12:%.*]] = tail call i32 @get_val() +; FNATTRS-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE11:%.*]], label [[IF_THEN:%.*]] +; FNATTRS: if.then: +; FNATTRS-NEXT: [[TOBOOL1_NOT:%.*]] = icmp eq i32 [[CALL12]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL1_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN2:%.*]] +; FNATTRS: if.then2: +; FNATTRS-NEXT: tail call void @cold1() +; FNATTRS-NEXT: br label [[IF_END16:%.*]] +; FNATTRS: if.else: +; FNATTRS-NEXT: [[CALL3:%.*]] = tail call i32 @get_val() +; FNATTRS-NEXT: [[TOBOOL4_NOT:%.*]] = icmp eq i32 [[CALL3]], 0 +; FNATTRS-NEXT: br i1 [[TOBOOL4_NOT]], label [[IF_ELSE6:%.*]], label [[IF_THEN5:%.*]] +; FNATTRS: if.then5: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: br label [[IF_END16]] +; FNATTRS: if.else6: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: [[CALL7:%.*]] = tail call i32 @get_val() +; FNATTRS-NEXT: switch i32 [[CALL7]], label [[SW_DEFAULT:%.*]] [ +; FNATTRS-NEXT: i32 0, label [[SW_BB:%.*]] +; FNATTRS-NEXT: i32 1, label [[SW_BB8:%.*]] +; FNATTRS-NEXT: i32 2, label [[SW_BB9:%.*]] +; FNATTRS-NEXT: ] +; FNATTRS: sw.bb: +; FNATTRS-NEXT: tail call void @not_cold0() +; FNATTRS-NEXT: br label [[CALL_COLD:%.*]] +; FNATTRS: sw.bb8: +; FNATTRS-NEXT: tail call void @not_cold1() +; FNATTRS-NEXT: br label [[CALL_COLD]] +; FNATTRS: sw.bb9: +; FNATTRS-NEXT: tail call void @not_cold2() +; FNATTRS-NEXT: br label [[CALL_COLD]] +; FNATTRS: sw.default: +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: br label [[IF_END16]] +; FNATTRS: call_cold: +; FNATTRS-NEXT: tail call void @cold_at_cb() #[[ATTR0]] +; FNATTRS-NEXT: br label [[IF_END16]] +; FNATTRS: if.else11: +; FNATTRS-NEXT: [[CMP:%.*]] = icmp slt i32 [[CALL12]], 1 +; FNATTRS-NEXT: br i1 [[CMP]], label [[IF_END14:%.*]], label [[FOR_BODY:%.*]] +; FNATTRS: if.end14: +; FNATTRS-NEXT: tail call void @cold1() +; FNATTRS-NEXT: br label [[IF_END16]] +; FNATTRS: for.body: +; FNATTRS-NEXT: [[I_021:%.*]] = phi i32 [ [[INC:%.*]], [[FOR_BODY]] ], [ 0, [[IF_ELSE11]] ] +; FNATTRS-NEXT: tail call void @cold0() +; FNATTRS-NEXT: [[INC]] = add nuw nsw i32 [[I_021]], 1 +; FNATTRS-NEXT: [[EXITCOND_NOT:%.*]] = icmp eq i32 [[INC]], [[CALL12]] +; FNATTRS-NEXT: br i1 [[EXITCOND_NOT]], label [[IF_END16]], label [[FOR_BODY]] +; FNATTRS: if.end16: +; FNATTRS-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define dso_local void @test_complex2 +; ATTRIBUTOR-SAME: (i32 noundef [[X:%.*]]) { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; ATTRIBUTOR-NEXT: [[CALL12:%.*]] = tail call i32 @get_val() +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE11:%.*]], label [[IF_THEN:%.*]] +; ATTRIBUTOR: if.then: +; ATTRIBUTOR-NEXT: [[TOBOOL1_NOT:%.*]] = icmp eq i32 [[CALL12]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL1_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN2:%.*]] +; ATTRIBUTOR: if.then2: +; ATTRIBUTOR-NEXT: tail call void @cold1() +; ATTRIBUTOR-NEXT: br label [[IF_END16:%.*]] +; ATTRIBUTOR: if.else: +; ATTRIBUTOR-NEXT: [[CALL3:%.*]] = tail call i32 @get_val() +; ATTRIBUTOR-NEXT: [[TOBOOL4_NOT:%.*]] = icmp eq i32 [[CALL3]], 0 +; ATTRIBUTOR-NEXT: br i1 [[TOBOOL4_NOT]], label [[IF_ELSE6:%.*]], label [[IF_THEN5:%.*]] +; ATTRIBUTOR: if.then5: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: br label [[IF_END16]] +; ATTRIBUTOR: if.else6: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: [[CALL7:%.*]] = tail call i32 @get_val() +; ATTRIBUTOR-NEXT: switch i32 [[CALL7]], label [[SW_DEFAULT:%.*]] [ +; ATTRIBUTOR-NEXT: i32 0, label [[SW_BB:%.*]] +; ATTRIBUTOR-NEXT: i32 1, label [[SW_BB8:%.*]] +; ATTRIBUTOR-NEXT: i32 2, label [[SW_BB9:%.*]] +; ATTRIBUTOR-NEXT: ] +; ATTRIBUTOR: sw.bb: +; ATTRIBUTOR-NEXT: tail call void @not_cold0() +; ATTRIBUTOR-NEXT: br label [[CALL_COLD:%.*]] +; ATTRIBUTOR: sw.bb8: +; ATTRIBUTOR-NEXT: tail call void @not_cold1() +; ATTRIBUTOR-NEXT: br label [[CALL_COLD]] +; ATTRIBUTOR: sw.bb9: +; ATTRIBUTOR-NEXT: tail call void @not_cold2() +; ATTRIBUTOR-NEXT: br label [[CALL_COLD]] +; ATTRIBUTOR: sw.default: +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: br label [[IF_END16]] +; ATTRIBUTOR: call_cold: +; ATTRIBUTOR-NEXT: tail call void @cold_at_cb() #[[ATTR0]] +; ATTRIBUTOR-NEXT: br label [[IF_END16]] +; ATTRIBUTOR: if.else11: +; ATTRIBUTOR-NEXT: [[CMP:%.*]] = icmp slt i32 [[CALL12]], 1 +; ATTRIBUTOR-NEXT: br i1 [[CMP]], label [[IF_END14:%.*]], label [[FOR_BODY:%.*]] +; ATTRIBUTOR: if.end14: +; ATTRIBUTOR-NEXT: tail call void @cold1() +; ATTRIBUTOR-NEXT: br label [[IF_END16]] +; ATTRIBUTOR: for.body: +; ATTRIBUTOR-NEXT: [[I_021:%.*]] = phi i32 [ [[INC:%.*]], [[FOR_BODY]] ], [ 0, [[IF_ELSE11]] ] +; ATTRIBUTOR-NEXT: tail call void @cold0() +; ATTRIBUTOR-NEXT: [[INC]] = add nuw nsw i32 [[I_021]], 1 +; ATTRIBUTOR-NEXT: [[EXITCOND_NOT:%.*]] = icmp eq i32 [[INC]], [[CALL12]] +; ATTRIBUTOR-NEXT: br i1 [[EXITCOND_NOT]], label [[IF_END16]], label [[FOR_BODY]] +; ATTRIBUTOR: if.end16: +; ATTRIBUTOR-NEXT: ret void +; +entry: + tail call void @not_cold0() + %tobool.not = icmp eq i32 %x, 0 + %call12 = tail call i32 @get_val() + br i1 %tobool.not, label %if.else11, label %if.then + +if.then: + %tobool1.not = icmp eq i32 %call12, 0 + br i1 %tobool1.not, label %if.else, label %if.then2 + +if.then2: + tail call void @cold1() + br label %if.end16 + +if.else: + %call3 = tail call i32 @get_val() + %tobool4.not = icmp eq i32 %call3, 0 + br i1 %tobool4.not, label %if.else6, label %if.then5 + +if.then5: + tail call void @cold0() + br label %if.end16 + +if.else6: + tail call void @not_cold0() + %call7 = tail call i32 @get_val() + switch i32 %call7, label %sw.default [ + i32 0, label %sw.bb + i32 1, label %sw.bb8 + i32 2, label %sw.bb9 + ] + +sw.bb: + tail call void @not_cold0() + br label %call_cold + +sw.bb8: + tail call void @not_cold1() + br label %call_cold + +sw.bb9: + tail call void @not_cold2() + br label %call_cold + +sw.default: + tail call void @cold0() + br label %if.end16 + +call_cold: + tail call void @cold_at_cb() cold + br label %if.end16 + +if.else11: + %cmp = icmp slt i32 %call12, 1 + br i1 %cmp, label %if.end14, label %for.body + +if.end14: + tail call void @cold1() + br label %if.end16 + +for.body: + %i.021 = phi i32 [ %inc, %for.body ], [ 0, %if.else11 ] + tail call void @cold0() + %inc = add nuw nsw i32 %i.021, 1 + %exitcond.not = icmp eq i32 %inc, %call12 + br i1 %exitcond.not, label %if.end16, label %for.body + +if.end16: + ret void +} + +define dso_local void @test_complex_fail(i32 noundef %x) { +; COMMON-LABEL: define dso_local void @test_complex_fail +; COMMON-SAME: (i32 noundef [[X:%.*]]) { +; COMMON-NEXT: entry: +; COMMON-NEXT: tail call void @not_cold0() +; COMMON-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; COMMON-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE11:%.*]], label [[IF_THEN:%.*]] +; COMMON: if.then: +; COMMON-NEXT: [[CALL:%.*]] = tail call i32 @get_val() +; COMMON-NEXT: [[TOBOOL1_NOT:%.*]] = icmp eq i32 [[CALL]], 0 +; COMMON-NEXT: br i1 [[TOBOOL1_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN2:%.*]] +; COMMON: if.then2: +; COMMON-NEXT: tail call void @cold1() +; COMMON-NEXT: br label [[IF_END12:%.*]] +; COMMON: if.else: +; COMMON-NEXT: [[CALL3:%.*]] = tail call i32 @get_val() +; COMMON-NEXT: [[TOBOOL4_NOT:%.*]] = icmp eq i32 [[CALL3]], 0 +; COMMON-NEXT: br i1 [[TOBOOL4_NOT]], label [[IF_ELSE6:%.*]], label [[IF_THEN5:%.*]] +; COMMON: if.then5: +; COMMON-NEXT: tail call void @cold0() +; COMMON-NEXT: br label [[IF_END12]] +; COMMON: if.else6: +; COMMON-NEXT: tail call void @not_cold0() +; COMMON-NEXT: [[CALL7:%.*]] = tail call i32 @get_val() +; COMMON-NEXT: switch i32 [[CALL7]], label [[SW_DEFAULT:%.*]] [ +; COMMON-NEXT: i32 0, label [[SW_BB:%.*]] +; COMMON-NEXT: i32 1, label [[SW_BB8:%.*]] +; COMMON-NEXT: i32 2, label [[SW_BB9:%.*]] +; COMMON-NEXT: ] +; COMMON: sw.bb: +; COMMON-NEXT: tail call void @not_cold0() +; COMMON-NEXT: br label [[CALL_COLD:%.*]] +; COMMON: sw.bb8: +; COMMON-NEXT: tail call void @not_cold1() +; COMMON-NEXT: br label [[CALL_COLD]] +; COMMON: sw.bb9: +; COMMON-NEXT: tail call void @not_cold2() +; COMMON-NEXT: br label [[IF_END12]] +; COMMON: sw.default: +; COMMON-NEXT: tail call void @cold0() +; COMMON-NEXT: br label [[IF_END12]] +; COMMON: call_cold: +; COMMON-NEXT: tail call void @cold_at_cb() #[[ATTR0:[0-9]+]] +; COMMON-NEXT: br label [[IF_END12]] +; COMMON: if.else11: +; COMMON-NEXT: tail call void @cold0() +; COMMON-NEXT: br label [[IF_END12]] +; COMMON: if.end12: +; COMMON-NEXT: ret void +; +entry: + tail call void @not_cold0() + %tobool.not = icmp eq i32 %x, 0 + br i1 %tobool.not, label %if.else11, label %if.then + +if.then: + %call = tail call i32 @get_val() + %tobool1.not = icmp eq i32 %call, 0 + br i1 %tobool1.not, label %if.else, label %if.then2 + +if.then2: + tail call void @cold1() + br label %if.end12 + +if.else: + %call3 = tail call i32 @get_val() + %tobool4.not = icmp eq i32 %call3, 0 + br i1 %tobool4.not, label %if.else6, label %if.then5 + +if.then5: + tail call void @cold0() + br label %if.end12 + +if.else6: + tail call void @not_cold0() + %call7 = tail call i32 @get_val() + switch i32 %call7, label %sw.default [ + i32 0, label %sw.bb + i32 1, label %sw.bb8 + i32 2, label %sw.bb9 + ] + +sw.bb: + tail call void @not_cold0() + br label %call_cold + +sw.bb8: + tail call void @not_cold1() + br label %call_cold + +sw.bb9: + tail call void @not_cold2() + br label %if.end12 + +sw.default: + tail call void @cold0() + br label %if.end12 + +call_cold: + tail call void @cold_at_cb() cold + br label %if.end12 + +if.else11: + tail call void @cold0() + br label %if.end12 + +if.end12: + ret void +} + +define dso_local void @test_complex2_fail(i32 noundef %x) { +; COMMON-LABEL: define dso_local void @test_complex2_fail +; COMMON-SAME: (i32 noundef [[X:%.*]]) { +; COMMON-NEXT: entry: +; COMMON-NEXT: tail call void @not_cold0() +; COMMON-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[X]], 0 +; COMMON-NEXT: [[CALL12:%.*]] = tail call i32 @get_val() +; COMMON-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_ELSE11:%.*]], label [[IF_THEN:%.*]] +; COMMON: if.then: +; COMMON-NEXT: [[TOBOOL1_NOT:%.*]] = icmp eq i32 [[CALL12]], 0 +; COMMON-NEXT: br i1 [[TOBOOL1_NOT]], label [[IF_ELSE:%.*]], label [[IF_THEN2:%.*]] +; COMMON: if.then2: +; COMMON-NEXT: tail call void @cold1() +; COMMON-NEXT: br label [[IF_END16:%.*]] +; COMMON: if.else: +; COMMON-NEXT: [[CALL3:%.*]] = tail call i32 @get_val() +; COMMON-NEXT: [[TOBOOL4_NOT:%.*]] = icmp eq i32 [[CALL3]], 0 +; COMMON-NEXT: br i1 [[TOBOOL4_NOT]], label [[IF_ELSE6:%.*]], label [[IF_THEN5:%.*]] +; COMMON: if.then5: +; COMMON-NEXT: tail call void @cold0() +; COMMON-NEXT: br label [[IF_END16]] +; COMMON: if.else6: +; COMMON-NEXT: tail call void @not_cold0() +; COMMON-NEXT: [[CALL7:%.*]] = tail call i32 @get_val() +; COMMON-NEXT: switch i32 [[CALL7]], label [[SW_DEFAULT:%.*]] [ +; COMMON-NEXT: i32 0, label [[SW_BB:%.*]] +; COMMON-NEXT: i32 1, label [[SW_BB8:%.*]] +; COMMON-NEXT: i32 2, label [[SW_BB9:%.*]] +; COMMON-NEXT: ] +; COMMON: sw.bb: +; COMMON-NEXT: tail call void @not_cold0() +; COMMON-NEXT: br label [[CALL_COLD:%.*]] +; COMMON: sw.bb8: +; COMMON-NEXT: tail call void @not_cold1() +; COMMON-NEXT: br label [[CALL_COLD]] +; COMMON: sw.bb9: +; COMMON-NEXT: tail call void @not_cold2() +; COMMON-NEXT: br label [[CALL_COLD]] +; COMMON: sw.default: +; COMMON-NEXT: tail call void @cold0() +; COMMON-NEXT: br label [[IF_END16]] +; COMMON: call_cold: +; COMMON-NEXT: tail call void @cold_at_cb() #[[ATTR0]] +; COMMON-NEXT: br label [[IF_END16]] +; COMMON: if.else11: +; COMMON-NEXT: [[CMP:%.*]] = icmp slt i32 [[CALL12]], 1 +; COMMON-NEXT: br i1 [[CMP]], label [[IF_END14:%.*]], label [[FOR_BODY:%.*]] +; COMMON: if.end14: +; COMMON-NEXT: tail call void @not_cold1() +; COMMON-NEXT: br label [[IF_END16]] +; COMMON: for.body: +; COMMON-NEXT: [[I_021:%.*]] = phi i32 [ [[INC:%.*]], [[FOR_BODY]] ], [ 0, [[IF_ELSE11]] ] +; COMMON-NEXT: tail call void @cold0() +; COMMON-NEXT: [[INC]] = add nuw nsw i32 [[I_021]], 1 +; COMMON-NEXT: [[EXITCOND_NOT:%.*]] = icmp eq i32 [[INC]], [[CALL12]] +; COMMON-NEXT: br i1 [[EXITCOND_NOT]], label [[IF_END16]], label [[FOR_BODY]] +; COMMON: if.end16: +; COMMON-NEXT: ret void +; +entry: + tail call void @not_cold0() + %tobool.not = icmp eq i32 %x, 0 + %call12 = tail call i32 @get_val() + br i1 %tobool.not, label %if.else11, label %if.then + +if.then: + %tobool1.not = icmp eq i32 %call12, 0 + br i1 %tobool1.not, label %if.else, label %if.then2 + +if.then2: + tail call void @cold1() + br label %if.end16 + +if.else: + %call3 = tail call i32 @get_val() + %tobool4.not = icmp eq i32 %call3, 0 + br i1 %tobool4.not, label %if.else6, label %if.then5 + +if.then5: + tail call void @cold0() + br label %if.end16 + +if.else6: + tail call void @not_cold0() + %call7 = tail call i32 @get_val() + switch i32 %call7, label %sw.default [ + i32 0, label %sw.bb + i32 1, label %sw.bb8 + i32 2, label %sw.bb9 + ] + +sw.bb: + tail call void @not_cold0() + br label %call_cold + +sw.bb8: + tail call void @not_cold1() + br label %call_cold + +sw.bb9: + tail call void @not_cold2() + br label %call_cold + +sw.default: + tail call void @cold0() + br label %if.end16 + +call_cold: + tail call void @cold_at_cb() cold + br label %if.end16 + +if.else11: + %cmp = icmp slt i32 %call12, 1 + br i1 %cmp, label %if.end14, label %for.body + +if.end14: + tail call void @not_cold1() + br label %if.end16 + +for.body: + %i.021 = phi i32 [ %inc, %for.body ], [ 0, %if.else11 ] + tail call void @cold0() + %inc = add nuw nsw i32 %i.021, 1 + %exitcond.not = icmp eq i32 %inc, %call12 + br i1 %exitcond.not, label %if.end16, label %for.body + +if.end16: + ret void +} + +;. +; FNATTRS: attributes #[[ATTR0]] = { cold } +; FNATTRS: attributes #[[ATTR1]] = { nofree norecurse noreturn nosync nounwind memory(none) } +; FNATTRS: attributes #[[ATTR2]] = { noreturn } +; FNATTRS: attributes #[[ATTR3]] = { cold noreturn } +; FNATTRS: attributes #[[ATTR4]] = { hot } +;. +; ATTRIBUTOR: attributes #[[ATTR0]] = { cold } +; ATTRIBUTOR: attributes #[[ATTR1]] = { nofree norecurse noreturn nosync nounwind memory(none) } +; ATTRIBUTOR: attributes #[[ATTR2]] = { noreturn } +; ATTRIBUTOR: attributes #[[ATTR3]] = { hot } +;.