[clang] bac6cd5 - [misexpect] Re-implement MisExpect Diagnostics
Paul Kirth via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 19 14:24:02 PDT 2022
Author: Paul Kirth
Date: 2022-04-19T21:23:48Z
New Revision: bac6cd5bf85669e3376610cfc4c4f9ca015e7b9b
URL: https://github.com/llvm/llvm-project/commit/bac6cd5bf85669e3376610cfc4c4f9ca015e7b9b
DIFF: https://github.com/llvm/llvm-project/commit/bac6cd5bf85669e3376610cfc4c4f9ca015e7b9b.diff
LOG: [misexpect] Re-implement MisExpect Diagnostics
Reimplements MisExpect diagnostics from D66324 to reconstruct its
original checking methodology only using MD_prof branch_weights
metadata.
New checks rely on 2 invariants:
1) For frontend instrumentation, MD_prof branch_weights will always be
populated before llvm.expect intrinsics are lowered.
2) for IR and sample profiling, llvm.expect intrinsics will always be
lowered before branch_weights are populated from the IR profiles.
These invariants allow the checking to assume how the existing branch
weights are populated depending on the profiling method used, and emit
the correct diagnostics. If these invariants are ever invalidated, the
MisExpect related checks would need to be updated, potentially by
re-introducing MD_misexpect metadata, and ensuring it always will be
transformed the same way as branch_weights in other optimization passes.
Frontend based profiling is now enabled without using LLVM Args, by
introducing a new CodeGen option, and checking if the -Wmisexpect flag
has been passed on the command line.
Reviewed By: tejohnson
Differential Revision: https://reviews.llvm.org/D115907
Added:
clang/docs/MisExpect.rst
clang/test/Profile/Inputs/misexpect-branch-nonconst-expect-arg.proftext
clang/test/Profile/Inputs/misexpect-branch.proftext
clang/test/Profile/Inputs/misexpect-switch-default-only.proftext
clang/test/Profile/Inputs/misexpect-switch-default.proftext
clang/test/Profile/Inputs/misexpect-switch-nonconst.proftext
clang/test/Profile/Inputs/misexpect-switch.proftext
clang/test/Profile/misexpect-branch-cold.c
clang/test/Profile/misexpect-branch-nonconst-expected-val.c
clang/test/Profile/misexpect-branch-unpredictable.c
clang/test/Profile/misexpect-branch.c
clang/test/Profile/misexpect-switch-default.c
clang/test/Profile/misexpect-switch-nonconst.c
clang/test/Profile/misexpect-switch-only-default-case.c
clang/test/Profile/misexpect-switch.c
llvm/docs/MisExpect.rst
llvm/include/llvm/Transforms/Utils/MisExpect.h
llvm/lib/Transforms/Utils/MisExpect.cpp
llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-correct.proftext
llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-overflow.proftext
llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-threshold.proftext
llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch.proftext
llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch-correct.proftext
llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch.proftext
llvm/test/Transforms/PGOProfile/misexpect-branch-correct.ll
llvm/test/Transforms/PGOProfile/misexpect-branch-overflow.ll
llvm/test/Transforms/PGOProfile/misexpect-branch-stripped.ll
llvm/test/Transforms/PGOProfile/misexpect-branch-unpredictable.ll
llvm/test/Transforms/PGOProfile/misexpect-branch.ll
llvm/test/Transforms/PGOProfile/misexpect-switch-default.ll
llvm/test/Transforms/PGOProfile/misexpect-switch.ll
Modified:
clang/docs/ReleaseNotes.rst
clang/docs/index.rst
clang/include/clang/Basic/CodeGenOptions.def
clang/include/clang/Basic/CodeGenOptions.h
clang/include/clang/Basic/DiagnosticDriverKinds.td
clang/include/clang/Basic/DiagnosticFrontendKinds.td
clang/include/clang/Basic/DiagnosticGroups.td
clang/include/clang/Driver/Options.td
clang/lib/CodeGen/BackendUtil.cpp
clang/lib/CodeGen/CodeGenAction.cpp
clang/lib/Frontend/CompilerInvocation.cpp
llvm/docs/UserGuides.rst
llvm/include/llvm/IR/DiagnosticInfo.h
llvm/include/llvm/IR/LLVMContext.h
llvm/include/llvm/Target/TargetOptions.h
llvm/lib/IR/DiagnosticInfo.cpp
llvm/lib/IR/LLVMContext.cpp
llvm/lib/IR/LLVMContextImpl.h
llvm/lib/Transforms/IPO/SampleProfile.cpp
llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp
llvm/lib/Transforms/Scalar/LowerExpectIntrinsic.cpp
llvm/lib/Transforms/Utils/CMakeLists.txt
Removed:
################################################################################
diff --git a/clang/docs/MisExpect.rst b/clang/docs/MisExpect.rst
new file mode 100644
index 0000000000000..29eb1269b8982
--- /dev/null
+++ b/clang/docs/MisExpect.rst
@@ -0,0 +1,75 @@
+===================
+Misexpect
+===================
+.. contents::
+
+.. toctree::
+ :maxdepth: 1
+
+When developers use ``llvm.expect`` intrinsics, i.e., through use of
+``__builtin_expect(...)``, they are trying to communicate how their code is
+expected to behave at runtime to the optimizer. These annotations, however, can
+be incorrect for a variety of reasons: changes to the code base invalidate them
+silently, the developer mis-annotated them (e.g., using ``LIKELY`` instead of
+``UNLIKELY``), or perhaps they assumed something incorrectly when they wrote
+the annotation. Regardless of why, it is useful to detect these situations so
+that the optimizer can make more useful decisions about the code.
+
+MisExpect diagnostics are intended to help developers identify and address
+these situations, by comparing the branch weights added by the ``llvm.expect``
+intrinsic to those collected through profiling. Whenever these values are
+mismatched, a diagnostic is surfaced to the user. Details on how the checks
+operate in the LLVM backed can be found in LLVM's documentation.
+
+By default MisExpect checking is quite strict, because the use of the
+``llvm.expect`` intrinsic is designed for specialized cases, where the outcome
+of a condition is severely skewed. As a result, the optimizer can be extremely
+aggressive, which can result in performance degradation if the outcome is less
+predictable than the annotation suggests. Even when the annotation is correct
+90% of the time, it may be beneficial to either remove the annotation or to use
+a
diff erent intrinsic that can communicate the probability more directly.
+
+Because this may be too strict, MisExpect diagnostics are not enabled by
+default, and support an additional flag to tolerate some deviation from the
+exact thresholds. The ``-fdiagnostic-misexpect-tolerance=N`` accepts
+deviations when comparing branch weights within ``N%`` of the expected values.
+So passing ``-fdiagnostic-misexpect-tolerance=5`` will not report diagnostic messages
+if the branch weight from the profile is within 5% of the weight added by
+the ``llvm.expect`` intrinsic.
+
+MisExpect diagnostics are also available in the form of optimization remarks,
+which can be serialized and processed through the ``opt-viewer.py``
+scripts in LLVM.
+
+.. option:: -Rpass=misexpect
+
+ Enables optimization remarks for misexpect when profiling data conflicts with
+ use of ``llvm.expect`` intrinsics.
+
+
+.. option:: -Wmisexpect
+
+ Enables misexpect warnings when profiling data conflicts with use of
+ ``llvm.expect`` intrinsics.
+
+.. option:: -fdiagnostic-misexpect-tolerance=N
+
+ Relaxes misexpect checking to tolerate profiling values within N% of the
+ expected branch weight. e.g., a value of ``N=5`` allows misexpect to check against
+ ``0.95 * Threshold``
+
+LLVM supports 4 types of profile formats: Frontend, IR, CS-IR, and
+Sampling. MisExpect Diagnostics are compatible with all Profiling formats.
+
++----------------+--------------------------------------------------------------------------------------+
+| Profile Type | Description |
++================+======================================================================================+
+| Frontend | Profiling instrumentation added during compilation by the frontend, i.e. ``clang`` |
++----------------+--------------------------------------------------------------------------------------+
+| IR | Profiling instrumentation added during by the LLVM backend |
++----------------+--------------------------------------------------------------------------------------+
+| CS-IR | Context Sensitive IR based profiles |
++----------------+--------------------------------------------------------------------------------------+
+| Sampling | Profiles collected through sampling with external tools, such as ``perf`` on Linux |
++----------------+--------------------------------------------------------------------------------------+
+
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4a0e2ff6170a2..4d175bd2daa0d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -157,6 +157,9 @@ Improvements to Clang's diagnostics
function with a prototype. e.g., ``void f(int); void f() {}`` is now properly
diagnosed.
+- ``-Wmisexpect`` warns when the branch weights collected during profiling
+ conflict with those added by ``llvm.expect``.
+
Non-comprehensive list of changes in this release
-------------------------------------------------
- Improve __builtin_dump_struct:
diff --git a/clang/docs/index.rst b/clang/docs/index.rst
index 7c6ddebdf96cc..21da27ec6c59f 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -42,6 +42,7 @@ Using Clang as a Compiler
SourceBasedCodeCoverage
Modules
MSVCCompatibility
+ MisExpect
OpenCLSupport
OpenMPSupport
SYCLSupport
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 3aef6f75302b5..d8f667dc387bb 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -174,6 +174,7 @@ CODEGENOPT(NoExecStack , 1, 0) ///< Set when -Wa,--noexecstack is enabled.
CODEGENOPT(FatalWarnings , 1, 0) ///< Set when -Wa,--fatal-warnings is
///< enabled.
CODEGENOPT(NoWarn , 1, 0) ///< Set when -Wa,--no-warn is enabled.
+CODEGENOPT(MisExpect , 1, 0) ///< Set when -Wmisexpect is enabled
CODEGENOPT(EnableSegmentedStacks , 1, 0) ///< Set when -fsplit-stack is enabled.
CODEGENOPT(NoInlineLineTables, 1, 0) ///< Whether debug info should contain
///< inline line tables.
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 59b8895257703..48a41244afa32 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -423,6 +423,10 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// If threshold option is not specified, it is disabled by default.
Optional<uint64_t> DiagnosticsHotnessThreshold = 0;
+ /// The maximum percentage profiling weights can deviate from the expected
+ /// values in order to be included in misexpect diagnostics.
+ Optional<uint64_t> DiagnosticsMisExpectTolerance = 0;
+
public:
// Define accessors/mutators for code generation options of enumeration type.
#define CODEGENOPT(Name, Bits, Default)
diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index cb53804d9e4f8..5fbe3cf8cf295 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -149,6 +149,8 @@ def err_drv_invalid_darwin_version : Error<
"invalid Darwin version number: %0">;
def err_drv_invalid_diagnotics_hotness_threshold : Error<
"invalid argument in '%0', only integer or 'auto' is supported">;
+def err_drv_invalid_diagnotics_misexpect_tolerance : Error<
+ "invalid argument in '%0', only integers are supported">;
def err_drv_missing_argument : Error<
"argument to '%0' is missing (expected %1 value%s1)">;
def err_drv_invalid_Xarch_argument_with_args : Error<
@@ -378,6 +380,9 @@ def warn_drv_empty_joined_argument : Warning<
def warn_drv_diagnostics_hotness_requires_pgo : Warning<
"argument '%0' requires profile-guided optimization information">,
InGroup<UnusedCommandLineArgument>;
+def warn_drv_diagnostics_misexpect_requires_pgo : Warning<
+ "argument '%0' requires profile-guided optimization information">,
+ InGroup<UnusedCommandLineArgument>;
def warn_drv_clang_unsupported : Warning<
"the clang compiler does not support '%0'">;
def warn_drv_deprecated_arg : Warning<
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index afc166b5d087a..50f7e2b9221d0 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -315,6 +315,11 @@ def warn_profile_data_missing : Warning<
def warn_profile_data_unprofiled : Warning<
"no profile data available for file \"%0\"">,
InGroup<ProfileInstrUnprofiled>;
+def warn_profile_data_misexpect : Warning<
+ "Potential performance regression from use of __builtin_expect(): "
+ "Annotation was correct on %0 of profiled executions.">,
+ BackendInfo,
+ InGroup<MisExpect>;
} // end of instrumentation issue category
}
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index ebcbb798eb941..5a9fe8673a044 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1256,6 +1256,7 @@ def BackendWarningAttributes : DiagGroup<"attribute-warning">;
def ProfileInstrMissing : DiagGroup<"profile-instr-missing">;
def ProfileInstrOutOfDate : DiagGroup<"profile-instr-out-of-date">;
def ProfileInstrUnprofiled : DiagGroup<"profile-instr-unprofiled">;
+def MisExpect : DiagGroup<"misexpect">;
// AddressSanitizer frontend instrumentation remarks.
def SanitizeAddressRemarks : DiagGroup<"sanitize-address">;
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 6ec7ddb331ecb..9666ac8be4ecb 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1430,6 +1430,9 @@ def fdiagnostics_hotness_threshold_EQ : Joined<["-"], "fdiagnostics-hotness-thre
Group<f_Group>, Flags<[CC1Option]>, MetaVarName<"<value>">,
HelpText<"Prevent optimization remarks from being output if they do not have at least this profile count. "
"Use 'auto' to apply the threshold from profile summary">;
+def fdiagnostics_misexpect_tolerance_EQ : Joined<["-"], "fdiagnostics-misexpect-tolerance=">,
+ Group<f_Group>, Flags<[CC1Option]>, MetaVarName<"<value>">,
+ HelpText<"Prevent misexpect diagnostics from being output if the profile counts are within N% of the expected. ">;
defm diagnostics_show_option : BoolFOption<"diagnostics-show-option",
DiagnosticOpts<"ShowOptionNames">, DefaultTrue,
NegFlag<SetFalse, [CC1Option]>, PosFlag<SetTrue, [], "Print option name with mappable diagnostics">>;
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index c53858acb2106..3d996b3e0c470 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -474,6 +474,7 @@ static bool initTargetOptions(DiagnosticsEngine &Diags,
Entry.IgnoreSysRoot ? Entry.Path : HSOpts.Sysroot + Entry.Path);
Options.MCOptions.Argv0 = CodeGenOpts.Argv0;
Options.MCOptions.CommandLineArgs = CodeGenOpts.CommandLineArgs;
+ Options.MisExpect = CodeGenOpts.MisExpect;
return true;
}
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp
index b8ed3534de8be..af7a60c179a67 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -340,6 +340,15 @@ namespace clang {
CodeGenOpts.getProfileUse() != CodeGenOptions::ProfileNone)
Ctx.setDiagnosticsHotnessRequested(true);
+ if (CodeGenOpts.MisExpect) {
+ Ctx.setMisExpectWarningRequested(true);
+ }
+
+ if (CodeGenOpts.DiagnosticsMisExpectTolerance) {
+ Ctx.setDiagnosticsMisExpectTolerance(
+ CodeGenOpts.DiagnosticsMisExpectTolerance);
+ }
+
// Link each LinkModule into our module.
if (LinkInModules())
return;
@@ -440,6 +449,9 @@ namespace clang {
void OptimizationFailureHandler(
const llvm::DiagnosticInfoOptimizationFailure &D);
void DontCallDiagHandler(const DiagnosticInfoDontCall &D);
+ /// Specialized handler for misexpect warnings.
+ /// Note that misexpect remarks are emitted through ORE
+ void MisExpectDiagHandler(const llvm::DiagnosticInfoMisExpect &D);
};
void BackendConsumer::anchor() {}
@@ -821,6 +833,25 @@ void BackendConsumer::DontCallDiagHandler(const DiagnosticInfoDontCall &D) {
<< llvm::demangle(D.getFunctionName().str()) << D.getNote();
}
+void BackendConsumer::MisExpectDiagHandler(
+ const llvm::DiagnosticInfoMisExpect &D) {
+ StringRef Filename;
+ unsigned Line, Column;
+ bool BadDebugInfo = false;
+ FullSourceLoc Loc =
+ getBestLocationFromDebugLoc(D, BadDebugInfo, Filename, Line, Column);
+
+ Diags.Report(Loc, diag::warn_profile_data_misexpect) << D.getMsg().str();
+
+ if (BadDebugInfo)
+ // If we were not able to translate the file:line:col information
+ // back to a SourceLocation, at least emit a note stating that
+ // we could not translate this location. This can happen in the
+ // case of #line directives.
+ Diags.Report(Loc, diag::note_fe_backend_invalid_loc)
+ << Filename << Line << Column;
+}
+
/// This function is invoked when the backend needs
/// to report something to the user.
void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) {
@@ -895,6 +926,9 @@ void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) {
case llvm::DK_DontCall:
DontCallDiagHandler(cast<DiagnosticInfoDontCall>(DI));
return;
+ case llvm::DK_MisExpect:
+ MisExpectDiagHandler(cast<DiagnosticInfoMisExpect>(DI));
+ return;
default:
// Plugin IDs are not bound to any value as they are set dynamically.
ComputeDiagRemarkID(Severity, backend_plugin, DiagID);
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 369295db9196c..fcb73bc858a43 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -107,6 +107,20 @@ using namespace driver;
using namespace options;
using namespace llvm::opt;
+//===----------------------------------------------------------------------===//
+// Helpers.
+//===----------------------------------------------------------------------===//
+
+// Parse misexpect tolerance argument value.
+// Valid option values are integers in the range [0, 100)
+inline Expected<Optional<uint64_t>> parseToleranceOption(StringRef Arg) {
+ int64_t Val;
+ if (Arg.getAsInteger(10, Val))
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Not an integer: %s", Arg.data());
+ return Val;
+}
+
//===----------------------------------------------------------------------===//
// Initialization.
//===----------------------------------------------------------------------===//
@@ -1547,6 +1561,9 @@ void CompilerInvocation::GenerateCodeGenArgs(
: "auto",
SA);
+ GenerateArg(Args, OPT_fdiagnostics_misexpect_tolerance_EQ,
+ Twine(*Opts.DiagnosticsMisExpectTolerance), SA);
+
for (StringRef Sanitizer : serializeSanitizerKinds(Opts.SanitizeRecover))
GenerateArg(Args, OPT_fsanitize_recover_EQ, Sanitizer, SA);
@@ -1959,6 +1976,23 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
}
}
+ if (auto *arg =
+ Args.getLastArg(options::OPT_fdiagnostics_misexpect_tolerance_EQ)) {
+ auto ResultOrErr = parseToleranceOption(arg->getValue());
+
+ if (!ResultOrErr) {
+ Diags.Report(diag::err_drv_invalid_diagnotics_misexpect_tolerance)
+ << "-fdiagnostics-misexpect-tolerance=";
+ } else {
+ Opts.DiagnosticsMisExpectTolerance = *ResultOrErr;
+ if ((!Opts.DiagnosticsMisExpectTolerance.hasValue() ||
+ Opts.DiagnosticsMisExpectTolerance.getValue() > 0) &&
+ !UsingProfile)
+ Diags.Report(diag::warn_drv_diagnostics_misexpect_requires_pgo)
+ << "-fdiagnostics-misexpect-tolerance=";
+ }
+ }
+
// If the user requested to use a sample profile for PGO, then the
// backend will need to track source location information so the profile
// can be incorporated into the IR.
@@ -4432,6 +4466,13 @@ bool CompilerInvocation::CreateFromArgsImpl(
if (Res.getFrontendOpts().ProgramAction == frontend::RewriteObjC)
LangOpts.ObjCExceptions = 1;
+ for (auto Warning : Res.getDiagnosticOpts().Warnings) {
+ if (Warning == "misexpect" &&
+ !Diags.isIgnored(diag::warn_profile_data_misexpect, SourceLocation())) {
+ Res.getCodeGenOpts().MisExpect = true;
+ }
+ }
+
if (LangOpts.CUDA) {
// During CUDA device-side compilation, the aux triple is the
// triple used for host compilation.
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
index 0000000000000..ffd1ef05f1e5b
--- /dev/null
+++ b/clang/test/Profile/Inputs/misexpect-branch-nonconst-expect-arg.proftext
@@ -0,0 +1,8 @@
+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
index 0000000000000..f6ad8be438614
--- /dev/null
+++ b/clang/test/Profile/Inputs/misexpect-branch.proftext
@@ -0,0 +1,17 @@
+bar
+# Func Hash:
+45795613684824
+# Num Counters:
+2
+# Counter Values:
+200000
+180000
+
+fizz
+# Func Hash:
+45795613684824
+# Num Counters:
+2
+# Counter Values:
+200000
+18000
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
index 0000000000000..ac59378e76877
--- /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-default.proftext b/clang/test/Profile/Inputs/misexpect-switch-default.proftext
new file mode 100644
index 0000000000000..533da91765234
--- /dev/null
+++ b/clang/test/Profile/Inputs/misexpect-switch-default.proftext
@@ -0,0 +1,16 @@
+main
+# Func Hash:
+8734802134600123338
+# Num Counters:
+9
+# Counter Values:
+1
+20000
+20000
+4066
+11889
+0
+0
+4045
+0
+
diff --git a/clang/test/Profile/Inputs/misexpect-switch-nonconst.proftext b/clang/test/Profile/Inputs/misexpect-switch-nonconst.proftext
new file mode 100644
index 0000000000000..0da9379357ae7
--- /dev/null
+++ b/clang/test/Profile/Inputs/misexpect-switch-nonconst.proftext
@@ -0,0 +1,16 @@
+main
+# Func Hash:
+3721743393642630379
+# Num Counters:
+10
+# Counter Values:
+1
+20
+20000
+20000
+1
+0
+0
+0
+19999
+0
diff --git a/clang/test/Profile/Inputs/misexpect-switch.proftext b/clang/test/Profile/Inputs/misexpect-switch.proftext
new file mode 100644
index 0000000000000..7128287b5c677
--- /dev/null
+++ b/clang/test/Profile/Inputs/misexpect-switch.proftext
@@ -0,0 +1,32 @@
+main
+# Func Hash:
+872687477373597607
+# Num Counters:
+9
+# Counter Values:
+1
+20
+20000
+20000
+3
+3
+1
+3
+19990
+
+random_sample
+# Func Hash:
+24
+# Num Counters:
+1
+# Counter Values:
+19990
+
+sum
+# Func Hash:
+24
+# Num Counters:
+1
+# Counter Values:
+3
+
diff --git a/clang/test/Profile/misexpect-branch-cold.c b/clang/test/Profile/misexpect-branch-cold.c
new file mode 100644
index 0000000000000..6d34f92a25454
--- /dev/null
+++ b/clang/test/Profile/misexpect-branch-cold.c
@@ -0,0 +1,26 @@
+// 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 -Wmisexpect
+
+// 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
index 0000000000000..c5167b9a2a0b7
--- /dev/null
+++ b/clang/test/Profile/misexpect-branch-nonconst-expected-val.c
@@ -0,0 +1,23 @@
+// 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 -Wmisexpect
+
+// expected-no-diagnostics
+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-unpredictable.c b/clang/test/Profile/misexpect-branch-unpredictable.c
new file mode 100644
index 0000000000000..6c4f90146a2ae
--- /dev/null
+++ b/clang/test/Profile/misexpect-branch-unpredictable.c
@@ -0,0 +1,25 @@
+// 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 -Wmisexpect
+
+// expected-no-diagnostics
+#define unpredictable(x) __builtin_unpredictable(!!(x))
+
+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 (unpredictable(rando % (outer_loop * inner_loop) == 0)) {
+ 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
index 0000000000000..ce46b46880611
--- /dev/null
+++ b/clang/test/Profile/misexpect-branch.c
@@ -0,0 +1,49 @@
+// Test that misexpect detects mis-annotated branches
+
+// test diagnostics are issued when profiling data mis-matches annotations
+// RUN: llvm-profdata merge %S/Inputs/misexpect-branch.proftext -o %t.profdata
+// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify=imprecise -Wmisexpect
+// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify=exact -Wmisexpect -debug-info-kind=line-tables-only
+
+// there should be no diagnostics when the tolerance is sufficiently high, or when -Wmisexpect is not requested
+// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify=foo -fdiagnostics-misexpect-tolerance=10 -Wmisexpect -debug-info-kind=line-tables-only
+// RUN: %clang_cc1 %s -O2 -o - -disable-llvm-passes -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify=foo
+
+// Ensure we emit an error when we don't use pgo with tolerance threshold
+// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fdiagnostics-misexpect-tolerance=10 -Wmisexpect -debug-info-kind=line-tables-only 2>&1 | FileCheck -check-prefix=NO_PGO %s
+
+// Test -fdiagnostics-misexpect-tolerance= requires pgo profile
+// NO_PGO: '-fdiagnostics-misexpect-tolerance=' requires profile-guided optimization information
+
+// foo-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() { // imprecise-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% ({{[0-9]+ / [0-9]+}}) of profiled executions.}}
+ int rando = buzz();
+ int x = 0;
+ if (likely(rando % (outer_loop * inner_loop) == 0)) { // exact-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% ({{[0-9]+ / [0-9]+}}) of profiled executions.}}
+ x = baz(rando);
+ } else {
+ x = foo(50);
+ }
+ return x;
+}
+
+int fizz() { // imprecise-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% ({{[0-9]+ / [0-9]+}}) of profiled executions.}}
+ int rando = buzz();
+ int x = 0;
+ if (unlikely(rando % (outer_loop * inner_loop) == 0)) { // exact-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% ({{[0-9]+ / [0-9]+}}) of profiled executions.}}
+ 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
index 0000000000000..033490e558e6a
--- /dev/null
+++ b/clang/test/Profile/misexpect-switch-default.c
@@ -0,0 +1,40 @@
+// Test that misexpect detects mis-annotated switch statements for default case
+
+// RUN: llvm-profdata merge %S/Inputs/misexpect-switch-default.proftext -o %t.profdata
+// RUN: %clang_cc1 %s -O2 -o - -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -Wmisexpect -debug-info-kind=line-tables-only
+
+#define inner_loop 1000
+#define outer_loop 20
+#define arry_size 25
+
+int sum(int *buff, int size);
+int random_sample(int *buff, int size);
+int rand();
+void init_arry();
+
+int arry[arry_size] = {0};
+
+int main() {
+ init_arry();
+ int val = 0;
+ int j;
+ for (j = 0; j < outer_loop * inner_loop; ++j) {
+ unsigned condition = rand() % 5;
+ switch (__builtin_expect(condition, 6)) { // expected-warning-re {{Potential performance regression from use of __builtin_expect(): Annotation was correct on {{.+}}% ({{[0-9]+ / [0-9]+}}) of profiled executions.}}
+ case 0:
+ val += sum(arry, arry_size);
+ break;
+ case 1:
+ case 2:
+ case 3:
+ break;
+ case 4:
+ val += random_sample(arry, arry_size);
+ break;
+ default:
+ __builtin_unreachable();
+ } // end switch
+ } // 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
index 0000000000000..cff3bce0d6d0a
--- /dev/null
+++ b/clang/test/Profile/misexpect-switch-nonconst.c
@@ -0,0 +1,44 @@
+// Test that misexpect emits no warning when switch condition is non-const
+
+// RUN: llvm-profdata merge %S/Inputs/misexpect-switch-nonconst.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 inner_loop 1000
+#define outer_loop 20
+#define arry_size 25
+
+int sum(int *buff, int size);
+int random_sample(int *buff, int size);
+int rand();
+void init_arry();
+
+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
index 0000000000000..26e8564c81b54
--- /dev/null
+++ b/clang/test/Profile/misexpect-switch-only-default-case.c
@@ -0,0 +1,36 @@
+// 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 - -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -Wmisexpect -debug-info-kind=line-tables-only
+
+// expected-no-diagnostics
+
+#define inner_loop 1000
+#define outer_loop 20
+#define arry_size 25
+
+int sum(int *buff, int size);
+int random_sample(int *buff, int size);
+int rand();
+void init_arry();
+
+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
index 0000000000000..8ca8a155c74a7
--- /dev/null
+++ b/clang/test/Profile/misexpect-switch.c
@@ -0,0 +1,39 @@
+// 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 - -emit-llvm -fprofile-instrument-use-path=%t.profdata -verify -Wmisexpect -debug-info-kind=line-tables-only
+
+#define inner_loop 1000
+#define outer_loop 20
+#define arry_size 25
+
+int arry[arry_size] = {0};
+
+int rand();
+int sum(int *buff, int size);
+int random_sample(int *buff, int size);
+
+int main() {
+ 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 {{.+}}% ({{[0-9]+ / [0-9]+}}) 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;
+}
diff --git a/llvm/docs/MisExpect.rst b/llvm/docs/MisExpect.rst
new file mode 100644
index 0000000000000..a9e6f8cc7006d
--- /dev/null
+++ b/llvm/docs/MisExpect.rst
@@ -0,0 +1,73 @@
+===================
+Misexpect
+===================
+.. contents::
+
+.. toctree::
+ :maxdepth: 1
+
+When developers use ``llvm.expect`` intrinsics, i.e., through use of
+``__builtin_expect(...)``, they are trying to communicate how their code is
+expected to behave at runtime to the optimizer. These annotations, however, can
+be incorrect for a variety of reasons: changes to the code base invalidate them
+silently, the developer mis-annotated them (e.g., using ``LIKELY`` instead of
+``UNLIKELY``), or perhaps they assumed something incorrectly when they wrote
+the annotation. Regardless of why, it is useful to detect these situations so
+that the optimizer can make more useful decisions about the code. MisExpect
+diagnostics are intended to help developers identify and address these
+situations, by comparing the use of the ``llvm.expect`` intrinsic to the ground
+truth provided by a profiling input.
+
+The MisExpect checks in the LLVM backend follow a simple procedure: if there is
+a mismatch between the branch weights collected during profiling and those
+supplied by an ``llvm.expect`` intrinsic, then it will emit a diagnostic
+message to the user.
+
+The most natural place to perform the verification is just prior to when
+branch weights are assigned to the target instruction in the form of
+branch weight metadata.
+
+There are 3 key places in the LLVM backend where branch weights are
+created and assigned based on profiling information or the use of the
+``llvm.expect`` intrinsic, and our implementation focuses on these
+places to perform the verification.
+
+We calculate the threshold for emitting MisExpect related diagnostics
+based on the values the compiler assigns to ``llvm.expect`` intrinsics,
+which can be set through the ``-likely-branch-weight`` and
+``-unlikely-branch-weight`` LLVM options. During verification, if the
+profile weights mismatch the calculated threshold, then we will emit a
+remark or warning detailing a potential performance regression. The
+diagnostic also reports the percentage of the time the annotation was
+correct during profiling to help developers reason about how to proceed.
+
+The diagnostics are also available in the form of optimization remarks,
+which can be serialized and processed through the ``opt-viewer.py``
+scripts in LLVM.
+
+.. option:: -pass-remarks=misexpect
+
+ Enables optimization remarks for misexpect when profiling data conflicts with
+ use of ``llvm.expect`` intrinsics.
+
+
+.. option:: -pgo-warn-misexpect
+
+ Enables misexpect warnings when profiling data conflicts with use of
+ ``llvm.expect`` intrinsics.
+
+LLVM supports 4 types of profile formats: Frontend, IR, CS-IR, and
+Sampling. MisExpect Diagnostics are compatible with all Profiling formats.
+
++----------------+--------------------------------------------------------------------------------------+
+| Profile Type | Description |
++================+======================================================================================+
+| Frontend | Profiling instrumentation added during compilation by the frontend, i.e. ``clang`` |
++----------------+--------------------------------------------------------------------------------------+
+| IR | Profiling instrumentation added during by the LLVM backend |
++----------------+--------------------------------------------------------------------------------------+
+| CS-IR | Context Sensitive IR based profiles |
++----------------+--------------------------------------------------------------------------------------+
+| Sampling | Profiles collected through sampling with external tools, such as ``perf`` on Linux |
++----------------+--------------------------------------------------------------------------------------+
+
diff --git a/llvm/docs/UserGuides.rst b/llvm/docs/UserGuides.rst
index f68dea71cb289..fb3f03700ff6c 100644
--- a/llvm/docs/UserGuides.rst
+++ b/llvm/docs/UserGuides.rst
@@ -44,6 +44,7 @@ intermediate LLVM representation.
MemorySSA
MergeFunctions
MCJITDesignAndImplementation
+ MisExpect
ORCv2
OpaquePointers
JITLink
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index 1ea1d9787d61f..da37801b6d192 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -85,6 +85,7 @@ enum DiagnosticKind {
DK_Unsupported,
DK_SrcMgr,
DK_DontCall,
+ DK_MisExpect,
DK_FirstPluginKind // Must be last value to work with
// getNextAvailablePluginDiagnosticKind
};
@@ -1032,6 +1033,25 @@ class DiagnosticInfoUnsupported : public DiagnosticInfoWithLocationBase {
void print(DiagnosticPrinter &DP) const override;
};
+/// Diagnostic information for MisExpect analysis.
+class DiagnosticInfoMisExpect : public DiagnosticInfoWithLocationBase {
+public:
+ DiagnosticInfoMisExpect(const Instruction *Inst, Twine &Msg);
+
+ /// \see DiagnosticInfo::print.
+ void print(DiagnosticPrinter &DP) const override;
+
+ static bool classof(const DiagnosticInfo *DI) {
+ return DI->getKind() == DK_MisExpect;
+ }
+
+ const Twine &getMsg() const { return Msg; }
+
+private:
+ /// Message to report.
+ const Twine &Msg;
+};
+
static DiagnosticSeverity getDiagnosticSeverity(SourceMgr::DiagKind DK) {
switch (DK) {
case llvm::SourceMgr::DK_Error:
diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h
index 73e2326b8fe3e..792e59a303623 100644
--- a/llvm/include/llvm/IR/LLVMContext.h
+++ b/llvm/include/llvm/IR/LLVMContext.h
@@ -202,6 +202,11 @@ class LLVMContext {
/// diagnostics.
void setDiagnosticsHotnessRequested(bool Requested);
+ bool getMisExpectWarningRequested() const;
+ void setMisExpectWarningRequested(bool Requested);
+ void setDiagnosticsMisExpectTolerance(Optional<uint64_t> Tolerance);
+ uint64_t getDiagnosticsMisExpectTolerance() const;
+
/// Return the minimum hotness value a diagnostic would need in order
/// to be included in optimization diagnostics.
///
diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h
index 9a0347ce9ffc8..6083d18d96f73 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -144,7 +144,7 @@ namespace llvm {
ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false),
XRayOmitFunctionIndex(false), DebugStrictDwarf(false),
Hotpatch(false), PPCGenScalarMASSEntries(false), JMCInstrument(false),
- EnableCFIFixup(false),
+ EnableCFIFixup(false), MisExpect(false),
FPDenormalMode(DenormalMode::IEEE, DenormalMode::IEEE) {}
/// DisableFramePointerElim - This returns true if frame pointer elimination
@@ -360,6 +360,10 @@ namespace llvm {
/// Enable the CFIFixup pass.
unsigned EnableCFIFixup : 1;
+ /// When set to true, enable MisExpect Diagnostics
+ /// By default, it is set to false
+ unsigned MisExpect : 1;
+
/// Name of the stack usage file (i.e., .su file) if user passes
/// -fstack-usage. If empty, it can be implied that -fstack-usage is not
/// passed on the command line.
diff --git a/llvm/include/llvm/Transforms/Utils/MisExpect.h b/llvm/include/llvm/Transforms/Utils/MisExpect.h
new file mode 100644
index 0000000000000..064eeac4c669d
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/MisExpect.h
@@ -0,0 +1,77 @@
+//===--- MisExpect.h - Check the use of llvm.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 diagnostic messages for potentially incorrect
+// usage of the llvm.expect intrinsic. This utility extracts the threshold
+// values from metadata associated with the instrumented Branch or Switch
+// instruction. The threshold values are then used to determine if a diagnostic
+// should be emitted.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LLVMContext.h"
+
+namespace llvm {
+namespace misexpect {
+
+/// checkBackendInstrumentation - compares PGO counters to the thresholds used
+/// for llvm.expect and warns if the PGO counters are outside of the expected
+/// range. It extracts the expected weights from the MD_prof weights attatched
+/// to the instruction, which are are assumed to come from lowered llvm.expect
+/// intrinsics. The RealWeights parameter and the extracted expected weights are
+/// then passed to verifyMisexpect() for verification
+///
+/// \param I The Instruction being checked
+/// \param RealWeights A vector of profile weights for each target block
+void checkBackendInstrumentation(Instruction &I,
+ const llvm::ArrayRef<uint32_t> RealWeights);
+
+/// checkFrontendInstrumentation - compares PGO counters to the thresholds used
+/// for llvm.expect and warns if the PGO counters are outside of the expected
+/// range. It extracts the expected weights from the MD_prof weights attatched
+/// to the instruction, which are are assumed to come from profiling data
+/// attached by the frontend prior to llvm.expect intrinsic lowering. The
+/// ExpectedWeights parameter and the extracted real weights are then passed to
+/// verifyMisexpect() for verification
+///
+/// \param I The Instruction being checked
+/// \param ExpectedWeights A vector of the expected weights for each target
+/// block, this determines the threshold values used when emiting diagnostics
+void checkFrontendInstrumentation(Instruction &I,
+ const ArrayRef<uint32_t> ExpectedWeights);
+
+/// veryifyMisExpect - compares RealWeights to the thresholds used
+/// for llvm.expect and warns if the PGO counters are outside of the expected
+/// range.
+///
+/// \param I The Instruction being checked
+/// \param RealWeights A vector of profile weights from the profile data
+/// \param ExpectedWeights A vector of the weights attatch by llvm.expect
+void verifyMisExpect(Instruction &I, ArrayRef<uint32_t> RealWeights,
+ const ArrayRef<uint32_t> ExpectedWeights);
+
+/// checkExpectAnnotations - compares PGO counters to the thresholds used
+/// for llvm.expect and warns if the PGO counters are outside of the expected
+/// range. It extracts the expected weights from the MD_prof weights attatched
+/// to the instruction, which are are assumed to come from lowered llvm.expect
+/// intrinsics. The RealWeights parameter and the extracted expected weights are
+/// then passed to verifyMisexpect() for verification. It is a thin wrapper
+/// around the checkFrontendInstrumentation and checkBackendInstrumentation APIs
+///
+/// \param I The Instruction being checked
+/// \param RealWeights A vector of profile weights for each target block
+/// \param IsBackend A boolean describing if this is Frontend instrumentation
+void checkExpectAnnotations(Instruction &I,
+ const ArrayRef<uint32_t> ExistingWeights,
+ bool IsFrontend);
+
+} // namespace misexpect
+} // namespace llvm
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index f46f0fdd947d1..50fe6829ad86f 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -393,6 +393,17 @@ std::string DiagnosticInfoOptimizationBase::getMsg() const {
return OS.str();
}
+DiagnosticInfoMisExpect::DiagnosticInfoMisExpect(const Instruction *Inst,
+ Twine &Msg)
+ : DiagnosticInfoWithLocationBase(DK_MisExpect, DS_Warning,
+ *Inst->getParent()->getParent(),
+ Inst->getDebugLoc()),
+ Msg(Msg) {}
+
+void DiagnosticInfoMisExpect::print(DiagnosticPrinter &DP) const {
+ DP << getLocationStr() << ": " << getMsg();
+}
+
void OptimizationRemarkAnalysisFPCommute::anchor() {}
void OptimizationRemarkAnalysisAliasing::anchor() {}
diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp
index c9b0d957aa887..09ab4b9f75fe5 100644
--- a/llvm/lib/IR/LLVMContext.cpp
+++ b/llvm/lib/IR/LLVMContext.cpp
@@ -138,10 +138,22 @@ bool LLVMContext::getDiagnosticsHotnessRequested() const {
void LLVMContext::setDiagnosticsHotnessThreshold(Optional<uint64_t> Threshold) {
pImpl->DiagnosticsHotnessThreshold = Threshold;
}
-
+void LLVMContext::setMisExpectWarningRequested(bool Requested) {
+ pImpl->MisExpectWarningRequested = Requested;
+}
+bool LLVMContext::getMisExpectWarningRequested() const {
+ return pImpl->MisExpectWarningRequested;
+}
uint64_t LLVMContext::getDiagnosticsHotnessThreshold() const {
return pImpl->DiagnosticsHotnessThreshold.getValueOr(UINT64_MAX);
}
+void LLVMContext::setDiagnosticsMisExpectTolerance(
+ Optional<uint64_t> Tolerance) {
+ pImpl->DiagnosticsMisExpectTolerance = Tolerance;
+}
+uint64_t LLVMContext::getDiagnosticsMisExpectTolerance() const {
+ return pImpl->DiagnosticsMisExpectTolerance.getValueOr(0);
+}
bool LLVMContext::isDiagnosticsHotnessThresholdSetFromPSI() const {
return !pImpl->DiagnosticsHotnessThreshold.hasValue();
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 730f64b569439..2d07a26fd67a2 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -1384,6 +1384,11 @@ class LLVMContextImpl {
/// If threshold option is not specified, it is disabled (0) by default.
Optional<uint64_t> DiagnosticsHotnessThreshold = 0;
+ /// The percentage of
diff erence between profiling branch weights and
+ // llvm.expect branch weights to tolerate when emiting MisExpect diagnostics
+ Optional<uint64_t> DiagnosticsMisExpectTolerance = 0;
+ bool MisExpectWarningRequested = false;
+
/// The specialized remark streamer used by LLVM's OptimizationRemarkEmitter.
std::unique_ptr<LLVMRemarkStreamer> LLVMRS;
diff --git a/llvm/lib/Transforms/IPO/SampleProfile.cpp b/llvm/lib/Transforms/IPO/SampleProfile.cpp
index 82324069933e8..4bd7900f110ab 100644
--- a/llvm/lib/Transforms/IPO/SampleProfile.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfile.cpp
@@ -74,6 +74,8 @@
#include "llvm/Transforms/Instrumentation.h"
#include "llvm/Transforms/Utils/CallPromotionUtils.h"
#include "llvm/Transforms/Utils/Cloning.h"
+#include "llvm/Transforms/Utils/MisExpect.h"
+#include "llvm/Transforms/Utils/SampleProfileInference.h"
#include "llvm/Transforms/Utils/SampleProfileLoaderBaseImpl.h"
#include "llvm/Transforms/Utils/SampleProfileLoaderBaseUtil.h"
#include <algorithm>
@@ -1739,6 +1741,8 @@ void SampleProfileLoader::generateMDProfMetadata(Function &F) {
}
}
+ misexpect::checkExpectAnnotations(*TI, Weights, /*IsFrontend=*/false);
+
uint64_t TempWeight;
// Only set weights if there is at least one non-zero weight.
// In any other case, let the analyzer set weights.
diff --git a/llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp b/llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp
index e69a71c610865..a08632e8406c3 100644
--- a/llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp
+++ b/llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp
@@ -110,6 +110,7 @@
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Instrumentation.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
+#include "llvm/Transforms/Utils/MisExpect.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include <algorithm>
#include <cassert>
@@ -1966,6 +1967,8 @@ void llvm::setProfMetadata(Module *M, Instruction *TI,
dbgs() << W << " ";
} dbgs() << "\n";);
+ misexpect::checkExpectAnnotations(*TI, Weights, /*IsFrontend=*/false);
+
TI->setMetadata(LLVMContext::MD_prof, MDB.createBranchWeights(Weights));
if (EmitBranchProbability) {
std::string BrCondStr = getBranchCondString(TI);
diff --git a/llvm/lib/Transforms/Scalar/LowerExpectIntrinsic.cpp b/llvm/lib/Transforms/Scalar/LowerExpectIntrinsic.cpp
index 97c8f335192c9..88fad9896c59e 100644
--- a/llvm/lib/Transforms/Scalar/LowerExpectIntrinsic.cpp
+++ b/llvm/lib/Transforms/Scalar/LowerExpectIntrinsic.cpp
@@ -25,6 +25,7 @@
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Transforms/Scalar.h"
+#include "llvm/Transforms/Utils/MisExpect.h"
using namespace llvm;
@@ -99,6 +100,8 @@ static bool handleSwitchExpect(SwitchInst &SI) {
uint64_t Index = (Case == *SI.case_default()) ? 0 : Case.getCaseIndex() + 1;
Weights[Index] = LikelyBranchWeightVal;
+ misexpect::checkExpectAnnotations(SI, Weights, /*IsFrontend=*/true);
+
SI.setCondition(ArgValue);
SI.setMetadata(LLVMContext::MD_prof,
@@ -313,13 +316,16 @@ template <class BrSelInst> static bool handleBrSelExpect(BrSelInst &BSI) {
std::tie(LikelyBranchWeightVal, UnlikelyBranchWeightVal) =
getBranchWeight(Fn->getIntrinsicID(), CI, 2);
+ SmallVector<uint32_t, 4> ExpectedWeights;
if ((ExpectedValue->getZExtValue() == ValueComparedTo) ==
(Predicate == CmpInst::ICMP_EQ)) {
Node =
MDB.createBranchWeights(LikelyBranchWeightVal, UnlikelyBranchWeightVal);
+ ExpectedWeights = {LikelyBranchWeightVal, UnlikelyBranchWeightVal};
} else {
Node =
MDB.createBranchWeights(UnlikelyBranchWeightVal, LikelyBranchWeightVal);
+ ExpectedWeights = {UnlikelyBranchWeightVal, LikelyBranchWeightVal};
}
if (CmpI)
@@ -327,6 +333,8 @@ template <class BrSelInst> static bool handleBrSelExpect(BrSelInst &BSI) {
else
BSI.setCondition(ArgValue);
+ misexpect::checkFrontendInstrumentation(BSI, ExpectedWeights);
+
BSI.setMetadata(LLVMContext::MD_prof, Node);
return true;
@@ -407,7 +415,7 @@ class LowerExpectIntrinsic : public FunctionPass {
bool runOnFunction(Function &F) override { return lowerExpectIntrinsic(F); }
};
-}
+} // namespace
char LowerExpectIntrinsic::ID = 0;
INITIALIZE_PASS(LowerExpectIntrinsic, "lower-expect",
diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt
index c83969f7b12d5..2d13d3ce2b000 100644
--- a/llvm/lib/Transforms/Utils/CMakeLists.txt
+++ b/llvm/lib/Transforms/Utils/CMakeLists.txt
@@ -54,6 +54,7 @@ add_llvm_component_library(LLVMTransformUtils
MemoryTaggingSupport.cpp
Mem2Reg.cpp
MetaRenamer.cpp
+ MisExpect.cpp
ModuleUtils.cpp
NameAnonGlobals.cpp
PredicateInfo.cpp
diff --git a/llvm/lib/Transforms/Utils/MisExpect.cpp b/llvm/lib/Transforms/Utils/MisExpect.cpp
new file mode 100644
index 0000000000000..846badf6e941e
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/MisExpect.cpp
@@ -0,0 +1,244 @@
+//===--- MisExpect.cpp - Check the use of llvm.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 the
+// llvm.expect intrinsic. This utility extracts the threshold values from
+// metadata associated with the instrumented Branch or Switch instruction. The
+// threshold values are then used to determine if a warning should be emmited.
+//
+// MisExpect's implementation relies on two assumptions about how branch weights
+// are managed in LLVM.
+//
+// 1) Frontend profiling weights are always in place before llvm.expect is
+// lowered in LowerExpectIntrinsic.cpp. Frontend based instrumentation therefore
+// needs to extract the branch weights and then compare them to the weights
+// being added by the llvm.expect intrinsic lowering.
+//
+// 2) Sampling and IR based profiles will *only* have branch weight metadata
+// before profiling data is consulted if they are from a lowered llvm.expect
+// intrinsic. These profiles thus always extract the expected weights and then
+// compare them to the weights collected during profiling to determine if a
+// diagnostic message is warranted.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/MisExpect.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DiagnosticInfo.h"
+#include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/Support/BranchProbability.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/FormatVariadic.h"
+#include <cstdint>
+#include <functional>
+#include <numeric>
+
+#define DEBUG_TYPE "misexpect"
+
+using namespace llvm;
+using namespace misexpect;
+
+namespace llvm {
+
+// Command line option to enable/disable the warning when profile data suggests
+// a mismatch with the use of the llvm.expect intrinsic
+static cl::opt<bool> PGOWarnMisExpect(
+ "pgo-warn-misexpect", cl::init(false), cl::Hidden,
+ cl::desc("Use this option to turn on/off "
+ "warnings about incorrect usage of llvm.expect intrinsics."));
+
+static cl::opt<unsigned> MisExpectTolerance(
+ "misexpect-tolerance", cl::init(0),
+ cl::desc("Prevents emiting diagnostics when profile counts are "
+ "within N% of the threshold.."));
+
+} // namespace llvm
+
+namespace {
+
+bool isMisExpectDiagEnabled(LLVMContext &Ctx) {
+ return PGOWarnMisExpect || Ctx.getMisExpectWarningRequested();
+}
+
+uint64_t getMisExpectTolerance(LLVMContext &Ctx) {
+ return std::max(static_cast<uint64_t>(MisExpectTolerance),
+ Ctx.getDiagnosticsMisExpectTolerance());
+}
+
+Instruction *getInstCondition(Instruction *I) {
+ assert(I != nullptr && "MisExpect target Instruction cannot be nullptr");
+ Instruction *Ret = nullptr;
+ if (auto *B = dyn_cast<BranchInst>(I)) {
+ Ret = dyn_cast<Instruction>(B->getCondition());
+ }
+ // TODO: Find a way to resolve condition location for switches
+ // Using the condition of the switch seems to often resolve to an earlier
+ // point in the program, i.e. the calculation of the switch condition, rather
+ // than the switch's location in the source code. Thus, we should use the
+ // instruction to get source code locations rather than the condition to
+ // improve diagnostic output, such as the caret. If the same problem exists
+ // for branch instructions, then we should remove this function and directly
+ // use the instruction
+ //
+ else if (auto *S = dyn_cast<SwitchInst>(I)) {
+ Ret = dyn_cast<Instruction>(S->getCondition());
+ }
+ return Ret ? Ret : I;
+}
+
+void emitMisexpectDiagnostic(Instruction *I, LLVMContext &Ctx,
+ uint64_t ProfCount, uint64_t TotalCount) {
+ double PercentageCorrect = (double)ProfCount / TotalCount;
+ auto PerString =
+ formatv("{0:P} ({1} / {2})", PercentageCorrect, ProfCount, TotalCount);
+ auto RemStr = formatv(
+ "Potential performance regression from use of the llvm.expect intrinsic: "
+ "Annotation was correct on {0} of profiled executions.",
+ PerString);
+ Twine Msg(PerString);
+ Instruction *Cond = getInstCondition(I);
+ if (isMisExpectDiagEnabled(Ctx))
+ Ctx.diagnose(DiagnosticInfoMisExpect(Cond, Msg));
+ OptimizationRemarkEmitter ORE(I->getParent()->getParent());
+ ORE.emit(OptimizationRemark(DEBUG_TYPE, "misexpect", Cond) << RemStr.str());
+}
+
+} // namespace
+
+namespace llvm {
+namespace misexpect {
+
+// Helper function to extract branch weights into a vector
+Optional<SmallVector<uint32_t, 4>> extractWeights(Instruction *I,
+ LLVMContext &Ctx) {
+ assert(I && "MisExpect::extractWeights given invalid pointer");
+
+ auto *ProfileData = I->getMetadata(LLVMContext::MD_prof);
+ if (!ProfileData)
+ return None;
+
+ unsigned NOps = ProfileData->getNumOperands();
+ if (NOps < 3)
+ return None;
+
+ auto *ProfDataName = dyn_cast<MDString>(ProfileData->getOperand(0));
+ if (!ProfDataName || !ProfDataName->getString().equals("branch_weights"))
+ return None;
+
+ SmallVector<uint32_t, 4> Weights(NOps - 1);
+ for (unsigned Idx = 1; Idx < NOps; Idx++) {
+ ConstantInt *Value =
+ mdconst::dyn_extract<ConstantInt>(ProfileData->getOperand(Idx));
+ uint32_t V = Value->getZExtValue();
+ Weights[Idx - 1] = V;
+ }
+
+ return Weights;
+}
+
+// TODO: when clang allows c++17, use std::clamp instead
+uint32_t clamp(uint64_t value, uint32_t low, uint32_t hi) {
+ if (value > hi)
+ return hi;
+ if (value < low)
+ return low;
+ return value;
+}
+
+void verifyMisExpect(Instruction &I, ArrayRef<uint32_t> RealWeights,
+ ArrayRef<uint32_t> ExpectedWeights) {
+ // To determine if we emit a diagnostic, we need to compare the branch weights
+ // from the profile to those added by the llvm.expect intrinsic.
+ // So first, we extract the "likely" and "unlikely" weights from
+ // ExpectedWeights And determine the correct weight in the profile to compare
+ // against.
+ uint64_t LikelyBranchWeight = 0,
+ UnlikelyBranchWeight = std::numeric_limits<uint32_t>::max();
+ size_t MaxIndex = 0;
+ for (size_t Idx = 0, End = ExpectedWeights.size(); Idx < End; Idx++) {
+ uint32_t V = ExpectedWeights[Idx];
+ if (LikelyBranchWeight < V) {
+ LikelyBranchWeight = V;
+ MaxIndex = Idx;
+ }
+ if (UnlikelyBranchWeight > V) {
+ UnlikelyBranchWeight = V;
+ }
+ }
+
+ const uint64_t ProfiledWeight = RealWeights[MaxIndex];
+ const uint64_t RealWeightsTotal =
+ std::accumulate(RealWeights.begin(), RealWeights.end(), (uint64_t)0,
+ std::plus<uint64_t>());
+ const uint64_t NumUnlikelyTargets = RealWeights.size() - 1;
+
+ uint64_t TotalBranchWeight =
+ LikelyBranchWeight + (UnlikelyBranchWeight * NumUnlikelyTargets);
+
+ assert((TotalBranchWeight >= LikelyBranchWeight) && (TotalBranchWeight > 0) &&
+ "TotalBranchWeight is less than the Likely branch weight");
+
+ // To determine our threshold value we need to obtain the branch probability
+ // for the weights added by llvm.expect and use that proportion to calculate
+ // our threshold based on the collected profile data.
+ auto LikelyProbablilty = BranchProbability::getBranchProbability(
+ LikelyBranchWeight, TotalBranchWeight);
+
+ uint64_t ScaledThreshold = LikelyProbablilty.scale(RealWeightsTotal);
+
+ // clamp tolerance range to [0, 100)
+ auto Tolerance = getMisExpectTolerance(I.getContext());
+ Tolerance = clamp(Tolerance, 0, 99);
+
+ // Allow users to relax checking by N% i.e., if they use a 5% tolerance,
+ // then we check against 0.95*ScaledThreshold
+ if (Tolerance > 0)
+ ScaledThreshold *= (1.0 - Tolerance / 100.0);
+
+ // When the profile weight is below the threshold, we emit the diagnostic
+ if (ProfiledWeight < ScaledThreshold)
+ emitMisexpectDiagnostic(&I, I.getContext(), ProfiledWeight,
+ RealWeightsTotal);
+}
+
+void checkBackendInstrumentation(Instruction &I,
+ const ArrayRef<uint32_t> RealWeights) {
+ auto ExpectedWeightsOpt = extractWeights(&I, I.getContext());
+ if (!ExpectedWeightsOpt.hasValue())
+ return;
+ auto ExpectedWeights = ExpectedWeightsOpt.getValue();
+ verifyMisExpect(I, RealWeights, ExpectedWeights);
+}
+
+void checkFrontendInstrumentation(Instruction &I,
+ const ArrayRef<uint32_t> ExpectedWeights) {
+ auto RealWeightsOpt = extractWeights(&I, I.getContext());
+ if (!RealWeightsOpt.hasValue())
+ return;
+ auto RealWeights = RealWeightsOpt.getValue();
+ verifyMisExpect(I, RealWeights, ExpectedWeights);
+}
+
+void checkExpectAnnotations(Instruction &I,
+ const ArrayRef<uint32_t> ExistingWeights,
+ bool IsFrontendInstr) {
+ if (IsFrontendInstr) {
+ checkFrontendInstrumentation(I, ExistingWeights);
+ } else {
+ checkBackendInstrumentation(I, ExistingWeights);
+ }
+}
+
+} // namespace misexpect
+} // namespace llvm
+#undef DEBUG_TYPE
diff --git a/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-correct.proftext b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-correct.proftext
new file mode 100644
index 0000000000000..24c9746262bda
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-correct.proftext
@@ -0,0 +1,38 @@
+# IR level Instrumentation Flag
+:ir
+bar
+# Func Hash:
+146835647075900052
+# Num Counters:
+2
+# Counter Values:
+200000
+0
+
+baz
+# Func Hash:
+12884901887
+# Num Counters:
+1
+# Counter Values:
+399668
+
+foo
+# Func Hash:
+29212902728
+# Num Counters:
+2
+# Counter Values:
+40803991
+1600332
+
+main
+# Func Hash:
+41605652536
+# Num Counters:
+3
+# Counter Values:
+2000000
+2000
+1
+
diff --git a/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-overflow.proftext b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-overflow.proftext
new file mode 100644
index 0000000000000..a30533736dfd4
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-overflow.proftext
@@ -0,0 +1,37 @@
+# IR level Instrumentation Flag
+:ir
+bar
+# Func Hash:
+146835647075900052
+# Num Counters:
+2
+# Counter Values:
+2147483648
+2147483648
+
+baz
+# Func Hash:
+12884901887
+# Num Counters:
+1
+# Counter Values:
+399668
+
+foo
+# Func Hash:
+29212902728
+# Num Counters:
+2
+# Counter Values:
+40803991
+1600332
+
+main
+# Func Hash:
+41605652536
+# Num Counters:
+2
+# Counter Values:
+2147483648
+2147483648
+
diff --git a/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-threshold.proftext b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-threshold.proftext
new file mode 100644
index 0000000000000..dcfffba1c12de
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch-threshold.proftext
@@ -0,0 +1,38 @@
+# IR level Instrumentation Flag
+:ir
+bar
+# Func Hash:
+146835647075900052
+# Num Counters:
+2
+# Counter Values:
+100
+10
+
+baz
+# Func Hash:
+12884901887
+# Num Counters:
+1
+# Counter Values:
+399668
+
+foo
+# Func Hash:
+29212902728
+# Num Counters:
+2
+# Counter Values:
+40803991
+1600332
+
+main
+# Func Hash:
+41605652536
+# Num Counters:
+3
+# Counter Values:
+2000000
+2000
+1
+
diff --git a/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch.proftext b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch.proftext
new file mode 100644
index 0000000000000..5505df7375271
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-branch.proftext
@@ -0,0 +1,38 @@
+# IR level Instrumentation Flag
+:ir
+bar
+# Func Hash:
+146835647075900052
+# Num Counters:
+2
+# Counter Values:
+399668
+1600332
+
+baz
+# Func Hash:
+12884901887
+# Num Counters:
+1
+# Counter Values:
+399668
+
+foo
+# Func Hash:
+29212902728
+# Num Counters:
+2
+# Counter Values:
+40803991
+1600332
+
+main
+# Func Hash:
+41605652536
+# Num Counters:
+3
+# Counter Values:
+2000000
+2000
+1
+
diff --git a/llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch-correct.proftext b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch-correct.proftext
new file mode 100644
index 0000000000000..8dfaaa1b85c27
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch-correct.proftext
@@ -0,0 +1,15 @@
+# IR level Instrumentation Flag
+:ir
+main
+# Func Hash:
+391331300939170156
+# Num Counters:
+7
+# Counter Values:
+0
+0
+20000
+0
+0
+1
+0
diff --git a/llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch.proftext b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch.proftext
new file mode 100644
index 0000000000000..61c41a5a33fab
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/Inputs/misexpect-switch.proftext
@@ -0,0 +1,15 @@
+# IR level Instrumentation Flag
+:ir
+main
+# Func Hash:
+391331300939170156
+# Num Counters:
+7
+# Counter Values:
+3973
+3970
+0
+11889
+8111
+1
+0
diff --git a/llvm/test/Transforms/PGOProfile/misexpect-branch-correct.ll b/llvm/test/Transforms/PGOProfile/misexpect-branch-correct.ll
new file mode 100644
index 0000000000000..63060d39e8ee5
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/misexpect-branch-correct.ll
@@ -0,0 +1,94 @@
+; Test misexpect checks do not issue diagnostics when profiling weights and
+; branch weights added by llvm.expect agree
+
+; RUN: llvm-profdata merge %S/Inputs/misexpect-branch-correct.proftext -o %t.profdata
+
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -pass-remarks=misexpect -S 2>&1 | FileCheck %s
+
+; CHECK-NOT: warning: {{.*}}
+; CHECK-NOT: remark: {{.*}}
+; CHECK: !{!"branch_weights", i32 0, i32 200000}
+
+
+; ModuleID = 'misexpect-branch-correct.c'
+source_filename = "misexpect-branch-correct.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at inner_loop = constant i32 100, align 4
+ at outer_loop = constant i32 2000, align 4
+
+; Function Attrs: nounwind
+define i32 @bar() #0 {
+entry:
+ %rando = alloca i32, align 4
+ %x = alloca i32, align 4
+ %0 = bitcast i32* %rando to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #4
+ %call = call i32 (...) @buzz()
+ store i32 %call, i32* %rando, align 4, !tbaa !3
+ %1 = bitcast i32* %x to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #4
+ store i32 0, i32* %x, align 4, !tbaa !3
+ %2 = load i32, i32* %rando, align 4, !tbaa !3
+ %rem = srem i32 %2, 200000
+ %cmp = icmp eq i32 %rem, 0
+ %lnot = xor i1 %cmp, true
+ %lnot1 = xor i1 %lnot, true
+ %lnot.ext = zext i1 %lnot1 to i32
+ %conv = sext i32 %lnot.ext to i64
+ %expval = call i64 @llvm.expect.i64(i64 %conv, i64 0)
+ %tobool = icmp ne i64 %expval, 0
+ br i1 %tobool, label %if.then, label %if.else
+
+if.then: ; preds = %entry
+ %3 = load i32, i32* %rando, align 4, !tbaa !3
+ %call2 = call i32 @baz(i32 %3)
+ store i32 %call2, i32* %x, align 4, !tbaa !3
+ br label %if.end
+
+if.else: ; preds = %entry
+ %call3 = call i32 @foo(i32 50)
+ store i32 %call3, i32* %x, align 4, !tbaa !3
+ br label %if.end
+
+if.end: ; preds = %if.else, %if.then
+ %4 = load i32, i32* %x, align 4, !tbaa !3
+ %5 = bitcast i32* %x to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %5) #4
+ %6 = bitcast i32* %rando to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %6) #4
+ ret i32 %4
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
+
+declare i32 @buzz(...) #2
+
+; Function Attrs: nounwind readnone willreturn
+declare i64 @llvm.expect.i64(i64, i64) #3
+
+declare i32 @baz(i32) #2
+
+declare i32 @foo(i32) #2
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
+
+attributes #0 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { argmemonly nounwind willreturn }
+attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { nounwind readnone willreturn }
+attributes #4 = { nounwind }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+
+!0 = !{i32 2, !"Debug Info Version", i32 3}
+!1 = !{i32 1, !"wchar_size", i32 4}
+!2 = !{!"clang version 10.0.0 (c20270bfffc9d6965219de339d66c61e9fe7d82d)"}
+!3 = !{!4, !4, i64 0}
+!4 = !{!"int", !5, i64 0}
+!5 = !{!"omnipotent char", !6, i64 0}
+!6 = !{!"Simple C/C++ TBAA"}
diff --git a/llvm/test/Transforms/PGOProfile/misexpect-branch-overflow.ll b/llvm/test/Transforms/PGOProfile/misexpect-branch-overflow.ll
new file mode 100644
index 0000000000000..7279f1ab3d60f
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/misexpect-branch-overflow.ll
@@ -0,0 +1,106 @@
+; Ensure that profile counters that overflow are handled correctly.
+; This can happen when the sum of all counters exceeds the max size of uint32_t
+
+; RUN: llvm-profdata merge %S/Inputs/misexpect-branch-overflow.proftext -o %t.profdata
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -pass-remarks=misexpect -unlikely-branch-weight=2147483648 -likely-branch-weight=2147483648 -S 2>&1 | FileCheck %s --check-prefix=OVERFLOW
+
+; OVERFLOW-NOT: warning: misexpect-branch.c:22:0: 50.00%
+; OVERFLOW-NOT: remark: misexpect-branch.c:22:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 50.00% (2147483648 / 4294967296) of profiled executions.
+
+; ModuleID = 'misexpect-branch.c'
+source_filename = "misexpect-branch.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at inner_loop = constant i32 100, align 4
+ at outer_loop = constant i32 2000, align 4
+
+; Function Attrs: nounwind
+define i32 @bar() #0 !dbg !6 {
+entry:
+ %rando = alloca i32, align 4
+ %x = alloca i32, align 4
+ %0 = bitcast i32* %rando to i8*, !dbg !9
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #4, !dbg !9
+ %call = call i32 (...) @buzz(), !dbg !9
+ store i32 %call, i32* %rando, align 4, !dbg !9, !tbaa !10
+ %1 = bitcast i32* %x to i8*, !dbg !14
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #4, !dbg !14
+ store i32 0, i32* %x, align 4, !dbg !14, !tbaa !10
+ %2 = load i32, i32* %rando, align 4, !dbg !15, !tbaa !10
+ %rem = srem i32 %2, 200000, !dbg !15
+ %cmp = icmp eq i32 %rem, 0, !dbg !15
+ %lnot = xor i1 %cmp, true, !dbg !15
+ %lnot1 = xor i1 %lnot, true, !dbg !15
+ %lnot.ext = zext i1 %lnot1 to i32, !dbg !15
+ %conv = sext i32 %lnot.ext to i64, !dbg !15
+ %expval = call i64 @llvm.expect.i64(i64 %conv, i64 1), !dbg !15
+ %tobool = icmp ne i64 %expval, 0, !dbg !15
+ br i1 %tobool, label %if.then, label %if.else, !dbg !15
+
+if.then: ; preds = %entry
+ %3 = load i32, i32* %rando, align 4, !dbg !16, !tbaa !10
+ %call2 = call i32 @baz(i32 %3), !dbg !16
+ store i32 %call2, i32* %x, align 4, !dbg !16, !tbaa !10
+ br label %if.end, !dbg !17
+
+if.else: ; preds = %entry
+ %call3 = call i32 @foo(i32 50), !dbg !18
+ store i32 %call3, i32* %x, align 4, !dbg !18, !tbaa !10
+ br label %if.end
+
+if.end: ; preds = %if.else, %if.then
+ %4 = load i32, i32* %x, align 4, !dbg !19, !tbaa !10
+ %5 = bitcast i32* %x to i8*, !dbg !20
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %5) #4, !dbg !20
+ %6 = bitcast i32* %rando to i8*, !dbg !20
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %6) #4, !dbg !20
+ ret i32 %4, !dbg !19
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
+
+declare i32 @buzz(...) #2
+
+; Function Attrs: nounwind readnone willreturn
+declare i64 @llvm.expect.i64(i64, i64) #3
+
+declare i32 @baz(i32) #2
+
+declare i32 @foo(i32) #2
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
+
+attributes #0 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { argmemonly nounwind willreturn }
+attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { nounwind readnone willreturn }
+attributes #4 = { nounwind }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+!llvm.ident = !{!5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 10.0.0 (trunk c20270bfffc9d6965219de339d66c61e9fe7d82d)", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, enums: !2, nameTableKind: None)
+!1 = !DIFile(filename: "<stdin>", directory: ".")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{!"clang version 10.0.0 (trunk c20270bfffc9d6965219de339d66c61e9fe7d82d)"}
+!6 = distinct !DISubprogram(name: "bar", scope: !7, file: !7, line: 19, type: !8, scopeLine: 19, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
+!7 = !DIFile(filename: "misexpect-branch.c", directory: ".")
+!8 = !DISubroutineType(types: !2)
+!9 = !DILocation(line: 20, scope: !6)
+!10 = !{!11, !11, i64 0}
+!11 = !{!"int", !12, i64 0}
+!12 = !{!"omnipotent char", !13, i64 0}
+!13 = !{!"Simple C/C++ TBAA"}
+!14 = !DILocation(line: 21, scope: !6)
+!15 = !DILocation(line: 22, scope: !6)
+!16 = !DILocation(line: 23, scope: !6)
+!17 = !DILocation(line: 24, scope: !6)
+!18 = !DILocation(line: 25, scope: !6)
+!19 = !DILocation(line: 27, scope: !6)
+!20 = !DILocation(line: 28, scope: !6)
diff --git a/llvm/test/Transforms/PGOProfile/misexpect-branch-stripped.ll b/llvm/test/Transforms/PGOProfile/misexpect-branch-stripped.ll
new file mode 100644
index 0000000000000..63f3bd7183f70
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/misexpect-branch-stripped.ll
@@ -0,0 +1,104 @@
+; Test that misexpect diagnostics handle stripped debug info gracefully
+
+; RUN: llvm-profdata merge %S/Inputs/misexpect-branch.proftext -o %t.profdata
+
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -S 2>&1 | FileCheck %s --check-prefix=WARNING
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=REMARK
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=BOTH
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -S 2>&1 | FileCheck %s --check-prefix=DISABLED
+
+; WARNING-DAG: warning: <unknown>:0:0: 19.98%
+; WARNING-NOT: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; REMARK-NOT: warning: <unknown>:0:0: 19.98%
+; REMARK-DAG: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; BOTH-DAG: warning: <unknown>:0:0: 19.98%
+; BOTH-DAG: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; DISABLED-NOT: warning: <unknown>:0:0: 19.98%
+; DISABLED-NOT: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+
+; ModuleID = 'misexpect-branch.c'
+source_filename = "misexpect-branch.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at inner_loop = constant i32 100, align 4
+ at outer_loop = constant i32 2000, align 4
+
+; Function Attrs: nounwind
+define i32 @bar() #0 {
+entry:
+ %rando = alloca i32, align 4
+ %x = alloca i32, align 4
+ %0 = bitcast i32* %rando to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #4
+ %call = call i32 (...) @buzz()
+ store i32 %call, i32* %rando, align 4, !tbaa !3
+ %1 = bitcast i32* %x to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #4
+ store i32 0, i32* %x, align 4, !tbaa !3
+ %2 = load i32, i32* %rando, align 4, !tbaa !3
+ %rem = srem i32 %2, 200000
+ %cmp = icmp eq i32 %rem, 0
+ %lnot = xor i1 %cmp, true
+ %lnot1 = xor i1 %lnot, true
+ %lnot.ext = zext i1 %lnot1 to i32
+ %conv = sext i32 %lnot.ext to i64
+ %expval = call i64 @llvm.expect.i64(i64 %conv, i64 1)
+ %tobool = icmp ne i64 %expval, 0
+ br i1 %tobool, label %if.then, label %if.else
+
+if.then: ; preds = %entry
+ %3 = load i32, i32* %rando, align 4, !tbaa !3
+ %call2 = call i32 @baz(i32 %3)
+ store i32 %call2, i32* %x, align 4, !tbaa !3
+ br label %if.end
+
+if.else: ; preds = %entry
+ %call3 = call i32 @foo(i32 50)
+ store i32 %call3, i32* %x, align 4, !tbaa !3
+ br label %if.end
+
+if.end: ; preds = %if.else, %if.then
+ %4 = load i32, i32* %x, align 4, !tbaa !3
+ %5 = bitcast i32* %x to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %5) #4
+ %6 = bitcast i32* %rando to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %6) #4
+ ret i32 %4
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
+
+declare i32 @buzz(...) #2
+
+; Function Attrs: nounwind readnone willreturn
+declare i64 @llvm.expect.i64(i64, i64) #3
+
+declare i32 @baz(i32) #2
+
+declare i32 @foo(i32) #2
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
+
+attributes #0 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { argmemonly nounwind willreturn }
+attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { nounwind readnone willreturn }
+attributes #4 = { nounwind }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+
+!0 = !{i32 2, !"Debug Info Version", i32 3}
+!1 = !{i32 1, !"wchar_size", i32 4}
+!2 = !{!"clang version 10.0.0 (trunk c20270bfffc9d6965219de339d66c61e9fe7d82d)"}
+!3 = !{!4, !4, i64 0}
+!4 = !{!"int", !5, i64 0}
+!5 = !{!"omnipotent char", !6, i64 0}
+!6 = !{!"Simple C/C++ TBAA"}
diff --git a/llvm/test/Transforms/PGOProfile/misexpect-branch-unpredictable.ll b/llvm/test/Transforms/PGOProfile/misexpect-branch-unpredictable.ll
new file mode 100644
index 0000000000000..21905ce60b3a5
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/misexpect-branch-unpredictable.ll
@@ -0,0 +1,87 @@
+; Test misexpect doesn't issue diagnostics when a branch is marked unpredictable
+
+; RUN: llvm-profdata merge %S/Inputs/misexpect-branch-correct.proftext -o %t.profdata
+
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -pass-remarks=misexpect -S 2>&1 | FileCheck %s
+
+; CHECK-NOT: warning: {{.*}}
+; CHECK-NOT: remark: {{.*}}
+
+
+; ModuleID = 'misexpect-branch-unpredictable.c'
+source_filename = "clang/test/Profile/misexpect-branch-unpredictable.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at inner_loop = constant i32 100, align 4
+ at outer_loop = constant i32 2000, align 4
+
+; Function Attrs: nounwind
+define i32 @bar() #0 {
+entry:
+ %rando = alloca i32, align 4
+ %x = alloca i32, align 4
+ %0 = bitcast i32* %rando to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #3
+ %call = call i32 (...) @buzz()
+ store i32 %call, i32* %rando, align 4, !tbaa !2
+ %1 = bitcast i32* %x to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #3
+ store i32 0, i32* %x, align 4, !tbaa !2
+ %2 = load i32, i32* %rando, align 4, !tbaa !2
+ %rem = srem i32 %2, 200000
+ %cmp = icmp eq i32 %rem, 0
+ %lnot = xor i1 %cmp, true
+ %lnot1 = xor i1 %lnot, true
+ %lnot.ext = zext i1 %lnot1 to i32
+ %conv = sext i32 %lnot.ext to i64
+ %tobool = icmp ne i64 %conv, 0
+ br i1 %tobool, label %if.then, label %if.else, !unpredictable !6
+
+if.then: ; preds = %entry
+ %3 = load i32, i32* %rando, align 4, !tbaa !2
+ %call2 = call i32 @baz(i32 %3)
+ store i32 %call2, i32* %x, align 4, !tbaa !2
+ br label %if.end
+
+if.else: ; preds = %entry
+ %call3 = call i32 @foo(i32 50)
+ store i32 %call3, i32* %x, align 4, !tbaa !2
+ br label %if.end
+
+if.end: ; preds = %if.else, %if.then
+ %4 = load i32, i32* %x, align 4, !tbaa !2
+ %5 = bitcast i32* %x to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %5) #3
+ %6 = bitcast i32* %rando to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %6) #3
+ ret i32 %4
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
+
+declare i32 @buzz(...) #2
+
+declare i32 @baz(i32) #2
+
+declare i32 @foo(i32) #2
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
+
+attributes #0 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { argmemonly nounwind willreturn }
+attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { nounwind }
+
+!llvm.module.flags = !{!0}
+!llvm.ident = !{!1}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{!"Fuchsia clang version 10.0.0 (153b453014c94291c8c6cf6320b2f46df40f26f3) (based on LLVM 10.0.0svn)"}
+!2 = !{!3, !3, i64 0}
+!3 = !{!"int", !4, i64 0}
+!4 = !{!"omnipotent char", !5, i64 0}
+!5 = !{!"Simple C/C++ TBAA"}
+!6 = !{}
diff --git a/llvm/test/Transforms/PGOProfile/misexpect-branch.ll b/llvm/test/Transforms/PGOProfile/misexpect-branch.ll
new file mode 100644
index 0000000000000..655785c0d6077
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/misexpect-branch.ll
@@ -0,0 +1,123 @@
+; Test that misexpect diagnostics are emtted when profile branch weights are
+; below the branch weights added by llvm.expect intrinsics
+
+; RUN: llvm-profdata merge %S/Inputs/misexpect-branch.proftext -o %t.profdata
+
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -S 2>&1 | FileCheck %s --check-prefix=WARNING
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=REMARK
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=BOTH
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -S 2>&1 | FileCheck %s --check-prefix=DISABLED
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -misexpect-tolerance=81 -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=THRESHOLD
+
+; WARNING-DAG: warning: misexpect-branch.c:22:0: 19.98%
+; WARNING-NOT: remark: misexpect-branch.c:22:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; REMARK-NOT: warning: misexpect-branch.c:22:0: 19.98%
+; REMARK-DAG: remark: misexpect-branch.c:22:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; BOTH-DAG: warning: misexpect-branch.c:22:0: 19.98%
+; BOTH-DAG: remark: misexpect-branch.c:22:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; DISABLED-NOT: warning: misexpect-branch.c:22:0: 19.98%
+; DISABLED-NOT: remark: misexpect-branch.c:22:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; THRESHOLD-NOT: warning: misexpect-branch.c:22:0: 19.98%
+; THRESHOLD-NOT: remark: misexpect-branch.c:22:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 19.98% (399668 / 2000000) of profiled executions.
+
+; ModuleID = 'misexpect-branch.c'
+source_filename = "misexpect-branch.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at inner_loop = constant i32 100, align 4
+ at outer_loop = constant i32 2000, align 4
+
+; Function Attrs: nounwind
+define i32 @bar() #0 !dbg !6 {
+entry:
+ %rando = alloca i32, align 4
+ %x = alloca i32, align 4
+ %0 = bitcast i32* %rando to i8*, !dbg !9
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #4, !dbg !9
+ %call = call i32 (...) @buzz(), !dbg !9
+ store i32 %call, i32* %rando, align 4, !dbg !9, !tbaa !10
+ %1 = bitcast i32* %x to i8*, !dbg !14
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #4, !dbg !14
+ store i32 0, i32* %x, align 4, !dbg !14, !tbaa !10
+ %2 = load i32, i32* %rando, align 4, !dbg !15, !tbaa !10
+ %rem = srem i32 %2, 200000, !dbg !15
+ %cmp = icmp eq i32 %rem, 0, !dbg !15
+ %lnot = xor i1 %cmp, true, !dbg !15
+ %lnot1 = xor i1 %lnot, true, !dbg !15
+ %lnot.ext = zext i1 %lnot1 to i32, !dbg !15
+ %conv = sext i32 %lnot.ext to i64, !dbg !15
+ %expval = call i64 @llvm.expect.i64(i64 %conv, i64 1), !dbg !15
+ %tobool = icmp ne i64 %expval, 0, !dbg !15
+ br i1 %tobool, label %if.then, label %if.else, !dbg !15
+
+if.then: ; preds = %entry
+ %3 = load i32, i32* %rando, align 4, !dbg !16, !tbaa !10
+ %call2 = call i32 @baz(i32 %3), !dbg !16
+ store i32 %call2, i32* %x, align 4, !dbg !16, !tbaa !10
+ br label %if.end, !dbg !17
+
+if.else: ; preds = %entry
+ %call3 = call i32 @foo(i32 50), !dbg !18
+ store i32 %call3, i32* %x, align 4, !dbg !18, !tbaa !10
+ br label %if.end
+
+if.end: ; preds = %if.else, %if.then
+ %4 = load i32, i32* %x, align 4, !dbg !19, !tbaa !10
+ %5 = bitcast i32* %x to i8*, !dbg !20
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %5) #4, !dbg !20
+ %6 = bitcast i32* %rando to i8*, !dbg !20
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %6) #4, !dbg !20
+ ret i32 %4, !dbg !19
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
+
+declare i32 @buzz(...) #2
+
+; Function Attrs: nounwind readnone willreturn
+declare i64 @llvm.expect.i64(i64, i64) #3
+
+declare i32 @baz(i32) #2
+
+declare i32 @foo(i32) #2
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
+
+attributes #0 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { argmemonly nounwind willreturn }
+attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { nounwind readnone willreturn }
+attributes #4 = { nounwind }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+!llvm.ident = !{!5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 10.0.0 (trunk c20270bfffc9d6965219de339d66c61e9fe7d82d)", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, enums: !2, nameTableKind: None)
+!1 = !DIFile(filename: "<stdin>", directory: ".")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{!"clang version 10.0.0 (trunk c20270bfffc9d6965219de339d66c61e9fe7d82d)"}
+!6 = distinct !DISubprogram(name: "bar", scope: !7, file: !7, line: 19, type: !8, scopeLine: 19, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
+!7 = !DIFile(filename: "misexpect-branch.c", directory: ".")
+!8 = !DISubroutineType(types: !2)
+!9 = !DILocation(line: 20, scope: !6)
+!10 = !{!11, !11, i64 0}
+!11 = !{!"int", !12, i64 0}
+!12 = !{!"omnipotent char", !13, i64 0}
+!13 = !{!"Simple C/C++ TBAA"}
+!14 = !DILocation(line: 21, scope: !6)
+!15 = !DILocation(line: 22, scope: !6)
+!16 = !DILocation(line: 23, scope: !6)
+!17 = !DILocation(line: 24, scope: !6)
+!18 = !DILocation(line: 25, scope: !6)
+!19 = !DILocation(line: 27, scope: !6)
+!20 = !DILocation(line: 28, scope: !6)
diff --git a/llvm/test/Transforms/PGOProfile/misexpect-switch-default.ll b/llvm/test/Transforms/PGOProfile/misexpect-switch-default.ll
new file mode 100644
index 0000000000000..dc2c426f1b2ce
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/misexpect-switch-default.ll
@@ -0,0 +1,189 @@
+; Test misexpect handles switch statements when debug information is stripped
+
+; RUN: llvm-profdata merge %S/Inputs/misexpect-switch.proftext -o %t.profdata
+
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -S 2>&1 | FileCheck %s --check-prefix=WARNING
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=REMARK
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=BOTH
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -S 2>&1 | FileCheck %s --check-prefix=DISABLED
+
+; WARNING-DAG: warning: <unknown>:0:0: 0.00%
+; WARNING-NOT: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 27943) of profiled executions.
+
+; REMARK-NOT: warning: <unknown>:0:0: 0.00%
+; REMARK-DAG: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 27943) of profiled executions.
+
+; BOTH-DAG: warning: <unknown>:0:0: 0.00%
+; BOTH-DAG: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 27943) of profiled executions.
+
+; DISABLED-NOT: warning: <unknown>:0:0: 0.00%
+; DISABLED-NOT: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 27943) of profiled executions.
+
+; DISABLED-NOT: warning: <unknown>:0:0: 0.00%
+; DISABLED-NOT: remark: <unknown>:0:0: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 27943) of profiled executions.
+
+; CORRECT-NOT: warning: {{.*}}
+; CORRECT-NOT: remark: {{.*}}
+
+
+; ModuleID = 'misexpect-switch.c'
+source_filename = "misexpect-switch.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at inner_loop = dso_local constant i32 1000, align 4
+ at outer_loop = dso_local constant i32 20, align 4
+ at arry_size = dso_local constant i32 25, align 4
+ at arry = dso_local global [25 x i32] zeroinitializer, align 16
+
+; Function Attrs: nounwind uwtable
+define dso_local void @init_arry() #0 {
+entry:
+ %i = alloca i32, align 4
+ %0 = bitcast i32* %i to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #6
+ store i32 0, i32* %i, align 4, !tbaa !4
+ br label %for.cond
+
+for.cond: ; preds = %for.inc, %entry
+ %1 = load i32, i32* %i, align 4, !tbaa !4
+ %cmp = icmp slt i32 %1, 25
+ br i1 %cmp, label %for.body, label %for.end
+
+for.body: ; preds = %for.cond
+ %call = call i32 @rand() #6
+ %rem = srem i32 %call, 10
+ %2 = load i32, i32* %i, align 4, !tbaa !4
+ %idxprom = sext i32 %2 to i64
+ %arrayidx = getelementptr inbounds [25 x i32], [25 x i32]* @arry, i64 0, i64 %idxprom
+ store i32 %rem, i32* %arrayidx, align 4, !tbaa !4
+ br label %for.inc
+
+for.inc: ; preds = %for.body
+ %3 = load i32, i32* %i, align 4, !tbaa !4
+ %inc = add nsw i32 %3, 1
+ store i32 %inc, i32* %i, align 4, !tbaa !4
+ br label %for.cond
+
+for.end: ; preds = %for.cond
+ %4 = bitcast i32* %i to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %4) #6
+ ret void
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
+
+; Function Attrs: nounwind readnone speculatable willreturn
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #2
+
+; Function Attrs: nounwind
+declare dso_local i32 @rand() #3
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
+
+; Function Attrs: nounwind uwtable
+define dso_local i32 @main() #0 {
+entry:
+ %retval = alloca i32, align 4
+ %val = alloca i32, align 4
+ %j = alloca i32, align 4
+ %condition = alloca i32, align 4
+ store i32 0, i32* %retval, align 4
+ call void @init_arry()
+ %0 = bitcast i32* %val to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #6
+ store i32 0, i32* %val, align 4, !tbaa !4
+ %1 = bitcast i32* %j to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #6
+ store i32 0, i32* %j, align 4, !tbaa !4
+ br label %for.cond
+
+for.cond: ; preds = %for.inc, %entry
+ %2 = load i32, i32* %j, align 4, !tbaa !4
+ %cmp = icmp slt i32 %2, 20000
+ br i1 %cmp, label %for.body, label %for.end
+
+for.body: ; preds = %for.cond
+ %3 = bitcast i32* %condition to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %3) #6
+ %call = call i32 @rand() #6
+ %rem = srem i32 %call, 5
+ store i32 %rem, i32* %condition, align 4, !tbaa !4
+ %4 = load i32, i32* %condition, align 4, !tbaa !4
+ %conv = zext i32 %4 to i64
+ %expval = call i64 @llvm.expect.i64(i64 %conv, i64 6)
+ switch i64 %expval, label %sw.default [
+ i64 0, label %sw.bb
+ i64 1, label %sw.bb2
+ i64 2, label %sw.bb2
+ i64 3, label %sw.bb2
+ i64 4, label %sw.bb3
+ ]
+
+sw.bb: ; preds = %for.body
+ %call1 = call i32 @sum(i32* getelementptr inbounds ([25 x i32], [25 x i32]* @arry, i64 0, i64 0), i32 25)
+ %5 = load i32, i32* %val, align 4, !tbaa !4
+ %add = add nsw i32 %5, %call1
+ store i32 %add, i32* %val, align 4, !tbaa !4
+ br label %sw.epilog
+
+sw.bb2: ; preds = %for.body, %for.body, %for.body
+ br label %sw.epilog
+
+sw.bb3: ; preds = %for.body
+ %call4 = call i32 @random_sample(i32* getelementptr inbounds ([25 x i32], [25 x i32]* @arry, i64 0, i64 0), i32 25)
+ %6 = load i32, i32* %val, align 4, !tbaa !4
+ %add5 = add nsw i32 %6, %call4
+ store i32 %add5, i32* %val, align 4, !tbaa !4
+ br label %sw.epilog
+
+sw.default: ; preds = %for.body
+ unreachable
+
+sw.epilog: ; preds = %sw.bb3, %sw.bb2, %sw.bb
+ %7 = bitcast i32* %condition to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %7) #6
+ br label %for.inc
+
+for.inc: ; preds = %sw.epilog
+ %8 = load i32, i32* %j, align 4, !tbaa !4
+ %inc = add nsw i32 %8, 1
+ store i32 %inc, i32* %j, align 4, !tbaa !4
+ br label %for.cond
+
+for.end: ; preds = %for.cond
+ %9 = bitcast i32* %j to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %9) #6
+ %10 = bitcast i32* %val to i8*
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %10) #6
+ ret i32 0
+}
+
+; Function Attrs: nounwind readnone willreturn
+declare i64 @llvm.expect.i64(i64, i64) #4
+
+declare dso_local i32 @sum(i32*, i32) #5
+
+declare dso_local i32 @random_sample(i32*, i32) #5
+
+attributes #0 = { nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { argmemonly nounwind willreturn }
+attributes #2 = { nounwind readnone speculatable willreturn }
+attributes #3 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #4 = { nounwind readnone willreturn }
+attributes #5 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #6 = { nounwind }
+
+!llvm.module.flags = !{!0, !1, !2}
+!llvm.ident = !{!3}
+
+!0 = !{i32 2, !"Dwarf Version", i32 4}
+!1 = !{i32 2, !"Debug Info Version", i32 3}
+!2 = !{i32 1, !"wchar_size", i32 4}
+!3 = !{!"clang version 10.0.0 (60b79b85b1763d3d25630261e5cd1adb7f0835bc)"}
+!4 = !{!5, !5, i64 0}
+!5 = !{!"int", !6, i64 0}
+!6 = !{!"omnipotent char", !7, i64 0}
+!7 = !{!"Simple C/C++ TBAA"}
diff --git a/llvm/test/Transforms/PGOProfile/misexpect-switch.ll b/llvm/test/Transforms/PGOProfile/misexpect-switch.ll
new file mode 100644
index 0000000000000..3fa4c894ee0b3
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/misexpect-switch.ll
@@ -0,0 +1,285 @@
+; Test misexpect diagnostics handle swich statements, and report source locations correctly
+
+; RUN: llvm-profdata merge %S/Inputs/misexpect-switch.proftext -o %t.profdata
+; RUN: llvm-profdata merge %S/Inputs/misexpect-switch-correct.proftext -o %t.c.profdata
+
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -S 2>&1 | FileCheck %s --check-prefix=WARNING
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=REMARK
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-warn-misexpect -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=BOTH
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -S 2>&1 | FileCheck %s --check-prefix=DISABLED
+
+; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.c.profdata -pgo-warn-misexpect -pass-remarks=misexpect -S 2>&1 | FileCheck %s --check-prefix=CORRECT
+
+; WARNING-DAG: warning: misexpect-switch.c:26:30: 0.00%
+; WARNING-NOT: remark: misexpect-switch.c:26:30: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 8112) of profiled executions.
+
+; REMARK-NOT: warning: misexpect-switch.c:26:30: 0.00%
+; REMARK-DAG: remark: misexpect-switch.c:26:30: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 8112) of profiled executions.
+
+; BOTH-DAG: warning: misexpect-switch.c:26:30: 0.00%
+; BOTH-DAG: remark: misexpect-switch.c:26:30: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 8112) of profiled executions.
+
+; DISABLED-NOT: warning: misexpect-switch.c:26:30: 0.00%
+; DISABLED-NOT: remark: misexpect-switch.c:26:30: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 8112) of profiled executions.
+
+; DISABLED-NOT: warning: misexpect-switch.c:26:30: 0.00%
+; DISABLED-NOT: remark: misexpect-switch.c:26:30: Potential performance regression from use of the llvm.expect intrinsic: Annotation was correct on 0.00% (0 / 8112) of profiled executions.
+
+; CORRECT-NOT: warning: {{.*}}
+; CORRECT-NOT: remark: {{.*}}
+
+
+; ModuleID = 'misexpect-switch.c'
+source_filename = "misexpect-switch.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at inner_loop = dso_local constant i32 1000, align 4, !dbg !0
+ at outer_loop = dso_local constant i32 20, align 4, !dbg !6
+ at arry_size = dso_local constant i32 25, align 4, !dbg !10
+ at arry = dso_local global [25 x i32] zeroinitializer, align 16, !dbg !12
+
+; Function Attrs: nounwind uwtable
+define dso_local void @init_arry() #0 !dbg !21 {
+entry:
+ %i = alloca i32, align 4
+ %0 = bitcast i32* %i to i8*, !dbg !26
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #6, !dbg !26
+ call void @llvm.dbg.declare(metadata i32* %i, metadata !25, metadata !DIExpression()), !dbg !27
+ store i32 0, i32* %i, align 4, !dbg !28, !tbaa !30
+ br label %for.cond, !dbg !34
+
+for.cond: ; preds = %for.inc, %entry
+ %1 = load i32, i32* %i, align 4, !dbg !35, !tbaa !30
+ %cmp = icmp slt i32 %1, 25, !dbg !37
+ br i1 %cmp, label %for.body, label %for.end, !dbg !38
+
+for.body: ; preds = %for.cond
+ %call = call i32 @rand() #6, !dbg !39
+ %rem = srem i32 %call, 10, !dbg !41
+ %2 = load i32, i32* %i, align 4, !dbg !42, !tbaa !30
+ %idxprom = sext i32 %2 to i64, !dbg !43
+ %arrayidx = getelementptr inbounds [25 x i32], [25 x i32]* @arry, i64 0, i64 %idxprom, !dbg !43
+ store i32 %rem, i32* %arrayidx, align 4, !dbg !44, !tbaa !30
+ br label %for.inc, !dbg !45
+
+for.inc: ; preds = %for.body
+ %3 = load i32, i32* %i, align 4, !dbg !46, !tbaa !30
+ %inc = add nsw i32 %3, 1, !dbg !46
+ store i32 %inc, i32* %i, align 4, !dbg !46, !tbaa !30
+ br label %for.cond, !dbg !47, !llvm.loop !48
+
+for.end: ; preds = %for.cond
+ %4 = bitcast i32* %i to i8*, !dbg !50
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %4) #6, !dbg !50
+ ret void, !dbg !50
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
+
+; Function Attrs: nounwind readnone speculatable willreturn
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #2
+
+; Function Attrs: nounwind
+declare dso_local i32 @rand() #3
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
+
+; Function Attrs: nounwind uwtable
+define dso_local i32 @main() #0 !dbg !51 {
+entry:
+ %retval = alloca i32, align 4
+ %val = alloca i32, align 4
+ %j = alloca i32, align 4
+ %condition = alloca i32, align 4
+ store i32 0, i32* %retval, align 4
+ call void @init_arry(), !dbg !62
+ %0 = bitcast i32* %val to i8*, !dbg !63
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #6, !dbg !63
+ call void @llvm.dbg.declare(metadata i32* %val, metadata !55, metadata !DIExpression()), !dbg !64
+ store i32 0, i32* %val, align 4, !dbg !64, !tbaa !30
+ %1 = bitcast i32* %j to i8*, !dbg !65
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #6, !dbg !65
+ call void @llvm.dbg.declare(metadata i32* %j, metadata !56, metadata !DIExpression()), !dbg !66
+ store i32 0, i32* %j, align 4, !dbg !67, !tbaa !30
+ br label %for.cond, !dbg !68
+
+for.cond: ; preds = %for.inc, %entry
+ %2 = load i32, i32* %j, align 4, !dbg !69, !tbaa !30
+ %cmp = icmp slt i32 %2, 20000, !dbg !70
+ br i1 %cmp, label %for.body, label %for.end, !dbg !71
+
+for.body: ; preds = %for.cond
+ %3 = bitcast i32* %condition to i8*, !dbg !72
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* %3) #6, !dbg !72
+ call void @llvm.dbg.declare(metadata i32* %condition, metadata !57, metadata !DIExpression()), !dbg !73
+ %call = call i32 @rand() #6, !dbg !74
+ %rem = srem i32 %call, 5, !dbg !75
+ store i32 %rem, i32* %condition, align 4, !dbg !73, !tbaa !30
+ %4 = load i32, i32* %condition, align 4, !dbg !76, !tbaa !30
+ %conv = zext i32 %4 to i64, !dbg !76
+ %expval = call i64 @llvm.expect.i64(i64 %conv, i64 0), !dbg !77
+ switch i64 %expval, label %sw.default [
+ i64 0, label %sw.bb
+ i64 1, label %sw.bb2
+ i64 2, label %sw.bb2
+ i64 3, label %sw.bb2
+ i64 4, label %sw.bb3
+ ], !dbg !78
+
+sw.bb: ; preds = %for.body
+ %call1 = call i32 @sum(i32* getelementptr inbounds ([25 x i32], [25 x i32]* @arry, i64 0, i64 0), i32 25), !dbg !79
+ %5 = load i32, i32* %val, align 4, !dbg !81, !tbaa !30
+ %add = add nsw i32 %5, %call1, !dbg !81
+ store i32 %add, i32* %val, align 4, !dbg !81, !tbaa !30
+ br label %sw.epilog, !dbg !82
+
+sw.bb2: ; preds = %for.body, %for.body, %for.body
+ br label %sw.epilog, !dbg !83
+
+sw.bb3: ; preds = %for.body
+ %call4 = call i32 @random_sample(i32* getelementptr inbounds ([25 x i32], [25 x i32]* @arry, i64 0, i64 0), i32 25), !dbg !84
+ %6 = load i32, i32* %val, align 4, !dbg !85, !tbaa !30
+ %add5 = add nsw i32 %6, %call4, !dbg !85
+ store i32 %add5, i32* %val, align 4, !dbg !85, !tbaa !30
+ br label %sw.epilog, !dbg !86
+
+sw.default: ; preds = %for.body
+ unreachable, !dbg !87
+
+sw.epilog: ; preds = %sw.bb3, %sw.bb2, %sw.bb
+ %7 = bitcast i32* %condition to i8*, !dbg !88
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %7) #6, !dbg !88
+ br label %for.inc, !dbg !89
+
+for.inc: ; preds = %sw.epilog
+ %8 = load i32, i32* %j, align 4, !dbg !90, !tbaa !30
+ %inc = add nsw i32 %8, 1, !dbg !90
+ store i32 %inc, i32* %j, align 4, !dbg !90, !tbaa !30
+ br label %for.cond, !dbg !91, !llvm.loop !92
+
+for.end: ; preds = %for.cond
+ %9 = bitcast i32* %j to i8*, !dbg !94
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %9) #6, !dbg !94
+ %10 = bitcast i32* %val to i8*, !dbg !94
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* %10) #6, !dbg !94
+ ret i32 0, !dbg !95
+}
+
+; Function Attrs: nounwind readnone willreturn
+declare i64 @llvm.expect.i64(i64, i64) #4
+
+declare dso_local i32 @sum(i32*, i32) #5
+
+declare dso_local i32 @random_sample(i32*, i32) #5
+
+attributes #0 = { nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { argmemonly nounwind willreturn }
+attributes #2 = { nounwind readnone speculatable willreturn }
+attributes #3 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #4 = { nounwind readnone willreturn }
+attributes #5 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #6 = { nounwind }
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!17, !18, !19}
+!llvm.ident = !{!20}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "inner_loop", scope: !2, file: !3, line: 7, type: !8, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang version 10.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, nameTableKind: None)
+!3 = !DIFile(filename: "misexpect-switch.c", directory: ".")
+!4 = !{}
+!5 = !{!0, !6, !10, !12}
+!6 = !DIGlobalVariableExpression(var: !7, expr: !DIExpression())
+!7 = distinct !DIGlobalVariable(name: "outer_loop", scope: !2, file: !3, line: 8, type: !8, isLocal: false, isDefinition: true)
+!8 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !9)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !DIGlobalVariableExpression(var: !11, expr: !DIExpression())
+!11 = distinct !DIGlobalVariable(name: "arry_size", scope: !2, file: !3, line: 9, type: !8, isLocal: false, isDefinition: true)
+!12 = !DIGlobalVariableExpression(var: !13, expr: !DIExpression())
+!13 = distinct !DIGlobalVariable(name: "arry", scope: !2, file: !3, line: 11, type: !14, isLocal: false, isDefinition: true)
+!14 = !DICompositeType(tag: DW_TAG_array_type, baseType: !9, size: 800, elements: !15)
+!15 = !{!16}
+!16 = !DISubrange(count: 25)
+!17 = !{i32 2, !"Dwarf Version", i32 4}
+!18 = !{i32 2, !"Debug Info Version", i32 3}
+!19 = !{i32 1, !"wchar_size", i32 4}
+!20 = !{!"clang version 10.0.0"}
+!21 = distinct !DISubprogram(name: "init_arry", scope: !3, file: !3, line: 13, type: !22, scopeLine: 13, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !24)
+!22 = !DISubroutineType(types: !23)
+!23 = !{null}
+!24 = !{!25}
+!25 = !DILocalVariable(name: "i", scope: !21, file: !3, line: 14, type: !9)
+!26 = !DILocation(line: 14, column: 3, scope: !21)
+!27 = !DILocation(line: 14, column: 7, scope: !21)
+!28 = !DILocation(line: 15, column: 10, scope: !29)
+!29 = distinct !DILexicalBlock(scope: !21, file: !3, line: 15, column: 3)
+!30 = !{!31, !31, i64 0}
+!31 = !{!"int", !32, i64 0}
+!32 = !{!"omnipotent char", !33, i64 0}
+!33 = !{!"Simple C/C++ TBAA"}
+!34 = !DILocation(line: 15, column: 8, scope: !29)
+!35 = !DILocation(line: 15, column: 15, scope: !36)
+!36 = distinct !DILexicalBlock(scope: !29, file: !3, line: 15, column: 3)
+!37 = !DILocation(line: 15, column: 17, scope: !36)
+!38 = !DILocation(line: 15, column: 3, scope: !29)
+!39 = !DILocation(line: 16, column: 15, scope: !40)
+!40 = distinct !DILexicalBlock(scope: !36, file: !3, line: 15, column: 35)
+!41 = !DILocation(line: 16, column: 22, scope: !40)
+!42 = !DILocation(line: 16, column: 10, scope: !40)
+!43 = !DILocation(line: 16, column: 5, scope: !40)
+!44 = !DILocation(line: 16, column: 13, scope: !40)
+!45 = !DILocation(line: 17, column: 3, scope: !40)
+!46 = !DILocation(line: 15, column: 30, scope: !36)
+!47 = !DILocation(line: 15, column: 3, scope: !36)
+!48 = distinct !{!48, !38, !49}
+!49 = !DILocation(line: 17, column: 3, scope: !29)
+!50 = !DILocation(line: 18, column: 1, scope: !21)
+!51 = distinct !DISubprogram(name: "main", scope: !3, file: !3, line: 20, type: !52, scopeLine: 20, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !54)
+!52 = !DISubroutineType(types: !53)
+!53 = !{!9}
+!54 = !{!55, !56, !57}
+!55 = !DILocalVariable(name: "val", scope: !51, file: !3, line: 22, type: !9)
+!56 = !DILocalVariable(name: "j", scope: !51, file: !3, line: 23, type: !9)
+!57 = !DILocalVariable(name: "condition", scope: !58, file: !3, line: 25, type: !61)
+!58 = distinct !DILexicalBlock(scope: !59, file: !3, line: 24, column: 49)
+!59 = distinct !DILexicalBlock(scope: !60, file: !3, line: 24, column: 3)
+!60 = distinct !DILexicalBlock(scope: !51, file: !3, line: 24, column: 3)
+!61 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned)
+!62 = !DILocation(line: 21, column: 3, scope: !51)
+!63 = !DILocation(line: 22, column: 3, scope: !51)
+!64 = !DILocation(line: 22, column: 7, scope: !51)
+!65 = !DILocation(line: 23, column: 3, scope: !51)
+!66 = !DILocation(line: 23, column: 7, scope: !51)
+!67 = !DILocation(line: 24, column: 10, scope: !60)
+!68 = !DILocation(line: 24, column: 8, scope: !60)
+!69 = !DILocation(line: 24, column: 15, scope: !59)
+!70 = !DILocation(line: 24, column: 17, scope: !59)
+!71 = !DILocation(line: 24, column: 3, scope: !60)
+!72 = !DILocation(line: 25, column: 5, scope: !58)
+!73 = !DILocation(line: 25, column: 14, scope: !58)
+!74 = !DILocation(line: 25, column: 26, scope: !58)
+!75 = !DILocation(line: 25, column: 33, scope: !58)
+!76 = !DILocation(line: 26, column: 30, scope: !58)
+!77 = !DILocation(line: 26, column: 13, scope: !58)
+!78 = !DILocation(line: 26, column: 5, scope: !58)
+!79 = !DILocation(line: 28, column: 14, scope: !80)
+!80 = distinct !DILexicalBlock(scope: !58, file: !3, line: 26, column: 45)
+!81 = !DILocation(line: 28, column: 11, scope: !80)
+!82 = !DILocation(line: 29, column: 7, scope: !80)
+!83 = !DILocation(line: 33, column: 7, scope: !80)
+!84 = !DILocation(line: 35, column: 14, scope: !80)
+!85 = !DILocation(line: 35, column: 11, scope: !80)
+!86 = !DILocation(line: 36, column: 7, scope: !80)
+!87 = !DILocation(line: 38, column: 7, scope: !80)
+!88 = !DILocation(line: 40, column: 3, scope: !59)
+!89 = !DILocation(line: 40, column: 3, scope: !58)
+!90 = !DILocation(line: 24, column: 44, scope: !59)
+!91 = !DILocation(line: 24, column: 3, scope: !59)
+!92 = distinct !{!92, !71, !93}
+!93 = !DILocation(line: 40, column: 3, scope: !60)
+!94 = !DILocation(line: 43, column: 1, scope: !51)
+!95 = !DILocation(line: 42, column: 3, scope: !51)
More information about the cfe-commits
mailing list