Skip to content

Commit 1d3847e

Browse files
committed
[llvm][misexpect] Enable diagnostics for profitable llvm.expect annotations
Issue llvm#56502 describes an enhancement related to the use of llvm.expect. The request is for a diagnostic mode that can identify branches that would benefit from the use of llvm.expect based on the branch_weights assigned from a PGO or sample profile. To support identify branches(or switches) that would benefit from the use of an llvm.expect intrinsic, we follow a similar checking pattern to that used in MisExpect, but only in cases where MisExpect diagnostics would not be used (i.e., when an llvm.expect intrinsic has already been used).
1 parent 9251b55 commit 1d3847e

File tree

8 files changed

+287
-34
lines changed

8 files changed

+287
-34
lines changed

llvm/include/llvm/IR/LLVMContext.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ class LLVMContext {
208208
void setMisExpectWarningRequested(bool Requested);
209209
void setDiagnosticsMisExpectTolerance(std::optional<uint32_t> Tolerance);
210210
uint32_t getDiagnosticsMisExpectTolerance() const;
211+
bool getAnnotationDiagsRequested() const;
212+
void setAnnotationDiagsRequested(bool Requested);
211213

212214
/// Return the minimum hotness value a diagnostic would need in order
213215
/// to be included in optimization diagnostics.

llvm/include/llvm/Target/TargetOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,10 @@ namespace llvm {
369369
/// By default, it is set to false
370370
unsigned MisExpect : 1;
371371

372+
/// When set to true, enable MissingAnnotations diagnostics
373+
/// By default, it is set to false
374+
unsigned MissingAnnotations : 1;
375+
372376
/// When set to true, const objects with relocatable address values are put
373377
/// into the RO data section.
374378
unsigned XCOFFReadOnlyPointers : 1;

llvm/include/llvm/Transforms/Utils/MisExpect.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ void checkExpectAnnotations(Instruction &I,
7676
const ArrayRef<uint32_t> ExistingWeights,
7777
bool IsFrontend);
7878

79+
void checkMissingAnnotations(Instruction &I,
80+
const ArrayRef<uint32_t> ExistingWeights,
81+
bool IsFrontendInstr);
7982
} // namespace misexpect
8083
} // namespace llvm
8184

llvm/lib/IR/LLVMContext.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,13 @@ uint32_t LLVMContext::getDiagnosticsMisExpectTolerance() const {
165165
return pImpl->DiagnosticsMisExpectTolerance.value_or(0);
166166
}
167167

168+
void LLVMContext::setAnnotationDiagsRequested(bool Requested) {
169+
pImpl->AnnotationsDiagsRequested = Requested;
170+
}
171+
bool LLVMContext::getAnnotationDiagsRequested() const {
172+
return pImpl->AnnotationsDiagsRequested;
173+
}
174+
168175
bool LLVMContext::isDiagnosticsHotnessThresholdSetFromPSI() const {
169176
return !pImpl->DiagnosticsHotnessThreshold.has_value();
170177
}

llvm/lib/IR/LLVMContextImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,10 @@ class LLVMContextImpl {
14831483
std::optional<uint32_t> DiagnosticsMisExpectTolerance = 0;
14841484
bool MisExpectWarningRequested = false;
14851485

1486+
/// Enables Diagnostics for Missing llvm.expect annotations on extremely hot
1487+
/// branches
1488+
bool AnnotationsDiagsRequested = false;
1489+
14861490
/// The specialized remark streamer used by LLVM's OptimizationRemarkEmitter.
14871491
std::unique_ptr<LLVMRemarkStreamer> LLVMRS;
14881492

llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,7 @@ void llvm::setProfMetadata(Module *M, Instruction *TI,
21912191
} dbgs() << "\n";);
21922192

21932193
misexpect::checkExpectAnnotations(*TI, Weights, /*IsFrontend=*/false);
2194+
misexpect::checkMissingAnnotations(*TI, Weights, /*IsFrontend=*/false);
21942195

21952196
setBranchWeights(*TI, Weights, /*IsExpected=*/false);
21962197
if (EmitBranchProbability) {

llvm/lib/Transforms/Utils/MisExpect.cpp

Lines changed: 156 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,60 @@ static cl::opt<uint32_t> MisExpectTolerance(
6565
cl::desc("Prevents emitting diagnostics when profile counts are "
6666
"within N% of the threshold.."));
6767

68+
// Command line option to enable/disable the Remparks when profile data suggests
69+
// that the llvm.expect intrinsic may be profitable
70+
static cl::opt<bool>
71+
PGOMissingAnnotations("pgo-missing-annotations", cl::init(false),
72+
cl::Hidden,
73+
cl::desc("Use this option to turn on/off suggestions "
74+
"of missing llvm.expect intrinsics."));
6875
} // namespace llvm
6976

7077
namespace {
78+
struct ProfDataSummary {
79+
uint64_t Likely;
80+
uint64_t Unlikely;
81+
uint64_t RealTotal;
82+
uint64_t NumUnlikely;
83+
};
84+
85+
enum class DiagKind {
86+
MisExpect, // Reports when llvm.expect usage is contradicted by PGO data
87+
MissingExpect, // Reports when llvm.expect would be profitable
88+
Unsupported, // An error
89+
};
90+
91+
std::optional<uint64_t> getScaledThreshold(const ProfDataSummary &PDS) {
92+
93+
uint64_t TotalBranchWeight = PDS.Likely + (PDS.Unlikely * PDS.NumUnlikely);
94+
95+
LLVM_DEBUG(dbgs() << "Total Branch Weight = " << TotalBranchWeight << "\n"
96+
<< "Likely Branch Weight = " << PDS.Likely << "\n");
97+
98+
// FIXME: When we've addressed sample profiling, restore the assertion
99+
//
100+
// We cannot calculate branch probability if either of these invariants aren't
101+
// met. However, MisExpect diagnostics should not prevent code from compiling,
102+
// so we simply forgo emitting diagnostics here, and return early.
103+
// assert((TotalBranchWeight >= LikelyBranchWeight) && (TotalBranchWeight > 0)
104+
// && "TotalBranchWeight is less than the Likely branch weight");
105+
if ((TotalBranchWeight == 0) || (TotalBranchWeight <= PDS.Likely))
106+
return std::nullopt;
107+
108+
// To determine our threshold value we need to obtain the branch probability
109+
// for the weights added by llvm.expect and use that proportion to calculate
110+
// our threshold based on the collected profile data.
111+
BranchProbability LikelyProbablilty =
112+
BranchProbability::getBranchProbability(PDS.Likely, TotalBranchWeight);
113+
114+
return LikelyProbablilty.scale(PDS.RealTotal);
115+
}
116+
117+
bool isAnnotationDiagEnabled(LLVMContext &Ctx) {
118+
LLVM_DEBUG(dbgs() << "PGOMissingAnnotations = " << PGOMissingAnnotations
119+
<< "\n");
120+
return PGOMissingAnnotations || Ctx.getAnnotationDiagsRequested();
121+
}
71122

72123
bool isMisExpectDiagEnabled(LLVMContext &Ctx) {
73124
return PGOWarnMisExpect || Ctx.getMisExpectWarningRequested();
@@ -112,10 +163,69 @@ void emitMisexpectDiagnostic(Instruction *I, LLVMContext &Ctx,
112163
Instruction *Cond = getInstCondition(I);
113164
if (isMisExpectDiagEnabled(Ctx))
114165
Ctx.diagnose(DiagnosticInfoMisExpect(Cond, Msg));
115-
OptimizationRemarkEmitter ORE(I->getParent()->getParent());
166+
OptimizationRemarkEmitter ORE(I->getFunction());
116167
ORE.emit(OptimizationRemark(DEBUG_TYPE, "misexpect", Cond) << RemStr.str());
117168
}
118169

170+
171+
void emitMissingAnnotationDiag(Instruction *I) {
172+
const auto *RemStr =
173+
"Extremely hot condition. Consider adding llvm.expect intrinsic";
174+
Instruction *Cond = getInstCondition(I);
175+
OptimizationRemarkEmitter ORE(I->getParent()->getParent());
176+
ORE.emit(
177+
OptimizationRemark("missing-annotations", "missing-annotations", Cond)
178+
<< RemStr);
179+
}
180+
181+
uint64_t totalWeight(const ArrayRef<uint32_t> Weights) {
182+
return std::accumulate(Weights.begin(), Weights.end(), (uint64_t)0,
183+
std::plus<uint64_t>());
184+
}
185+
186+
void scaleByTollerance(const Instruction &I, uint64_t &ScaledThreshold) {
187+
// clamp tolerance range to [0, 100)
188+
uint32_t Tolerance = getMisExpectTolerance(I.getContext());
189+
Tolerance = std::clamp(Tolerance, 0u, 99u);
190+
191+
// Allow users to relax checking by N% i.e., if they use a 5% tolerance,
192+
// then we check against 0.95*ScaledThreshold
193+
if (Tolerance > 0)
194+
ScaledThreshold *= (1.0 - Tolerance / 100.0);
195+
196+
LLVM_DEBUG(dbgs() << "Scaled Threshold = " << ScaledThreshold << "\n");
197+
}
198+
199+
void reportDiagnostics(Instruction &I, const ProfDataSummary &PDS,
200+
uint32_t ProfiledWeight, DiagKind Kind) {
201+
std::optional<uint64_t> ScaledOpt = getScaledThreshold(PDS);
202+
if (!ScaledOpt)
203+
return;
204+
uint64_t ScaledThreshold = ScaledOpt.value();
205+
scaleByTollerance(I, ScaledThreshold);
206+
207+
LLVM_DEBUG(dbgs() << "Total Branch Weight = " << PDS.RealTotal << "\n"
208+
<< "Scaled Threshold = " << ScaledThreshold << "\n"
209+
<< "Profiled Weight = " << ProfiledWeight << "\n"
210+
<< "Likely Branch Weight = " << PDS.Likely << "\n");
211+
// When the profile weight is outside the range, we emit the diagnostic
212+
switch (Kind) {
213+
case DiagKind::MisExpect:
214+
if (ProfiledWeight < ScaledThreshold) {
215+
emitMisexpectDiagnostic(&I, I.getContext(), ProfiledWeight,
216+
PDS.RealTotal);
217+
}
218+
return;
219+
case DiagKind::MissingExpect:
220+
if (ProfiledWeight > ScaledThreshold) {
221+
emitMissingAnnotationDiag(&I);
222+
}
223+
return;
224+
default:
225+
llvm_unreachable("Unsupported diagnostic type used in PGO based analysis");
226+
};
227+
}
228+
119229
} // namespace
120230

121231
namespace llvm {
@@ -143,39 +253,10 @@ void verifyMisExpect(Instruction &I, ArrayRef<uint32_t> RealWeights,
143253
}
144254

145255
const uint64_t ProfiledWeight = RealWeights[MaxIndex];
146-
const uint64_t RealWeightsTotal =
147-
std::accumulate(RealWeights.begin(), RealWeights.end(), (uint64_t)0,
148-
std::plus<uint64_t>());
149-
const uint64_t NumUnlikelyTargets = RealWeights.size() - 1;
150-
151-
uint64_t TotalBranchWeight =
152-
LikelyBranchWeight + (UnlikelyBranchWeight * NumUnlikelyTargets);
153-
154-
// Failing this assert means that we have corrupted metadata.
155-
assert((TotalBranchWeight >= LikelyBranchWeight) && (TotalBranchWeight > 0) &&
156-
"TotalBranchWeight is less than the Likely branch weight");
157-
158-
// To determine our threshold value we need to obtain the branch probability
159-
// for the weights added by llvm.expect and use that proportion to calculate
160-
// our threshold based on the collected profile data.
161-
auto LikelyProbablilty = BranchProbability::getBranchProbability(
162-
LikelyBranchWeight, TotalBranchWeight);
163-
164-
uint64_t ScaledThreshold = LikelyProbablilty.scale(RealWeightsTotal);
165-
166-
// clamp tolerance range to [0, 100)
167-
auto Tolerance = getMisExpectTolerance(I.getContext());
168-
Tolerance = std::clamp(Tolerance, 0u, 99u);
169-
170-
// Allow users to relax checking by N% i.e., if they use a 5% tolerance,
171-
// then we check against 0.95*ScaledThreshold
172-
if (Tolerance > 0)
173-
ScaledThreshold *= (1.0 - Tolerance / 100.0);
174-
175-
// When the profile weight is below the threshold, we emit the diagnostic
176-
if (ProfiledWeight < ScaledThreshold)
177-
emitMisexpectDiagnostic(&I, I.getContext(), ProfiledWeight,
178-
RealWeightsTotal);
256+
const ProfDataSummary PDS = {LikelyBranchWeight, UnlikelyBranchWeight,
257+
totalWeight(RealWeights),
258+
RealWeights.size() - 1};
259+
reportDiagnostics(I,PDS, ProfiledWeight, DiagKind::MisExpect);
179260
}
180261

181262
void checkBackendInstrumentation(Instruction &I,
@@ -211,6 +292,47 @@ void checkExpectAnnotations(Instruction &I,
211292
}
212293
}
213294

295+
void verifyMissingAnnotations(Instruction &I, ArrayRef<uint32_t> RealWeights) {
296+
// To determine if we emit a diagnostic, we need to compare the branch weights
297+
// from the profile to those that would be added by the llvm.expect intrinsic.
298+
// And compare it to the real profile to see if it would be profitable.
299+
uint32_t ProfiledWeight =
300+
*std::max_element(RealWeights.begin(), RealWeights.end());
301+
302+
const uint64_t LikelyBranchWeight = 2000;
303+
const uint64_t UnlikelyBranchWeight = 1;
304+
const ProfDataSummary PDS = {LikelyBranchWeight, UnlikelyBranchWeight,
305+
totalWeight(RealWeights),
306+
RealWeights.size() - 1};
307+
reportDiagnostics(I, PDS, ProfiledWeight, DiagKind::MissingExpect);
308+
}
309+
310+
void checkMissingAnnotations(Instruction &I,
311+
const ArrayRef<uint32_t> ExistingWeights,
312+
bool IsFrontendInstr) {
313+
314+
// TODO: Ironically, this is probably a branch that should be marked UNLIKELY
315+
// exit early if these diagnostics weren't requested
316+
if (!isAnnotationDiagEnabled(I.getContext()))
317+
return;
318+
319+
if (IsFrontendInstr) {
320+
// TODO: Fronend checking will have to be thought through, since we need
321+
// to do the check on branches that don't have expect intrinsics
322+
323+
// auto RealWeightsOpt = extractWeights(&I, I.getContext());
324+
// if (!RealWeightsOpt)
325+
// return;
326+
// auto RealWeights = RealWeightsOpt.getValue();
327+
// verifyMissingAnnotations(I, RealWeights, ExistingWeights);
328+
} else {
329+
SmallVector<uint32_t> ExpectedWeights;
330+
if (extractBranchWeights(I, ExpectedWeights))
331+
return;
332+
verifyMissingAnnotations(I, ExistingWeights);
333+
}
334+
}
335+
214336
} // namespace misexpect
215337
} // namespace llvm
216338
#undef DEBUG_TYPE
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
; Test misexpect checks do not issue diagnostics when profiling weights and
2+
; branch weights added by llvm.expect agree
3+
4+
; RUN: llvm-profdata merge %S/Inputs/misexpect-branch-correct.proftext -o %t.profdata
5+
6+
; RUN: opt < %s -passes="function(lower-expect),pgo-instr-use" -pgo-test-profile-file=%t.profdata -pgo-missing-annotations -pass-remarks=missing-annotation -S 2>&1 | FileCheck %s --check-prefix=MISSING_ANNOTATION
7+
8+
; MISSING_ANNOTATION: remark: misexpect-branch.c:22:0: Extremely hot condition. Consider adding llvm.expect intrinsic
9+
10+
; CHECK-NOT: warning: {{.*}}
11+
; CHECK-NOT: remark: {{.*}}
12+
; CHECK: !{!"branch_weights", i32 0, i32 200000}
13+
14+
15+
; ModuleID = 'misexpect-branch.c'
16+
source_filename = "misexpect-branch.c"
17+
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
18+
target triple = "x86_64-unknown-linux-gnu"
19+
20+
@inner_loop = constant i32 100, align 4
21+
@outer_loop = constant i32 2000, align 4
22+
23+
; Function Attrs: nounwind
24+
define i32 @bar() #0 !dbg !6 {
25+
entry:
26+
%rando = alloca i32, align 4
27+
%x = alloca i32, align 4
28+
%0 = bitcast i32* %rando to i8*, !dbg !9
29+
call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) #4, !dbg !9
30+
%call = call i32 (...) @buzz(), !dbg !9
31+
store i32 %call, i32* %rando, align 4, !dbg !9, !tbaa !10
32+
%1 = bitcast i32* %x to i8*, !dbg !14
33+
call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) #4, !dbg !14
34+
store i32 0, i32* %x, align 4, !dbg !14, !tbaa !10
35+
%2 = load i32, i32* %rando, align 4, !dbg !15, !tbaa !10
36+
%rem = srem i32 %2, 200000, !dbg !15
37+
%cmp = icmp eq i32 %rem, 0, !dbg !15
38+
%lnot = xor i1 %cmp, true, !dbg !15
39+
%lnot1 = xor i1 %lnot, true, !dbg !15
40+
%lnot.ext = zext i1 %lnot1 to i32, !dbg !15
41+
%conv = sext i32 %lnot.ext to i64, !dbg !15
42+
%tobool = icmp ne i64 %conv, 0, !dbg !15
43+
br i1 %tobool, label %if.then, label %if.else, !dbg !15
44+
45+
if.then: ; preds = %entry
46+
%3 = load i32, i32* %rando, align 4, !dbg !16, !tbaa !10
47+
%call2 = call i32 @baz(i32 %3), !dbg !16
48+
store i32 %call2, i32* %x, align 4, !dbg !16, !tbaa !10
49+
br label %if.end, !dbg !17
50+
51+
if.else: ; preds = %entry
52+
%call3 = call i32 @foo(i32 50), !dbg !18
53+
store i32 %call3, i32* %x, align 4, !dbg !18, !tbaa !10
54+
br label %if.end
55+
56+
if.end: ; preds = %if.else, %if.then
57+
%4 = load i32, i32* %x, align 4, !dbg !19, !tbaa !10
58+
%5 = bitcast i32* %x to i8*, !dbg !20
59+
call void @llvm.lifetime.end.p0i8(i64 4, i8* %5) #4, !dbg !20
60+
%6 = bitcast i32* %rando to i8*, !dbg !20
61+
call void @llvm.lifetime.end.p0i8(i64 4, i8* %6) #4, !dbg !20
62+
ret i32 %4, !dbg !19
63+
}
64+
65+
; Function Attrs: argmemonly nounwind willreturn
66+
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
67+
68+
declare i32 @buzz(...) #2
69+
70+
; Function Attrs: nounwind readnone willreturn
71+
declare i64 @llvm.expect.i64(i64, i64) #3
72+
73+
declare i32 @baz(i32) #2
74+
75+
declare i32 @foo(i32) #2
76+
77+
; Function Attrs: argmemonly nounwind willreturn
78+
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
79+
80+
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" }
81+
attributes #1 = { argmemonly nounwind willreturn }
82+
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" }
83+
attributes #3 = { nounwind readnone willreturn }
84+
attributes #4 = { nounwind }
85+
86+
!llvm.dbg.cu = !{!0}
87+
!llvm.module.flags = !{!3, !4}
88+
!llvm.ident = !{!5}
89+
90+
!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)
91+
!1 = !DIFile(filename: "<stdin>", directory: ".")
92+
!2 = !{}
93+
!3 = !{i32 2, !"Debug Info Version", i32 3}
94+
!4 = !{i32 1, !"wchar_size", i32 4}
95+
!5 = !{!"clang version 10.0.0 (trunk c20270bfffc9d6965219de339d66c61e9fe7d82d)"}
96+
!6 = distinct !DISubprogram(name: "bar", scope: !7, file: !7, line: 19, type: !8, scopeLine: 19, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
97+
!7 = !DIFile(filename: "misexpect-branch.c", directory: ".")
98+
!8 = !DISubroutineType(types: !2)
99+
!9 = !DILocation(line: 20, scope: !6)
100+
!10 = !{!11, !11, i64 0}
101+
!11 = !{!"int", !12, i64 0}
102+
!12 = !{!"omnipotent char", !13, i64 0}
103+
!13 = !{!"Simple C/C++ TBAA"}
104+
!14 = !DILocation(line: 21, scope: !6)
105+
!15 = !DILocation(line: 22, scope: !6)
106+
!16 = !DILocation(line: 23, scope: !6)
107+
!17 = !DILocation(line: 24, scope: !6)
108+
!18 = !DILocation(line: 25, scope: !6)
109+
!19 = !DILocation(line: 27, scope: !6)
110+
!20 = !DILocation(line: 28, scope: !6)

0 commit comments

Comments
 (0)