diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -169,6 +169,7 @@ ///< enable code coverage analysis. CODEGENOPT(DumpCoverageMapping , 1, 0) ///< Dump the generated coverage mapping ///< regions. +CODEGENOPT(MisExpect , 1, 0) ///< Validate __builtin_expect with PGO counters /// If -fpcc-struct-return or -freg-struct-return is specified. ENUM_CODEGENOPT(StructReturnConvention, StructReturnConventionKind, 2, SRCK_Default) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -716,6 +716,11 @@ Group, Alias; def fno_auto_profile_accurate : Flag<["-"], "fno-auto-profile-accurate">, Group, Alias; +def fmisexpect : Flag<["-"], "fmisexpect">, + Group, Flags<[CC1Option]>, + HelpText<"Validate use of __builtin_expect with instrumentation data">; +def fno_misexpect : Flag<["-"], "fno-misexpect">, + Group, Flags<[CC1Option]>; def fdebug_compilation_dir : Separate<["-"], "fdebug-compilation-dir">, Group, Flags<[CC1Option, CC1AsOption, CoreOption]>, HelpText<"The compilation directory to embed in the debug info.">; diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -14,6 +14,7 @@ #include "CGDebugInfo.h" #include "CodeGenModule.h" #include "TargetInfo.h" +#include "MisExpect.h" #include "clang/AST/StmtVisitor.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/PrettyStackTrace.h" @@ -1284,8 +1285,16 @@ llvm::BasicBlock *CaseDest = createBasicBlock("sw.bb"); EmitBlockWithFallThrough(CaseDest, &S); - if (SwitchWeights) + size_t MapIndex = 0; + if (SwitchWeights) { SwitchWeights->push_back(getProfileCount(&S)); + if (CaseMap) { + MapIndex = SwitchWeights->size() - 1; + llvm::ConstantInt *CaseVal = + Builder.getInt(S.getLHS()->EvaluateKnownConstInt(getContext())); + (*CaseMap)[CaseVal->getSExtValue()] = MapIndex; + } + } SwitchInsn->addCase(CaseVal, CaseDest); // Recursively emitting the statement is acceptable, but is not wonderful for @@ -1306,8 +1315,15 @@ llvm::ConstantInt *CaseVal = Builder.getInt(CurCase->getLHS()->EvaluateKnownConstInt(getContext())); - if (SwitchWeights) + if (SwitchWeights) { SwitchWeights->push_back(getProfileCount(NextCase)); + if (CaseMap) { + // only the original statement gets a counter, so map back to it's index + // in SwitchWeights + (*CaseMap)[CaseVal->getSExtValue()] = MapIndex; + } + } + if (CGM.getCodeGenOpts().hasProfileClangInstr()) { CaseDest = createBasicBlock("sw.bb"); EmitBlockWithFallThrough(CaseDest, &S); @@ -1575,6 +1591,7 @@ // Handle nested switch statements. llvm::SwitchInst *SavedSwitchInsn = SwitchInsn; SmallVector *SavedSwitchWeights = SwitchWeights; + llvm::DenseMap *SavedCaseMap = CaseMap; llvm::BasicBlock *SavedCRBlock = CaseRangeBlock; // See if we can constant fold the condition of the switch and therefore only @@ -1646,6 +1663,8 @@ } SwitchWeights = new SmallVector(); SwitchWeights->reserve(NumCases); + CaseMap = new llvm::DenseMap(); + CaseMap->reserve(NumCases); // The default needs to be first. We store the edge count, so we already // know the right weight. SwitchWeights->push_back(DefaultCount); @@ -1698,10 +1717,15 @@ auto *Call = dyn_cast(S.getCond()); if (Call && CGM.getCodeGenOpts().OptimizationLevel != 0) { auto *FD = dyn_cast_or_null(Call->getCalleeDecl()); - if (FD && FD->getBuiltinID() == Builtin::BI__builtin_unpredictable) { - llvm::MDBuilder MDHelper(getLLVMContext()); - SwitchInsn->setMetadata(llvm::LLVMContext::MD_unpredictable, - MDHelper.createUnpredictable()); + if (FD) { + if (FD->getBuiltinID() == Builtin::BI__builtin_unpredictable) { + llvm::MDBuilder MDHelper(getLLVMContext()); + SwitchInsn->setMetadata(llvm::LLVMContext::MD_unpredictable, + MDHelper.createUnpredictable()); + } else if (CGM.getCodeGenOpts().MisExpect && + FD->getBuiltinID() == Builtin::BI__builtin_expect) { + MisExpect::CheckMisExpectSwitch(Call, SwitchWeights, CaseMap, CGM); + } } } @@ -1714,8 +1738,14 @@ createProfileWeights(*SwitchWeights)); delete SwitchWeights; } + + if (CaseMap) { + delete CaseMap; + } + SwitchInsn = SavedSwitchInsn; SwitchWeights = SavedSwitchWeights; + CaseMap = SavedCaseMap; CaseRangeBlock = SavedCRBlock; } diff --git a/clang/lib/CodeGen/CMakeLists.txt b/clang/lib/CodeGen/CMakeLists.txt --- a/clang/lib/CodeGen/CMakeLists.txt +++ b/clang/lib/CodeGen/CMakeLists.txt @@ -87,6 +87,7 @@ ItaniumCXXABI.cpp MacroPPCallbacks.cpp MicrosoftCXXABI.cpp + MisExpect.cpp ModuleBuilder.cpp ObjectFilePCHContainerOperations.cpp PatternInit.cpp diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1370,6 +1370,8 @@ llvm::SwitchInst *SwitchInsn = nullptr; /// The branch weights of SwitchInsn when doing instrumentation based PGO. SmallVector *SwitchWeights = nullptr; + llvm::DenseMap *CaseMap = nullptr; + /// CaseRangeBlock - This block holds if condition check for last case /// statement range in current switch instruction. diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -20,6 +20,7 @@ #include "CodeGenModule.h" #include "CodeGenPGO.h" #include "TargetInfo.h" +#include "MisExpect.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/Decl.h" @@ -1346,8 +1347,6 @@ return true; } - - /// EmitBranchOnBoolExpr - Emit a branch on a boolean condition (e.g. for an if /// statement) to the specified blocks. Based on the condition, this might try /// to simplify the codegen of the conditional based on the branch. @@ -1358,6 +1357,8 @@ uint64_t TrueCount) { Cond = Cond->IgnoreParens(); + MisExpect::CheckMisExpectBranch(Cond, TrueCount, getCurrentProfileCount(), CGM); + if (const BinaryOperator *CondBOp = dyn_cast(Cond)) { // Handle X && Y in a condition. diff --git a/clang/lib/CodeGen/MisExpect.h b/clang/lib/CodeGen/MisExpect.h new file mode 100644 --- /dev/null +++ b/clang/lib/CodeGen/MisExpect.h @@ -0,0 +1,52 @@ +//===--- MisExpect.h - Check Use of __builtin_expect() with PGO data ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This contains code to emit warnings for potentially incorrect usage of +// __builtin_expect(). It uses PGO profiles to validation. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CODEGEN_MISEXPECT_H +#define LLVM_CLANG_LIB_CODEGEN_MISEXPECT_H + +#include "CodeGenModule.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/Optional.h" + +namespace clang { +namespace CodeGen { +namespace MisExpect { + +/// getExpectedValue - Returns the value that __builtin_expect() is expecting. +/// If the second parameter cannot be evaluated at compile-time, returns an +/// empty Optional. Returns None when Call is not __builtin_expect() +Optional getExpectedValue(const clang::CallExpr *Call, + ASTContext &Context); + +/// CheckMisExpectBranch - check if a branch is annotated with +/// __builtin_expect and when using profiling data, verify that the profile +/// agrees with the use of the annotation + +void CheckMisExpectBranch(const Expr *Cond, uint64_t TrueCount, + uint64_t CurrProfCount, CodeGenModule &CGM); + +/// CheckMisExpect - check if a branch is annotated with __builtin_expect and +/// when using profiling data, verify that the profile agrees with the use of +/// the annotation +void CheckMisExpectSwitch(const CallExpr *Call, + llvm::SmallVector *SwitchWeights, + llvm::DenseMap *CaseMap, + CodeGenModule &CGM); + +} // namespace MisExpect +} // namespace CodeGen +} // namespace clang + +#endif // LLVM_CLANG_LIB_CODEGEN_MISEXPECT_H diff --git a/clang/lib/CodeGen/MisExpect.cpp b/clang/lib/CodeGen/MisExpect.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/CodeGen/MisExpect.cpp @@ -0,0 +1,183 @@ +//===--- MisExpect.cpp - Check Use of __builtin_expect() with PGO data ----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This contains code to emit warnings for potentially incorrect usage of +// __builtin_expect(). It uses PGO profiles to validation. +// +//===----------------------------------------------------------------------===// + +#include "MisExpect.h" +#include "CodeGenModule.h" +#include "clang/Basic/Builtins.h" +#include "clang/Basic/CodeGenOptions.h" +#include "clang/Basic/Diagnostic.h" +#include "llvm/Support/BranchProbability.h" + +#include +#include + +namespace { + +using namespace clang; +using namespace clang::CodeGen; +// Emit Warning notifiying user that the current PGO counter is a mismatch with +// the use of __builtin_expect() +void EmitMisExpectWarning(const clang::CallExpr *Call, CodeGenModule &CGM, + unsigned PercentageCorrect) { + SourceLocation ExprLoc = Call->getBeginLoc(); + unsigned DiagID = CGM.getDiags().getCustomDiagID( + DiagnosticsEngine::Warning, + "Potential performance regression from use of __builtin_expect(): " + "Annotation was correct on %0%% of profiled executions."); + + CGM.getDiags() + .Report(ExprLoc, DiagID) + .AddTaggedVal(PercentageCorrect, + clang::DiagnosticsEngine::ArgumentKind::ak_uint); +} + +// Prints some debug diagnostics useful when checking SwitchStmts. +// Allows for simple comparison of the Case Value mappings to their index in the +// SwitchWeights datastructure in CGStmts.cpp +void DebugPrintMisExpectSwitchInfo(SmallVector *SwitchWeights, + llvm::DenseMap *CaseMap) { + auto size = SwitchWeights->size(); + for (size_t i = 0; i < size; ++i) { + llvm::dbgs() << "Index: " << i << "\tProfile Value:\t" + << (*SwitchWeights)[i] << "\n"; + } + + llvm::dbgs() << "------------------\n"; + + for (auto &item : *CaseMap) { + llvm::dbgs() << "Case Key: " << item.first << "\tRelated Index:\t" + << item.second << "\n"; + } + + llvm::dbgs() << "------------------\n"; + uint64_t CaseTotal = + std::accumulate(SwitchWeights->begin(), SwitchWeights->end(), 0); + llvm::dbgs() << "Switch Profile Count:\t" << CaseTotal << "\n"; +} + +} // namespace + +namespace clang { +namespace CodeGen { +namespace MisExpect { + +#define DEBUG_TYPE "misexpect" +Optional getExpectedValue(const clang::CallExpr *Call, + ASTContext &Context) { + if (!Call) + return Optional(llvm::None); + + auto *FD = dyn_cast_or_null(Call->getCalleeDecl()); + if (!FD || FD->getBuiltinID() != Builtin::BI__builtin_expect) { + return Optional(llvm::None); + } + + // Check if we can evaluate the 2nd parameter to __builtin_expect(expr,long) + // since it may not be able to be evaluated at compile-time + Expr::EvalResult ExprResult; + auto Arg = Call->getArg(1); + Arg->EvaluateAsInt(ExprResult, Context, Expr::SE_AllowSideEffects); + + if (!ExprResult.Val.hasValue()) + return Optional(llvm::None); + + llvm::APSInt &Into = ExprResult.Val.getInt(); + int64_t ExpectedVal = Into.getExtValue(); + return ExpectedVal; +} + +void CheckMisExpectBranch(const Expr *Cond, uint64_t TrueCount, + uint64_t CurrProfCount, CodeGenModule &CGM) { + if (!CGM.getCodeGenOpts().MisExpect || + CGM.getCodeGenOpts().getProfileUse() == CodeGenOptions::ProfileNone) + return; + + auto *Call = dyn_cast(Cond->IgnoreImpCasts()); + + Optional ExpectedValOpt = getExpectedValue(Call, CGM.getContext()); + + if (!ExpectedValOpt.hasValue()) + return; + + const long ExpectedVal = ExpectedValOpt.getValue(); + const bool ExpectedTrueBranch = (ExpectedVal != 0); + + LLVM_DEBUG(llvm::dbgs() << "Expected Value:\t" << ExpectedVal << "\n"); + LLVM_DEBUG(llvm::dbgs() << "Current Count:\t" << CurrProfCount << "\n"); + LLVM_DEBUG(llvm::dbgs() << "True Count:\t" << TrueCount << "\n"); + + bool IncorrectPerfCounters = false; + uint64_t Scaled; + unsigned Percentage; + + // TODO: determine better heuristics than hot/cold function thresholds + // Currenlty {Hot|Cold}FunctionThreshold is taken from + // PGOInstrumentation.cpp:1084 + if (ExpectedTrueBranch) { + const llvm::BranchProbability HotFunctionThreshold(1, 100); + Scaled = HotFunctionThreshold.scale(CurrProfCount); + Percentage = (TrueCount / (float)CurrProfCount) * 100; + if (TrueCount < Scaled) + IncorrectPerfCounters = true; + } else { + const llvm::BranchProbability ColdFunctionThreshold(2, 10000); + Scaled = ColdFunctionThreshold.scale(CurrProfCount); + Percentage = ((CurrProfCount - TrueCount) / (float)CurrProfCount) * 100; + if (TrueCount > Scaled) + IncorrectPerfCounters = true; + } + LLVM_DEBUG(llvm::dbgs() << "Scaled Count:\t" << Scaled << "\n"); + + if (IncorrectPerfCounters) + EmitMisExpectWarning(Call, CGM, Percentage); +} + +void CheckMisExpectSwitch(const CallExpr *Call, + SmallVector *SwitchWeights, + llvm::DenseMap *CaseMap, + CodeGenModule &CGM) { + if (!SwitchWeights || !CaseMap) + return; + + Optional ExpectedValOpt = getExpectedValue(Call, CGM.getContext()); + + if (!ExpectedValOpt.hasValue()) + return; + + LLVM_DEBUG(DebugPrintMisExpectSwitchInfo(SwitchWeights, CaseMap)); + const long ExpectedVal = ExpectedValOpt.getValue(); + + LLVM_DEBUG(llvm::dbgs() << "Expected Value:\t" << ExpectedVal << "\n"); + + uint64_t Max = + *std::max_element(SwitchWeights->begin(), SwitchWeights->end()); + + auto MapIndex = CaseMap->find(ExpectedVal); + + uint64_t Index = (MapIndex != CaseMap->end()) ? MapIndex->second : 0; + uint64_t TakenCount = (*SwitchWeights)[Index]; + + LLVM_DEBUG(llvm::dbgs() << "Taken Count:\t" << TakenCount << "\n"); + LLVM_DEBUG(llvm::dbgs() << "Max Count:\t" << Max << "\n"); + uint64_t CaseTotal = + std::accumulate(SwitchWeights->begin(), SwitchWeights->end(), 0); + unsigned Percentage = ((float)TakenCount / (float)CaseTotal) * 100; + + if (TakenCount < Max) + EmitMisExpectWarning(Call, CGM, Percentage); +} +#undef DEBUG_TYPE + +} // namespace MisExpect +} // namespace CodeGen +} // namespace clang diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -3998,6 +3998,11 @@ Args.AddLastArg(CmdArgs, options::OPT_ffine_grained_bitfield_accesses, options::OPT_fno_fine_grained_bitfield_accesses); + if (Args.hasFlag(options::OPT_fmisexpect, options::OPT_fno_misexpect, + false)) { + CmdArgs.push_back("-fmisexpect"); + } + // Handle segmented stacks. if (Args.hasArg(options::OPT_fsplit_stack)) CmdArgs.push_back("-split-stacks"); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -809,6 +809,7 @@ << Args.getLastArg(OPT_fprofile_remapping_file_EQ)->getAsString(Args) << "-fexperimental-new-pass-manager"; } + Opts.MisExpect = Args.hasFlag(OPT_fmisexpect, OPT_fno_misexpect, false); Opts.CoverageMapping = Args.hasFlag(OPT_fcoverage_mapping, OPT_fno_coverage_mapping, false); diff --git a/clang/test/Profile/Inputs/misexpect-branch-nonconst-expect-arg.proftext b/clang/test/Profile/Inputs/misexpect-branch-nonconst-expect-arg.proftext new file mode 100644 --- /dev/null +++ b/clang/test/Profile/Inputs/misexpect-branch-nonconst-expect-arg.proftext @@ -0,0 +1,36 @@ +bar +# Func Hash: +11262309464 +# Num Counters: +2 +# Counter Values: +200000 +2 + +baz +# Func Hash: +24 +# Num Counters: +1 +# Counter Values: +2 + +foo +# Func Hash: +635992 +# Num Counters: +2 +# Counter Values: +199998 +5096876 + +main +# Func Hash: +303943193688 +# Num Counters: +3 +# Counter Values: +1 +2000 +200000 + diff --git a/clang/test/Profile/Inputs/misexpect-branch.proftext b/clang/test/Profile/Inputs/misexpect-branch.proftext new file mode 100644 --- /dev/null +++ b/clang/test/Profile/Inputs/misexpect-branch.proftext @@ -0,0 +1,36 @@ +bar +# Func Hash: +45795613684824 +# Num Counters: +2 +# Counter Values: +200000 +0 + +baz +# Func Hash: +24 +# Num Counters: +1 +# Counter Values: +0 + +foo +# Func Hash: +635992 +# Num Counters: +2 +# Counter Values: +200000 +5095099 + +main +# Func Hash: +303943193688 +# Num Counters: +3 +# Counter Values: +1 +2000 +200000 + diff --git a/clang/test/Profile/Inputs/misexpect-switch-default-only.proftext b/clang/test/Profile/Inputs/misexpect-switch-default-only.proftext new file mode 100644 --- /dev/null +++ b/clang/test/Profile/Inputs/misexpect-switch-default-only.proftext @@ -0,0 +1,40 @@ +init_arry +# Func Hash: +18129 +# Num Counters: +2 +# Counter Values: +1 +25 + +main +# Func Hash: +79676873694057560 +# Num Counters: +5 +# Counter Values: +1 +20 +20000 +20000 +20000 + +random_sample +# Func Hash: +19458874217560 +# Num Counters: +3 +# Counter Values: +20000 +500000 +100006 + +sum +# Func Hash: +1160280 +# Num Counters: +2 +# Counter Values: +0 +0 + diff --git a/clang/test/Profile/Inputs/misexpect-switch.proftext b/clang/test/Profile/Inputs/misexpect-switch.proftext new file mode 100644 --- /dev/null +++ b/clang/test/Profile/Inputs/misexpect-switch.proftext @@ -0,0 +1,45 @@ +init_arry +# Func Hash: +18129 +# Num Counters: +2 +# Counter Values: +1 +25 + +main +# Func Hash: +1965403898329309329 +# Num Counters: +10 +# Counter Values: +1 +20 +20000 +20000 +5 +19995 +0 +0 +0 +0 + +random_sample +# Func Hash: +19458874217560 +# Num Counters: +3 +# Counter Values: +19995 +499875 +100042 + +sum +# Func Hash: +1160280 +# Num Counters: +2 +# Counter Values: +5 +125 + diff --git a/clang/test/Profile/misexpect-branch-cold.c b/clang/test/Profile/misexpect-branch-cold.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-branch-cold.c @@ -0,0 +1,51 @@ +// Test that misexpect detects mis-annotated branches + +// RUN: llvm-profdata merge %S/Inputs/misexpect-branch.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -fmisexpect + +// expected-no-diagnostics +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +int foo(int); +int bar(); +int baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +int main() { + int val = 0; + for (int i = 0; i < outer_loop; ++i) { + for (int i = 0; i < inner_loop; ++i) { + val = bar(); + } + } + return 0; +} + +int bar() { + int rando = buzz(); + int x = 0; + if (unlikely(rando % (outer_loop * inner_loop) == 0)) { + x = baz(rando); + } else { + x = foo(50); + } + return x; +} + +int foo(int i) { + int j = 0; + while (i < 100) { + i += buzz() % 5; + j++; + } + return j; +} + +int baz(int rando) { + int x = rando; + return x; +} diff --git a/clang/test/Profile/misexpect-branch-nonconst-expected-val.c b/clang/test/Profile/misexpect-branch-nonconst-expected-val.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-branch-nonconst-expected-val.c @@ -0,0 +1,51 @@ +// Test that misexpect detects mis-annotated branches + +// RUN: llvm-profdata merge %S/Inputs/misexpect-branch-nonconst-expect-arg.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -fmisexpect + +// expected-no-diagnostics +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +int foo(int); +int bar(); +int baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +int main() { + int val = 0; + for (int i = 0; i < outer_loop; ++i) { + for (int i = 0; i < inner_loop; ++i) { + val = bar(); + } + } + return 0; +} + +int bar() { + int rando = buzz(); + int x = 0; + if (__builtin_expect(rando % (outer_loop * inner_loop) == 0, buzz())) { + x = baz(rando); + } else { + x = foo(50); + } + return x; +} + +int foo(int i) { + int j = 0; + while (i < 100) { + i += buzz() % 5; + j++; + } + return j; +} + +int baz(int rando) { + int x = rando; + return x; +} diff --git a/clang/test/Profile/misexpect-branch.c b/clang/test/Profile/misexpect-branch.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-branch.c @@ -0,0 +1,50 @@ +// Test that misexpect detects mis-annotated branches + +// RUN: llvm-profdata merge %S/Inputs/misexpect-branch.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -fmisexpect + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +int foo(int); +int bar(); +int baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +int main() { + int val = 0; + for (int i = 0; i < outer_loop; ++i) { + for (int i = 0; i < inner_loop; ++i) { + val = bar(); + } + } + return 0; +} + +int bar() { + int rando = buzz(); + int x = 0; + if (likely(rando % (outer_loop * inner_loop) == 0)) { // expected-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% of profiled executions.}} + x = baz(rando); + } else { + x = foo(50); + } + return x; +} + +int foo(int i) { + int j = 0; + while (i < 100) { + i += buzz() % 5; + j++; + } + return j; +} + +int baz(int rando) { + int x = rando; + return x; +} diff --git a/clang/test/Profile/misexpect-no-warning-without-flag.c b/clang/test/Profile/misexpect-no-warning-without-flag.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-no-warning-without-flag.c @@ -0,0 +1,51 @@ +// Test that misexpect detects mis-annotated branches + +// RUN: llvm-profdata merge %S/Inputs/misexpect-branch.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify + +// expected-no-diagnostics +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +int foo(int); +int bar(); +int baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +int main() { + int val = 0; + for (int i = 0; i < outer_loop; ++i) { + for (int i = 0; i < inner_loop; ++i) { + val = bar(); + } + } + return 0; +} + +int bar() { + int rando = buzz(); + int x = 0; + if (likely(rando % (outer_loop * inner_loop) == 0)) { + x = baz(rando); + } else { + x = foo(50); + } + return x; +} + +int foo(int i) { + int j = 0; + while (i < 100) { + i += buzz() % 5; + j++; + } + return j; +} + +int baz(int rando) { + int x = rando; + return x; +} diff --git a/clang/test/Profile/misexpect-switch-default.c b/clang/test/Profile/misexpect-switch-default.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-switch-default.c @@ -0,0 +1,68 @@ +// Test that misexpect detects mis-annotated switch statements + +// RUN: llvm-profdata merge %S/Inputs/misexpect-switch.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -fmisexpect + +int sum(int *buff, int size); +int random_sample(int *buff, int size); +int rand(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +void init_arry() { + int i; + for (i = 0; i < arry_size; ++i) { + arry[i] = rand() % 10; + } +} + +int main() { + init_arry(); + int val = 0; + + int j, k; + for (j = 0; j < outer_loop; ++j) { + for (k = 0; k < inner_loop; ++k) { + unsigned condition = rand() % 5; + switch (__builtin_expect(condition, 6)) { // expected-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% of profiled executions.}} + case 0: + val += sum(arry, arry_size); + break; + case 1: + case 2: + case 3: + case 4: + val += random_sample(arry, arry_size); + break; + default: + __builtin_unreachable(); + } // end switch + } // end inner_loop + } // end outer_loop + + return 0; +} + +int sum(int *buff, int size) { + int total = 0; + int i = 0; + for (i = 0; i < size; ++i) { + total += buff[i]; + } + return total; +} + +int random_sample(int *buff, int size) { + int total = 0; + int i; + for (i = 0; i < size; ++i) { + if (rand() % 5 == 0) + total += buff[i]; + } + + return total; +} diff --git a/clang/test/Profile/misexpect-switch-nonconst.c b/clang/test/Profile/misexpect-switch-nonconst.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-switch-nonconst.c @@ -0,0 +1,69 @@ +// Test that misexpect detects mis-annotated switch statements + +// RUN: llvm-profdata merge %S/Inputs/misexpect-switch.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -fmisexpect + +// expected-no-diagnostics +int sum(int *buff, int size); +int random_sample(int *buff, int size); +int rand(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +void init_arry() { + int i; + for (i = 0; i < arry_size; ++i) { + arry[i] = rand() % 10; + } +} + +int main() { + init_arry(); + int val = 0; + + int j, k; + for (j = 0; j < outer_loop; ++j) { + for (k = 0; k < inner_loop; ++k) { + unsigned condition = rand() % 10000; + switch (__builtin_expect(condition, rand())) { + case 0: + val += sum(arry, arry_size); + break; + case 1: + case 2: + case 3: + case 4: + val += random_sample(arry, arry_size); + break; + default: + __builtin_unreachable(); + } // end switch + } // end inner_loop + } // end outer_loop + + return 0; +} + +int sum(int *buff, int size) { + int total = 0; + int i = 0; + for (i = 0; i < size; ++i) { + total += buff[i]; + } + return total; +} + +int random_sample(int *buff, int size) { + int total = 0; + int i; + for (i = 0; i < size; ++i) { + if (rand() % 5 == 0) + total += buff[i]; + } + + return total; +} diff --git a/clang/test/Profile/misexpect-switch-only-default-case.c b/clang/test/Profile/misexpect-switch-only-default-case.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-switch-only-default-case.c @@ -0,0 +1,62 @@ +// Test that misexpect detects mis-annotated switch statements + +// RUN: llvm-profdata merge %S/Inputs/misexpect-switch-default-only.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -fmisexpect + +// expected-no-diagnostics +int sum(int *buff, int size); +int random_sample(int *buff, int size); +int rand(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +void init_arry() { + int i; + for (i = 0; i < arry_size; ++i) { + arry[i] = rand() % 10; + } +} + +int main() +{ + init_arry(); + int val = 0; + + int j, k; + for (j = 0; j < outer_loop; ++j) { + for (k = 0; k < inner_loop; ++k) { + unsigned condition = rand() % 10000; + switch (__builtin_expect(condition, 0)) { + default: + val += random_sample(arry, arry_size); + break; + }; // end switch + } // end inner_loop + } // end outer_loop + + return 0; +} + +int sum(int *buff, int size) { + int total = 0; + int i = 0; + for (i = 0; i < size; ++i) { + total += buff[i]; + } + return total; +} + +int random_sample(int *buff, int size) { + int total = 0; + int i; + for (i = 0; i < size; ++i) { + if (rand() % 5 == 0) + total += buff[i]; + } + + return total; +} diff --git a/clang/test/Profile/misexpect-switch.c b/clang/test/Profile/misexpect-switch.c new file mode 100644 --- /dev/null +++ b/clang/test/Profile/misexpect-switch.c @@ -0,0 +1,68 @@ +// Test that misexpect detects mis-annotated switch statements + +// RUN: llvm-profdata merge %S/Inputs/misexpect-switch.proftext -o %t.profdata +// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -fmisexpect + +int sum(int *buff, int size); +int random_sample(int *buff, int size); +int rand(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +void init_arry() { + int i; + for (i = 0; i < arry_size; ++i) { + arry[i] = rand() % 10; + } +} + +int main() { + init_arry(); + int val = 0; + + int j, k; + for (j = 0; j < outer_loop; ++j) { + for (k = 0; k < inner_loop; ++k) { + unsigned condition = rand() % 10000; + switch (__builtin_expect(condition, 0)) { // expected-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% of profiled executions.}} + case 0: + val += sum(arry, arry_size); + break; + case 1: + case 2: + case 3: + case 4: + val += random_sample(arry, arry_size); + break; + default: + __builtin_unreachable(); + } // end switch + } // end inner_loop + } // end outer_loop + + return 0; +} + +int sum(int *buff, int size) { + int total = 0; + int i = 0; + for (i = 0; i < size; ++i) { + total += buff[i]; + } + return total; +} + +int random_sample(int *buff, int size) { + int total = 0; + int i; + for (i = 0; i < size; ++i) { + if (rand() % 5 == 0) + total += buff[i]; + } + + return total; +}