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" @@ -1698,10 +1699,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, SwitchInsn, SwitchWeights, CGM); + } } } 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.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" @@ -33,6 +34,7 @@ #include "clang/Frontend/FrontendDiagnostic.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/Dominators.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/MDBuilder.h" #include "llvm/IR/Operator.h" @@ -1346,8 +1348,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. @@ -1544,7 +1544,9 @@ ApplyDebugLocation DL(*this, Cond); CondV = EvaluateExprAsBool(Cond); } - Builder.CreateCondBr(CondV, TrueBlock, FalseBlock, Weights, Unpredictable); + llvm::BranchInst *BI = Builder.CreateCondBr(CondV, TrueBlock, FalseBlock, + Weights, Unpredictable); + MisExpect::CheckMisExpectBranch(Cond, BI, TrueCount, CurrentCount, CGM); } /// ErrorUnsupported - Print out an error that codegen doesn't support the 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,55 @@ +//===--- 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 for 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 { + +/// 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 +/// \param Cond the conditional expression being checked +/// \param TrueCount the profile counter for this block +/// \param CurrProfCount the current total profile count +/// \param CGM a reference to the current CodeGenModule +void CheckMisExpectBranch(const Expr *Cond, const llvm::BranchInst *BI, + 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 +/// \param Call the call expression to __builtin_expect() +/// \param SwitchWeights pointer to a vector of profile counts for each case arm +/// \param CaseMap a table mapping the constant value of a case target to its +/// index in the SwitchWeights vector +/// \param CGM a reference to the current CodeGenModule +void CheckMisExpectSwitch(const CallExpr *Call, + llvm::SwitchInst *SwitchInstruction, + llvm::SmallVector *SwitchWeights, + 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,236 @@ +//===--- 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 for validation. +// +//===----------------------------------------------------------------------===// + +#include "MisExpect.h" +#include "CodeGenModule.h" +#include "clang/Basic/Builtins.h" +#include "clang/Basic/CodeGenOptions.h" +#include "clang/Basic/Diagnostic.h" +#include "llvm/ADT/Optional.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Instructions.h" +#include "llvm/Support/BranchProbability.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" + +#include +#include +#include +#include + +namespace { + +using namespace clang; +using namespace clang::CodeGen; + +struct SwitchDebugInfo { + SmallVector *SwitchWeights; + long ExpectedVal; + uint64_t Index; + uint64_t TakenCount; + uint64_t Max; + uint64_t ScaledThreshold; + float Percentage; +}; + +// Emit Warning notifying user that the current PGO counter is a mismatch with +// the use of __builtin_expect() +// \param PercentageCorrect the percentage the expected target of +// __builtin_expect() was taken during profiling as an integer +void EmitMisExpectWarning(const clang::CallExpr *Call, CodeGenModule &CGM, + float 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."); + + auto PercentStr = llvm::formatv("{0:P}", PercentageCorrect); + CGM.getDiags().Report(ExprLoc, DiagID).AddString(PercentStr.str()); +} + +// Prints some debug diagnostics useful when checking SwitchStmts. +// Allows for simple comparison of the Case Value mappings to their index in the +// SwitchWeights data structure in CGStmts.cpp +void DebugPrintMisExpectSwitchInfo(SwitchDebugInfo SDI) { + auto size = SDI.SwitchWeights->size(); + llvm::dbgs() << "------------------\n"; + for (size_t i = 0; i < size; ++i) { + llvm::dbgs() << "Index: " << i << "\tProfile Value:\t" + << (*SDI.SwitchWeights)[i] << "\n"; + } + + uint64_t CaseTotal = + std::accumulate(SDI.SwitchWeights->begin(), SDI.SwitchWeights->end(), 0); + llvm::dbgs() << "Profile Count:\t" << CaseTotal << "\n"; + llvm::dbgs() << "Expected Value:\t" << SDI.ExpectedVal << "\n"; + llvm::dbgs() << "Expected Index:\t" << SDI.Index << "\n"; + llvm::dbgs() << "Taken Count:\t" << SDI.TakenCount << "\n"; + llvm::dbgs() << "Max Count:\t" << SDI.Max << "\n"; + llvm::dbgs() << "Threshold:\t" << SDI.ScaledThreshold << "\n"; + llvm::dbgs() << llvm::formatv("Ratio: {0}/{1} = {2:P}\n", SDI.TakenCount, + CaseTotal, SDI.Percentage); + llvm::dbgs() << "------------------\n"; +} + +// getExpectedValue - Returns the value that __builtin_expect() is expecting. +// \param BI the branch instruction being checked +// \return None if second parameter to __builtin_expect() cannot be evaluated at +// compile-time, else returns an empty Optional. +template +llvm::Optional getExpectedValue(const BrSwtchInst *BI) { + // TODO: consider moving funciton into a support lib to improve code reuse + if (!BI) + return llvm::Optional(llvm::None); + + llvm::CallInst *CI = nullptr; + llvm::ICmpInst *CmpI = dyn_cast(BI->getCondition()); + llvm::ConstantInt *CmpConstOperand = nullptr; + + if (!CmpI) { + CI = dyn_cast(BI->getCondition()); + } else { + CmpConstOperand = dyn_cast(CmpI->getOperand(1)); + if (!CmpConstOperand) + return llvm::Optional(llvm::None); + CI = dyn_cast(CmpI->getOperand(0)); + } + + if (!CI) + return llvm::Optional(llvm::None); + + llvm::Function *Fn = CI->getCalledFunction(); + if (!Fn || Fn->getIntrinsicID() != llvm::Intrinsic::expect) + return Optional(llvm::None); + + llvm::ConstantInt *ExpectedValue = + dyn_cast(CI->getArgOperand(1)); + + if (!ExpectedValue) + return Optional(llvm::None); + + return ExpectedValue; +} + +} // namespace + +namespace clang { +namespace CodeGen { +namespace MisExpect { + +// TODO: see LowerExpectIntrinsic.cpp for notes on sharing these constants +const uint32_t LikelyBranchWeight = 2000; +const uint32_t UnlikelyBranchWeight = 1; + +#define DEBUG_TYPE "misexpect" + +void CheckMisExpectBranch(const Expr *Cond, const llvm::BranchInst *BI, + uint64_t TrueCount, uint64_t CurrProfCount, + CodeGenModule &CGM) { + if (!CGM.getCodeGenOpts().MisExpect || + CGM.getCodeGenOpts().getProfileUse() == CodeGenOptions::ProfileNone) + return; + + auto *Call = dyn_cast(Cond->IgnoreImpCasts()); + auto Exp = getExpectedValue(BI); + + if (!Call || !Exp.hasValue()) + return; + + const long ExpectedVal = Exp.getValue()->getZExtValue(); + const bool ExpectedTrueBranch = (ExpectedVal != 0); + bool IncorrectPerfCounters = false; + uint64_t Scaled; + float Percentage; + const uint64_t TotalBranchWeight = LikelyBranchWeight + UnlikelyBranchWeight; + + // TODO: determine better heuristics than hot/cold function thresholds + // LowerExpectIntrinsics.cpp:49 LikelyBranchWeight = 2000 + // LowerExpectIntrinsics.cpp:52 UnlikelyBranchWeight = 1 + if (ExpectedTrueBranch) { + const llvm::BranchProbability LikelyThreshold(LikelyBranchWeight, + TotalBranchWeight); + Scaled = LikelyThreshold.scale(CurrProfCount); + Percentage = (TrueCount / (float)CurrProfCount) * 100; + if (TrueCount < Scaled) + IncorrectPerfCounters = true; + } else { + const llvm::BranchProbability UnlikelyThreshold(UnlikelyBranchWeight, + LikelyBranchWeight); + Scaled = UnlikelyThreshold.scale(CurrProfCount); + Percentage = ((CurrProfCount - TrueCount) / (float)CurrProfCount); + if (TrueCount > Scaled) + IncorrectPerfCounters = true; + } + + LLVM_DEBUG(llvm::dbgs() << "------------------\n"); + 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"); + LLVM_DEBUG(llvm::dbgs() << "Scaled Count:\t" << Scaled << "\n"); + LLVM_DEBUG(llvm::dbgs() << "------------------\n"); + + if (IncorrectPerfCounters) + EmitMisExpectWarning(Call, CGM, Percentage); +} + +void CheckMisExpectSwitch(const CallExpr *Call, llvm::SwitchInst *SI, + SmallVector *SwitchWeights, + CodeGenModule &CGM) { + if (!SwitchWeights) + return; + + Optional ExpectedValOpt = getExpectedValue(SI); + + if (!ExpectedValOpt.hasValue()) + return; + + llvm::ConstantInt *ExpectedValue = ExpectedValOpt.getValue(); + + llvm::SwitchInst::CaseHandle Case = *SI->findCaseValue(ExpectedValue); + unsigned n = SI->getNumCases(); // +1 for default case. + + const long ExpectedVal = ExpectedValOpt.getValue()->getZExtValue(); + + uint64_t Max = + *std::max_element(SwitchWeights->begin(), SwitchWeights->end()); + + // The default case is always mapped to index 0 of the SwitchWeights vector. + // This relies on internal details of another component, so ideally we can + // expose an interface that we can use instead of relying on implementaion + // details in another module. + // TODO: create interface to switchweights default index + uint64_t Index = (Case == *SI->case_default()) ? 0 : Case.getCaseIndex() + 1; + uint64_t TakenCount = (*SwitchWeights)[Index]; + + uint64_t CaseTotal = + std::accumulate(SwitchWeights->begin(), SwitchWeights->end(), 0); + const uint64_t TotalBranchWeight = + LikelyBranchWeight + (UnlikelyBranchWeight * n); + float Percentage = ((float)TakenCount / (float)CaseTotal); + const llvm::BranchProbability LikelyThreshold(LikelyBranchWeight, + TotalBranchWeight); + auto ScaledThreshold = LikelyThreshold.scale(CaseTotal); + + LLVM_DEBUG(DebugPrintMisExpectSwitchInfo({SwitchWeights, ExpectedVal, Index, + TakenCount, Max, ScaledThreshold, + Percentage})); + + if (TakenCount < ScaledThreshold) + 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,9 @@ 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,9 @@ +bar +# Func Hash: +11262309464 +# Num Counters: +2 +# Counter Values: +200000 +2 + 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,9 @@ +bar +# Func Hash: +45795613684824 +# Num Counters: +2 +# Counter Values: +200000 +0 + 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,12 @@ +main +# Func Hash: +79676873694057560 +# Num Counters: +5 +# Counter Values: +1 +20 +20000 +20000 +20000 + 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,16 @@ +main +# Func Hash: +1965403898329309329 +# Num Counters: +9 +# Counter Values: +1 +20 +20000 +20000 +12 +26 +0 +0 +19962 + 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,27 @@ +// Test that misexpect emits no warning when prediction is correct + +// 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 baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +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; +} + 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,26 @@ +// Test that misexpect emits no warning when condition is not a compile-time constant + +// 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 baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +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; +} 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,26 @@ +// 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 baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +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; +} + 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,27 @@ +// Test that misexpect emits no warning without -fmisexpect flag + +// 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 baz(int); +int buzz(); + +const int inner_loop = 100; +const int outer_loop = 2000; + +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; +} + 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,42 @@ +// Test that misexpect detects mis-annotated switch statements for default case + +// 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(); +void init_arry(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +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; +} 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,43 @@ +// Test that misexpect emits no warning when switch condition is non-const + +// 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(); +void init_arry(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +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; +} 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,35 @@ +// Test that misexpect emits no warning when there is only one switch case + +// 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(); +void init_arry(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +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; +} 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,41 @@ +// 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(); +void init_arry(); + +const int inner_loop = 1000; +const int outer_loop = 20; +const int arry_size = 25; + +int arry[arry_size] = {0}; + +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: + break; + default: + val += random_sample(arry, arry_size); + break; + } // end switch + } // end inner_loop + } // end outer_loop + + return 0; +}