Skip to content

Commit 3fb7e44

Browse files
committed
Auto merge of rust-lang#120370 - x17jiri:likely_unlikely_fix, r=saethlin
Likely unlikely fix RFC 1131 ( rust-lang#26179 ) added likely/unlikely intrinsics, but they have been broken for a while: rust-lang#96276 , rust-lang#96275 , rust-lang#88767 . This PR tries to fix them. Changes: - added a new `cold_path()` intrinsic - `likely()` and `unlikely()` changed to regular functions implemented using `cold_path()`
2 parents 5ec7d6e + 777003a commit 3fb7e44

File tree

22 files changed

+256
-73
lines changed

22 files changed

+256
-73
lines changed

Diff for: compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -453,11 +453,6 @@ fn codegen_regular_intrinsic_call<'tcx>(
453453
fx.bcx.ins().trap(TrapCode::user(2).unwrap());
454454
return Ok(());
455455
}
456-
sym::likely | sym::unlikely => {
457-
intrinsic_args!(fx, args => (a); intrinsic);
458-
459-
ret.write_cvalue(fx, a);
460-
}
461456
sym::breakpoint => {
462457
intrinsic_args!(fx, args => (); intrinsic);
463458

@@ -1267,6 +1262,10 @@ fn codegen_regular_intrinsic_call<'tcx>(
12671262
);
12681263
}
12691264

1265+
sym::cold_path => {
1266+
// This is a no-op. The intrinsic is just a hint to the optimizer.
1267+
}
1268+
12701269
// Unimplemented intrinsics must have a fallback body. The fallback body is obtained
12711270
// by converting the `InstanceKind::Intrinsic` to an `InstanceKind::Item`.
12721271
_ => {

Diff for: compiler/rustc_codegen_gcc/src/intrinsic/mod.rs

-2
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,6 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc
139139
&args.iter().map(|arg| arg.immediate()).collect::<Vec<_>>(),
140140
)
141141
}
142-
sym::likely => self.expect(args[0].immediate(), true),
143-
sym::unlikely => self.expect(args[0].immediate(), false),
144142
sym::is_val_statically_known => {
145143
let a = args[0].immediate();
146144
let builtin = self.context.get_builtin_function("__builtin_constant_p");

Diff for: compiler/rustc_codegen_llvm/src/intrinsic.rs

-2
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
192192
Some(instance),
193193
)
194194
}
195-
sym::likely => self.expect(args[0].immediate(), true),
196195
sym::is_val_statically_known => {
197196
let intrinsic_type = args[0].layout.immediate_llvm_type(self.cx);
198197
let kind = self.type_kind(intrinsic_type);
@@ -213,7 +212,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
213212
self.const_bool(false)
214213
}
215214
}
216-
sym::unlikely => self.expect(args[0].immediate(), false),
217215
sym::select_unpredictable => {
218216
let cond = args[0].immediate();
219217
assert_eq!(args[1].layout, args[2].layout);

Diff for: compiler/rustc_codegen_ssa/src/mir/block.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -377,20 +377,32 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
377377
// If there are two targets (one conditional, one fallback), emit `br` instead of
378378
// `switch`.
379379
let (test_value, target) = target_iter.next().unwrap();
380-
let lltrue = helper.llbb_with_cleanup(self, target);
381-
let llfalse = helper.llbb_with_cleanup(self, targets.otherwise());
380+
let otherwise = targets.otherwise();
381+
let lltarget = helper.llbb_with_cleanup(self, target);
382+
let llotherwise = helper.llbb_with_cleanup(self, otherwise);
383+
let target_cold = self.cold_blocks[target];
384+
let otherwise_cold = self.cold_blocks[otherwise];
385+
// If `target_cold == otherwise_cold`, the branches have the same weight
386+
// so there is no expectation. If they differ, the `target` branch is expected
387+
// when the `otherwise` branch is cold.
388+
let expect = if target_cold == otherwise_cold { None } else { Some(otherwise_cold) };
382389
if switch_ty == bx.tcx().types.bool {
383390
// Don't generate trivial icmps when switching on bool.
384391
match test_value {
385-
0 => bx.cond_br(discr_value, llfalse, lltrue),
386-
1 => bx.cond_br(discr_value, lltrue, llfalse),
392+
0 => {
393+
let expect = expect.map(|e| !e);
394+
bx.cond_br_with_expect(discr_value, llotherwise, lltarget, expect);
395+
}
396+
1 => {
397+
bx.cond_br_with_expect(discr_value, lltarget, llotherwise, expect);
398+
}
387399
_ => bug!(),
388400
}
389401
} else {
390402
let switch_llty = bx.immediate_backend_type(bx.layout_of(switch_ty));
391403
let llval = bx.const_uint_big(switch_llty, test_value);
392404
let cmp = bx.icmp(IntPredicate::IntEQ, discr_value, llval);
393-
bx.cond_br(cmp, lltrue, llfalse);
405+
bx.cond_br_with_expect(cmp, lltarget, llotherwise, expect);
394406
}
395407
} else if self.cx.sess().opts.optimize == OptLevel::No
396408
&& target_iter.len() == 2

Diff for: compiler/rustc_codegen_ssa/src/mir/intrinsic.rs

+5
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
498498
}
499499
}
500500

501+
sym::cold_path => {
502+
// This is a no-op. The intrinsic is just a hint to the optimizer.
503+
return Ok(());
504+
}
505+
501506
_ => {
502507
// Need to use backend-specific things in the implementation.
503508
return bx.codegen_intrinsic_call(instance, fn_abi, args, llresult, span);

Diff for: compiler/rustc_codegen_ssa/src/mir/mod.rs

+41
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
9191
/// Cached terminate upon unwinding block and its reason
9292
terminate_block: Option<(Bx::BasicBlock, UnwindTerminateReason)>,
9393

94+
/// A bool flag for each basic block indicating whether it is a cold block.
95+
/// A cold block is a block that is unlikely to be executed at runtime.
96+
cold_blocks: IndexVec<mir::BasicBlock, bool>,
97+
9498
/// The location where each MIR arg/var/tmp/ret is stored. This is
9599
/// usually an `PlaceRef` representing an alloca, but not always:
96100
/// sometimes we can skip the alloca and just store the value
@@ -207,6 +211,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
207211
cleanup_kinds,
208212
landing_pads: IndexVec::from_elem(None, &mir.basic_blocks),
209213
funclets: IndexVec::from_fn_n(|_| None, mir.basic_blocks.len()),
214+
cold_blocks: find_cold_blocks(cx.tcx(), mir),
210215
locals: locals::Locals::empty(),
211216
debug_context,
212217
per_local_var_debug_info: None,
@@ -477,3 +482,39 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
477482

478483
args
479484
}
485+
486+
fn find_cold_blocks<'tcx>(
487+
tcx: TyCtxt<'tcx>,
488+
mir: &mir::Body<'tcx>,
489+
) -> IndexVec<mir::BasicBlock, bool> {
490+
let local_decls = &mir.local_decls;
491+
492+
let mut cold_blocks: IndexVec<mir::BasicBlock, bool> =
493+
IndexVec::from_elem(false, &mir.basic_blocks);
494+
495+
// Traverse all basic blocks from end of the function to the start.
496+
for (bb, bb_data) in traversal::postorder(mir) {
497+
let terminator = bb_data.terminator();
498+
499+
// If a BB ends with a call to a cold function, mark it as cold.
500+
if let mir::TerminatorKind::Call { ref func, .. } = terminator.kind
501+
&& let ty::FnDef(def_id, ..) = *func.ty(local_decls, tcx).kind()
502+
&& let attrs = tcx.codegen_fn_attrs(def_id)
503+
&& attrs.flags.contains(CodegenFnAttrFlags::COLD)
504+
{
505+
cold_blocks[bb] = true;
506+
continue;
507+
}
508+
509+
// If all successors of a BB are cold and there's at least one of them, mark this BB as cold
510+
let mut succ = terminator.successors();
511+
if let Some(first) = succ.next()
512+
&& cold_blocks[first]
513+
&& succ.all(|s| cold_blocks[s])
514+
{
515+
cold_blocks[bb] = true;
516+
}
517+
}
518+
519+
cold_blocks
520+
}

Diff for: compiler/rustc_codegen_ssa/src/traits/builder.rs

+20
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ pub trait BuilderMethods<'a, 'tcx>:
8484
then_llbb: Self::BasicBlock,
8585
else_llbb: Self::BasicBlock,
8686
);
87+
88+
// Conditional with expectation.
89+
//
90+
// This function is opt-in for back ends.
91+
//
92+
// The default implementation calls `self.expect()` before emiting the branch
93+
// by calling `self.cond_br()`
94+
fn cond_br_with_expect(
95+
&mut self,
96+
mut cond: Self::Value,
97+
then_llbb: Self::BasicBlock,
98+
else_llbb: Self::BasicBlock,
99+
expect: Option<bool>,
100+
) {
101+
if let Some(expect) = expect {
102+
cond = self.expect(cond, expect);
103+
}
104+
self.cond_br(cond, then_llbb, else_llbb)
105+
}
106+
87107
fn switch(
88108
&mut self,
89109
v: Self::Value,

Diff for: compiler/rustc_const_eval/src/interpret/intrinsics.rs

+3
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
417417
// These just return their argument
418418
self.copy_op(&args[0], dest)?;
419419
}
420+
sym::cold_path => {
421+
// This is a no-op. The intrinsic is just a hint to the optimizer.
422+
}
420423
sym::raw_eq => {
421424
let result = self.raw_eq_intrinsic(&args[0], &args[1])?;
422425
self.write_scalar(result, dest)?;

Diff for: compiler/rustc_hir_analysis/src/check/intrinsic.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,8 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
109109
| sym::three_way_compare
110110
| sym::discriminant_value
111111
| sym::type_id
112-
| sym::likely
113-
| sym::unlikely
114112
| sym::select_unpredictable
113+
| sym::cold_path
115114
| sym::ptr_guaranteed_cmp
116115
| sym::minnumf16
117116
| sym::minnumf32
@@ -489,9 +488,8 @@ pub fn check_intrinsic_type(
489488
sym::float_to_int_unchecked => (2, 0, vec![param(0)], param(1)),
490489

491490
sym::assume => (0, 0, vec![tcx.types.bool], tcx.types.unit),
492-
sym::likely => (0, 0, vec![tcx.types.bool], tcx.types.bool),
493-
sym::unlikely => (0, 0, vec![tcx.types.bool], tcx.types.bool),
494491
sym::select_unpredictable => (1, 0, vec![tcx.types.bool, param(0), param(0)], param(0)),
492+
sym::cold_path => (0, 0, vec![], tcx.types.unit),
495493

496494
sym::read_via_copy => (1, 0, vec![Ty::new_imm_ptr(tcx, param(0))], param(0)),
497495
sym::write_via_move => {

Diff for: compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ symbols! {
589589
cmse_nonsecure_entry,
590590
coerce_unsized,
591591
cold,
592+
cold_path,
592593
collapse_debuginfo,
593594
column,
594595
compare_bytes,

Diff for: library/core/src/intrinsics/mod.rs

+40-8
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,22 @@ pub const unsafe fn assume(b: bool) {
14651465
}
14661466
}
14671467

1468+
/// Hints to the compiler that current code path is cold.
1469+
///
1470+
/// Note that, unlike most intrinsics, this is safe to call;
1471+
/// it does not require an `unsafe` block.
1472+
/// Therefore, implementations must not require the user to uphold
1473+
/// any safety invariants.
1474+
///
1475+
/// This intrinsic does not have a stable counterpart.
1476+
#[unstable(feature = "core_intrinsics", issue = "none")]
1477+
#[cfg_attr(not(bootstrap), rustc_intrinsic)]
1478+
#[cfg(not(bootstrap))]
1479+
#[rustc_nounwind]
1480+
#[miri::intrinsic_fallback_is_spec]
1481+
#[cold]
1482+
pub const fn cold_path() {}
1483+
14681484
/// Hints to the compiler that branch condition is likely to be true.
14691485
/// Returns the value passed to it.
14701486
///
@@ -1480,13 +1496,21 @@ pub const unsafe fn assume(b: bool) {
14801496
bootstrap,
14811497
rustc_const_stable(feature = "const_likely", since = "CURRENT_RUSTC_VERSION")
14821498
)]
1483-
#[cfg_attr(not(bootstrap), rustc_const_stable_intrinsic)]
14841499
#[unstable(feature = "core_intrinsics", issue = "none")]
1485-
#[rustc_intrinsic]
14861500
#[rustc_nounwind]
1487-
#[miri::intrinsic_fallback_is_spec]
1501+
#[inline(always)]
14881502
pub const fn likely(b: bool) -> bool {
1489-
b
1503+
#[cfg(bootstrap)]
1504+
{
1505+
b
1506+
}
1507+
#[cfg(not(bootstrap))]
1508+
if b {
1509+
true
1510+
} else {
1511+
cold_path();
1512+
false
1513+
}
14901514
}
14911515

14921516
/// Hints to the compiler that branch condition is likely to be false.
@@ -1504,13 +1528,21 @@ pub const fn likely(b: bool) -> bool {
15041528
bootstrap,
15051529
rustc_const_stable(feature = "const_likely", since = "CURRENT_RUSTC_VERSION")
15061530
)]
1507-
#[cfg_attr(not(bootstrap), rustc_const_stable_intrinsic)]
15081531
#[unstable(feature = "core_intrinsics", issue = "none")]
1509-
#[rustc_intrinsic]
15101532
#[rustc_nounwind]
1511-
#[miri::intrinsic_fallback_is_spec]
1533+
#[inline(always)]
15121534
pub const fn unlikely(b: bool) -> bool {
1513-
b
1535+
#[cfg(bootstrap)]
1536+
{
1537+
b
1538+
}
1539+
#[cfg(not(bootstrap))]
1540+
if b {
1541+
cold_path();
1542+
true
1543+
} else {
1544+
false
1545+
}
15141546
}
15151547

15161548
/// Returns either `true_val` or `false_val` depending on condition `b` with a
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
The loop took around 1250ms
1+
The loop took around 1350ms
22
(It's fine for this number to change when you `--bless` this test.)

Diff for: tests/codegen/checked_math.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pub fn checked_shr_signed(a: i32, b: u32) -> Option<i32> {
9090
#[no_mangle]
9191
pub fn checked_add_one_unwrap_unsigned(x: u32) -> u32 {
9292
// CHECK: %[[IS_MAX:.+]] = icmp eq i32 %x, -1
93-
// CHECK: br i1 %[[IS_MAX]], label %[[NONE_BB:.+]], label %[[SOME_BB:.+]]
93+
// CHECK: br i1 %[[IS_MAX]], label %[[NONE_BB:.+]], label %[[SOME_BB:.+]],
9494
// CHECK: [[SOME_BB]]:
9595
// CHECK: %[[R:.+]] = add nuw i32 %x, 1
9696
// CHECK: ret i32 %[[R]]

Diff for: tests/codegen/intrinsics/cold_path.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ compile-flags: -O
2+
#![crate_type = "lib"]
3+
#![feature(core_intrinsics)]
4+
5+
use std::intrinsics::cold_path;
6+
7+
#[no_mangle]
8+
pub fn test_cold_path(x: bool) {
9+
cold_path();
10+
}
11+
12+
// CHECK-LABEL: @test_cold_path(
13+
// CHECK-NOT: cold_path

Diff for: tests/codegen/intrinsics/likely.rs

+25-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
1-
//@ compile-flags: -C no-prepopulate-passes -Copt-level=1
2-
1+
//@ compile-flags: -O
32
#![crate_type = "lib"]
43
#![feature(core_intrinsics)]
54

6-
use std::intrinsics::{likely, unlikely};
5+
use std::intrinsics::likely;
76

7+
#[inline(never)]
88
#[no_mangle]
9-
pub fn check_likely(x: i32, y: i32) -> Option<i32> {
10-
unsafe {
11-
// CHECK: call i1 @llvm.expect.i1(i1 %{{.*}}, i1 true)
12-
if likely(x == y) { None } else { Some(x + y) }
13-
}
9+
pub fn path_a() {
10+
println!("path a");
11+
}
12+
13+
#[inline(never)]
14+
#[no_mangle]
15+
pub fn path_b() {
16+
println!("path b");
1417
}
1518

1619
#[no_mangle]
17-
pub fn check_unlikely(x: i32, y: i32) -> Option<i32> {
18-
unsafe {
19-
// CHECK: call i1 @llvm.expect.i1(i1 %{{.*}}, i1 false)
20-
if unlikely(x == y) { None } else { Some(x + y) }
20+
pub fn test_likely(x: bool) {
21+
if likely(x) {
22+
path_a();
23+
} else {
24+
path_b();
2125
}
2226
}
27+
28+
// CHECK-LABEL: @test_likely(
29+
// CHECK: br i1 %x, label %bb2, label %bb3, !prof ![[NUM:[0-9]+]]
30+
// CHECK: bb3:
31+
// CHECK-NOT: cold_path
32+
// CHECK: path_b
33+
// CHECK: bb2:
34+
// CHECK: path_a
35+
// CHECK: ![[NUM]] = !{!"branch_weights", {{(!"expected", )?}}i32 2000, i32 1}

Diff for: tests/codegen/intrinsics/likely_assert.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ compile-flags: -O
2+
#![crate_type = "lib"]
3+
4+
#[no_mangle]
5+
pub fn test_assert(x: bool) {
6+
assert!(x);
7+
}
8+
9+
// check that assert! emits branch weights
10+
11+
// CHECK-LABEL: @test_assert(
12+
// CHECK: br i1 %x, label %bb2, label %bb1, !prof ![[NUM:[0-9]+]]
13+
// CHECK: bb1:
14+
// CHECK: panic
15+
// CHECK: bb2:
16+
// CHECK: ret void
17+
// CHECK: ![[NUM]] = !{!"branch_weights", {{(!"expected", )?}}i32 2000, i32 1}

0 commit comments

Comments
 (0)