Skip to content

Commit 5a45617

Browse files
Centri3RalfJung
authored andcommitted
Add new intrinsic is_constant and optimize pow
Fix overflow check Make MIRI choose the path randomly and rename the intrinsic Add back test Add miri test and make it operate on `ptr` Define `llvm.is.constant` for primitives Update MIRI comment and fix test in stage2 Add const eval test Clarify that both branches must have the same side effects guaranteed non guarantee use immediate type instead Co-Authored-By: Ralf Jung <[email protected]>
1 parent 9480767 commit 5a45617

File tree

14 files changed

+324
-34
lines changed

14 files changed

+324
-34
lines changed

compiler/rustc_codegen_llvm/src/context.rs

+14
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,20 @@ impl<'ll> CodegenCx<'ll, '_> {
908908
ifn!("llvm.lifetime.start.p0i8", fn(t_i64, ptr) -> void);
909909
ifn!("llvm.lifetime.end.p0i8", fn(t_i64, ptr) -> void);
910910

911+
// FIXME: This is an infinitesimally small portion of the types you can
912+
// pass to this intrinsic, if we can ever lazily register intrinsics we
913+
// should register these when they're used, that way any type can be
914+
// passed.
915+
ifn!("llvm.is.constant.i1", fn(i1) -> i1);
916+
ifn!("llvm.is.constant.i8", fn(t_i8) -> i1);
917+
ifn!("llvm.is.constant.i16", fn(t_i16) -> i1);
918+
ifn!("llvm.is.constant.i32", fn(t_i32) -> i1);
919+
ifn!("llvm.is.constant.i64", fn(t_i64) -> i1);
920+
ifn!("llvm.is.constant.i128", fn(t_i128) -> i1);
921+
ifn!("llvm.is.constant.isize", fn(t_isize) -> i1);
922+
ifn!("llvm.is.constant.f32", fn(t_f32) -> i1);
923+
ifn!("llvm.is.constant.f64", fn(t_f64) -> i1);
924+
911925
ifn!("llvm.expect.i1", fn(i1, i1) -> i1);
912926
ifn!("llvm.eh.typeid.for", fn(ptr) -> t_i32);
913927
ifn!("llvm.localescape", fn(...) -> void);

compiler/rustc_codegen_llvm/src/intrinsic.rs

+4
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> {
119119
sym::likely => {
120120
self.call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(true)])
121121
}
122+
sym::is_val_statically_known => self.call_intrinsic(
123+
&format!("llvm.is.constant.{:?}", args[0].layout.immediate_llvm_type(self.cx)),
124+
&[args[0].immediate()],
125+
),
122126
sym::unlikely => self
123127
.call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(false)]),
124128
kw::Try => {

compiler/rustc_const_eval/src/const_eval/machine.rs

+5
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,11 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
531531
)?;
532532
}
533533
}
534+
// The intrinsic represents whether the value is known to the optimizer (LLVM).
535+
// We're not doing any optimizations here, so there is no optimizer that could know the value.
536+
// (We know the value here in the machine of course, but this is the runtime of that code,
537+
// not the optimization stage.)
538+
sym::is_val_statically_known => ecx.write_scalar(Scalar::from_bool(false), dest)?,
534539
_ => {
535540
throw_unsup_format!(
536541
"intrinsic `{intrinsic_name}` is not supported at compile-time"

compiler/rustc_hir_analysis/src/check/intrinsic.rs

+2
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
453453

454454
sym::black_box => (1, vec![param(0)], param(0)),
455455

456+
sym::is_val_statically_known => (1, vec![param(0)], tcx.types.bool),
457+
456458
sym::const_eval_select => (4, vec![param(0), param(1), param(2)], param(3)),
457459

458460
sym::vtable_size | sym::vtable_align => {

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ symbols! {
907907
io_stderr,
908908
io_stdout,
909909
irrefutable_let_patterns,
910+
is_val_statically_known,
910911
isa_attribute,
911912
isize,
912913
issue,

library/core/src/intrinsics.rs

+46
Original file line numberDiff line numberDiff line change
@@ -2511,6 +2511,52 @@ extern "rust-intrinsic" {
25112511
where
25122512
G: FnOnce<ARG, Output = RET>,
25132513
F: FnOnce<ARG, Output = RET>;
2514+
2515+
/// Returns whether the argument's value is statically known at
2516+
/// compile-time.
2517+
///
2518+
/// This is useful when there is a way of writing the code that will
2519+
/// be *faster* when some variables have known values, but *slower*
2520+
/// in the general case: an `if is_val_statically_known(var)` can be used
2521+
/// to select between these two variants. The `if` will be optimized away
2522+
/// and only the desired branch remains.
2523+
///
2524+
/// Formally speaking, this function non-deterministically returns `true`
2525+
/// or `false`, and the caller has to ensure sound behavior for both cases.
2526+
/// In other words, the following code has *Undefined Behavior*:
2527+
///
2528+
/// ```rust
2529+
/// if !is_val_statically_known(0) { unreachable_unchecked(); }
2530+
/// ```
2531+
///
2532+
/// This also means that the following code's behavior is unspecified; it
2533+
/// may panic, or it may not:
2534+
///
2535+
/// ```rust,no_run
2536+
/// assert_eq!(is_val_statically_known(0), black_box(is_val_statically_known(0)))
2537+
/// ```
2538+
///
2539+
/// Unsafe code may not rely on `is_val_statically_known` returning any
2540+
/// particular value, ever. However, the compiler will generally make it
2541+
/// return `true` only if the value of the argument is actually known.
2542+
///
2543+
/// When calling this in a `const fn`, both paths must be semantically
2544+
/// equivalent, that is, the result of the `true` branch and the `false`
2545+
/// branch must return the same value and have the same side-effects *no
2546+
/// matter what*.
2547+
#[rustc_const_unstable(feature = "is_val_statically_known", issue = "none")]
2548+
#[rustc_nounwind]
2549+
#[cfg(not(bootstrap))]
2550+
pub fn is_val_statically_known<T>(arg: T) -> bool;
2551+
}
2552+
2553+
// FIXME: Seems using `unstable` here completely ignores `rustc_allow_const_fn_unstable`
2554+
// and thus compiling stage0 core doesn't work.
2555+
#[rustc_const_stable(feature = "is_val_statically_known", since = "never")]
2556+
#[cfg(bootstrap)]
2557+
pub const unsafe fn is_val_statically_known<T>(t: T) -> bool {
2558+
mem::forget(t);
2559+
false
25142560
}
25152561

25162562
// Some functions are defined here because they accidentally got made

library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
//
198198
// Language features:
199199
// tidy-alphabetical-start
200+
#![cfg_attr(not(bootstrap), feature(is_val_statically_known))]
200201
#![feature(abi_unadjusted)]
201202
#![feature(adt_const_params)]
202203
#![feature(allow_internal_unsafe)]

library/core/src/num/int_macros.rs

+40-17
Original file line numberDiff line numberDiff line change
@@ -2088,26 +2088,49 @@ macro_rules! int_impl {
20882088
without modifying the original"]
20892089
#[inline]
20902090
#[rustc_inherit_overflow_checks]
2091+
#[rustc_allow_const_fn_unstable(is_val_statically_known)]
20912092
pub const fn pow(self, mut exp: u32) -> Self {
2092-
if exp == 0 {
2093-
return 1;
2094-
}
2095-
let mut base = self;
2096-
let mut acc = 1;
2097-
2098-
while exp > 1 {
2099-
if (exp & 1) == 1 {
2100-
acc = acc * base;
2093+
// SAFETY: This path has the same behavior as the other.
2094+
if unsafe { intrinsics::is_val_statically_known(self) }
2095+
&& self > 0
2096+
&& (self & (self - 1) == 0)
2097+
{
2098+
let power_used = match self.checked_ilog2() {
2099+
Some(v) => v,
2100+
// SAFETY: We just checked this is a power of two. and above zero.
2101+
None => unsafe { core::hint::unreachable_unchecked() },
2102+
};
2103+
// So it panics. Have to use `overflowing_mul` to efficiently set the
2104+
// result to 0 if not.
2105+
#[cfg(debug_assertions)]
2106+
{
2107+
_ = power_used * exp;
2108+
}
2109+
let (num_shl, overflowed) = power_used.overflowing_mul(exp);
2110+
let fine = !overflowed
2111+
& (num_shl < (mem::size_of::<Self>() * 8) as u32);
2112+
(1 << num_shl) * fine as Self
2113+
} else {
2114+
if exp == 0 {
2115+
return 1;
2116+
}
2117+
let mut base = self;
2118+
let mut acc = 1;
2119+
2120+
while exp > 1 {
2121+
if (exp & 1) == 1 {
2122+
acc = acc * base;
2123+
}
2124+
exp /= 2;
2125+
base = base * base;
21012126
}
2102-
exp /= 2;
2103-
base = base * base;
2104-
}
21052127

2106-
// since exp!=0, finally the exp must be 1.
2107-
// Deal with the final bit of the exponent separately, since
2108-
// squaring the base afterwards is not necessary and may cause a
2109-
// needless overflow.
2110-
acc * base
2128+
// since exp!=0, finally the exp must be 1.
2129+
// Deal with the final bit of the exponent separately, since
2130+
// squaring the base afterwards is not necessary and may cause a
2131+
// needless overflow.
2132+
acc * base
2133+
}
21112134
}
21122135

21132136
/// Returns the square root of the number, rounded down.

library/core/src/num/uint_macros.rs

+51-17
Original file line numberDiff line numberDiff line change
@@ -1973,26 +1973,60 @@ macro_rules! uint_impl {
19731973
without modifying the original"]
19741974
#[inline]
19751975
#[rustc_inherit_overflow_checks]
1976+
#[rustc_allow_const_fn_unstable(is_val_statically_known)]
19761977
pub const fn pow(self, mut exp: u32) -> Self {
1977-
if exp == 0 {
1978-
return 1;
1979-
}
1980-
let mut base = self;
1981-
let mut acc = 1;
1982-
1983-
while exp > 1 {
1984-
if (exp & 1) == 1 {
1985-
acc = acc * base;
1978+
// LLVM now knows that `self` is a constant value, but not a
1979+
// constant in Rust. This allows us to compute the power used at
1980+
// compile-time.
1981+
//
1982+
// This will likely add a branch in debug builds, but this should
1983+
// be ok.
1984+
//
1985+
// This is a massive performance boost in release builds as you can
1986+
// get the power of a power of two and the exponent through a `shl`
1987+
// instruction, but we must add a couple more checks for parity with
1988+
// our own `pow`.
1989+
// SAFETY: This path has the same behavior as the other.
1990+
if unsafe { intrinsics::is_val_statically_known(self) }
1991+
&& self.is_power_of_two()
1992+
{
1993+
let power_used = match self.checked_ilog2() {
1994+
Some(v) => v,
1995+
// SAFETY: We just checked this is a power of two. `0` is not a
1996+
// power of two.
1997+
None => unsafe { core::hint::unreachable_unchecked() },
1998+
};
1999+
// So it panics. Have to use `overflowing_mul` to efficiently set the
2000+
// result to 0 if not.
2001+
#[cfg(debug_assertions)]
2002+
{
2003+
_ = power_used * exp;
2004+
}
2005+
let (num_shl, overflowed) = power_used.overflowing_mul(exp);
2006+
let fine = !overflowed
2007+
& (num_shl < (mem::size_of::<Self>() * 8) as u32);
2008+
(1 << num_shl) * fine as Self
2009+
} else {
2010+
if exp == 0 {
2011+
return 1;
2012+
}
2013+
let mut base = self;
2014+
let mut acc = 1;
2015+
2016+
while exp > 1 {
2017+
if (exp & 1) == 1 {
2018+
acc = acc * base;
2019+
}
2020+
exp /= 2;
2021+
base = base * base;
19862022
}
1987-
exp /= 2;
1988-
base = base * base;
1989-
}
19902023

1991-
// since exp!=0, finally the exp must be 1.
1992-
// Deal with the final bit of the exponent separately, since
1993-
// squaring the base afterwards is not necessary and may cause a
1994-
// needless overflow.
1995-
acc * base
2024+
// since exp!=0, finally the exp must be 1.
2025+
// Deal with the final bit of the exponent separately, since
2026+
// squaring the base afterwards is not necessary and may cause a
2027+
// needless overflow.
2028+
acc * base
2029+
}
19962030
}
19972031

19982032
/// Returns the square root of the number, rounded down.

src/tools/miri/src/shims/intrinsics/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::iter;
55

66
use log::trace;
77

8+
use rand::Rng;
89
use rustc_apfloat::{Float, Round};
910
use rustc_middle::ty::layout::LayoutOf;
1011
use rustc_middle::{
@@ -141,6 +142,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
141142
this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
142143
}
143144

145+
// We want to return either `true` or `false` at random, or else something like
146+
// ```
147+
// if !is_val_statically_known(0) { unreachable_unchecked(); }
148+
// ```
149+
// Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
150+
"is_val_statically_known" => {
151+
let [_] = check_arg_count(args)?;
152+
let branch: bool = this.machine.rng.get_mut().gen();
153+
this.write_scalar(Scalar::from_bool(branch), dest)?;
154+
}
155+
144156
// Floating-point operations
145157
"fabsf32" => {
146158
let [f] = check_arg_count(args)?;

src/tools/miri/tests/pass/intrinsics.rs

+15
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ fn main() {
3333
assert_eq!(intrinsics::likely(false), false);
3434
assert_eq!(intrinsics::unlikely(true), true);
3535

36+
let mut saw_true = false;
37+
let mut saw_false = false;
38+
39+
for _ in 0..50 {
40+
if unsafe { intrinsics::is_val_statically_known(0) } {
41+
saw_true = true;
42+
} else {
43+
saw_false = true;
44+
}
45+
}
46+
assert!(
47+
saw_true && saw_false,
48+
"`is_val_statically_known` failed to return both true and false. Congrats, you won the lottery!"
49+
);
50+
3651
intrinsics::forget(Bomb);
3752

3853
let _v = intrinsics::discriminant_value(&Some(()));
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// #[cfg(bootstrap)]
2+
// ignore-stage1
3+
// compile-flags: --crate-type=lib -Zmerge-functions=disabled
4+
5+
#![feature(core_intrinsics)]
6+
7+
use std::intrinsics::is_val_statically_known;
8+
9+
pub struct A(u32);
10+
pub enum B {
11+
Ye(u32),
12+
}
13+
14+
#[inline]
15+
pub fn _u32(a: u32) -> i32 {
16+
if unsafe { is_val_statically_known(a) } { 1 } else { 0 }
17+
}
18+
19+
// CHECK-LABEL: @_u32_true(
20+
#[no_mangle]
21+
pub fn _u32_true() -> i32 {
22+
// CHECK: ret i32 1
23+
_u32(1)
24+
}
25+
26+
// CHECK-LABEL: @_u32_false(
27+
#[no_mangle]
28+
pub fn _u32_false(a: u32) -> i32 {
29+
// CHECK: ret i32 0
30+
_u32(a)
31+
}
32+
33+
#[inline]
34+
pub fn _bool(b: bool) -> i32 {
35+
if unsafe { is_val_statically_known(b) } { 3 } else { 2 }
36+
}
37+
38+
// CHECK-LABEL: @_bool_true(
39+
#[no_mangle]
40+
pub fn _bool_true() -> i32 {
41+
// CHECK: ret i32 3
42+
_bool(true)
43+
}
44+
45+
// CHECK-LABEL: @_bool_false(
46+
#[no_mangle]
47+
pub fn _bool_false(b: bool) -> i32 {
48+
// CHECK: ret i32 2
49+
_bool(b)
50+
}

0 commit comments

Comments
 (0)