From abeda62691605355d0ded2ae08d887b7da782616 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Tue, 1 Aug 2023 15:35:12 +0000 Subject: [PATCH 01/11] Add new intrinsic `is_constant` and optimize `pow` --- compiler/rustc_codegen_llvm/src/context.rs | 2 + compiler/rustc_codegen_llvm/src/intrinsic.rs | 1 + .../src/interpret/intrinsics.rs | 1 + compiler/rustc_hir_analysis/messages.ftl | 2 + .../rustc_hir_analysis/src/check/intrinsic.rs | 8 +- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/intrinsics.rs | 19 +++++ library/core/src/num/int_macros.rs | 70 ++++++++++++---- library/core/src/num/uint_macros.rs | 82 +++++++++++++++---- tests/codegen/is_constant.rs | 47 +++++++++++ tests/codegen/pow_of_two.rs | 57 +++++++++++++ 11 files changed, 259 insertions(+), 31 deletions(-) create mode 100644 tests/codegen/is_constant.rs create mode 100644 tests/codegen/pow_of_two.rs diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 24fd5bbf8c526..0ccdbe9b193f9 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -892,6 +892,8 @@ impl<'ll> CodegenCx<'ll, '_> { ifn!("llvm.lifetime.start.p0i8", fn(t_i64, ptr) -> void); ifn!("llvm.lifetime.end.p0i8", fn(t_i64, ptr) -> void); + ifn!("llvm.is.constant", fn(...) -> i1); + ifn!("llvm.expect.i1", fn(i1, i1) -> i1); ifn!("llvm.eh.typeid.for", fn(ptr) -> t_i32); ifn!("llvm.localescape", fn(...) -> void); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index a9b06030e70a6..d38cb299e69dd 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -119,6 +119,7 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { sym::likely => { self.call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(true)]) } + sym::is_constant => self.call_intrinsic("llvm.is.constant", &[args[0].immediate()]), sym::unlikely => self .call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(false)]), kw::Try => { diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index f22cd919c3695..5a589e52f44e5 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -258,6 +258,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { sym::copy => { self.copy_intrinsic(&args[0], &args[1], &args[2], /*nonoverlapping*/ false)?; } + sym::is_constant => self.write_scalar(Scalar::from_bool(true), dest)?, sym::write_bytes => { self.write_bytes_intrinsic(&args[0], &args[1], &args[2])?; } diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 597cae6ff33ca..63296a31e44c0 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -102,6 +102,8 @@ hir_analysis_invalid_union_field = hir_analysis_invalid_union_field_sugg = wrap the field type in `ManuallyDrop<...>` +hir_analysis_is_constant_zst = parameter for `is_constant` cannot be zero-sized + hir_analysis_late_bound_const_in_apit = `impl Trait` can only mention const parameters from an fn or impl .label = const parameter declared here diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index f89e2e5c25bf2..15add6503caf3 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -114,7 +114,8 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: DefId) -> hir | sym::forget | sym::black_box | sym::variant_count - | sym::ptr_mask => hir::Unsafety::Normal, + | sym::ptr_mask + | sym::is_constant => hir::Unsafety::Normal, _ => hir::Unsafety::Unsafe, }; @@ -477,6 +478,11 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { sym::black_box => (1, vec![param(0)], param(0)), + sym::is_constant => { + // FIXME: ZSTs cause an ICE. We should check for this. + (1, vec![param(0)], tcx.types.bool) + } + sym::const_eval_select => (4, vec![param(0), param(1), param(2)], param(3)), sym::vtable_size | sym::vtable_align => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 28a2dfebcfe96..8f583ef8c7894 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -861,6 +861,7 @@ symbols! { intra_doc_pointers, intrinsics, irrefutable_let_patterns, + is_constant, isa_attribute, isize, issue, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 676d4f2f38ca3..2b9a4980b4bed 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2489,6 +2489,25 @@ extern "rust-intrinsic" { /// constructing an empty slice) is returned. #[rustc_nounwind] pub fn option_payload_ptr(arg: *const Option) -> *const T; + + /// Returns whether the argument is known at compile-time. This opens the + /// door for additional optimizations, in that LLVM can then optimize + /// following checks to either `true` or `false`. This is often paired with + /// an `if-else` statement, as LLVM will only keep one branch (if + /// optimizations are on). + /// + /// "Constant" in this context is not the same as a constant in Rust. As + /// such, this should only be used for optimizations. + /// + /// Note that, unlike most intrinsics, this is safe to call; + /// it does not require an `unsafe` block. + /// Therefore, implementations must not require the user to uphold + /// any safety invariants. + #[rustc_const_stable(feature = "todo", since = "never")] + #[rustc_safe_intrinsic] + #[rustc_nounwind] + #[cfg(not(bootstrap))] + pub fn is_constant(arg: T) -> bool; } // Some functions are defined here because they accidentally got made diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 1f43520e1b30a..52bce730f91bd 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2040,25 +2040,65 @@ macro_rules! int_impl { #[inline] #[rustc_inherit_overflow_checks] pub const fn pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; + #[cfg(not(bootstrap))] + if intrinsics::is_constant(self) && self > 0 && (self & (self - 1) == 0) { + let power_used = match self.checked_ilog2() { + Some(v) => v, + // SAFETY: We just checked this is a power of two. and above zero. + None => unsafe { core::hint::unreachable_unchecked() }, + }; + // So it panics. Have to use `overflowing_mul` to efficiently set the + // result to 0 if not. + #[cfg(debug_assertions)] + power_used * exp; + let (num_shl, overflowed) = power_used.overflowing_mul(exp); + let fine = !overflowed + & (power_used < (mem::size_of::() * 8) as u32); + (1 << num_shl) * fine as Self + } else { + if exp == 0 { + return 1; + } + let mut base = self; + let mut acc = 1; + + while exp > 1 { + if (exp & 1) == 1 { + acc = acc * base; + } + exp /= 2; + base = base * base; + } + + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + acc * base } - let mut base = self; - let mut acc = 1; - while exp > 1 { - if (exp & 1) == 1 { - acc = acc * base; + #[cfg(bootstrap)] + { + if exp == 0 { + return 1; + } + let mut base = self; + let mut acc = 1; + + while exp > 1 { + if (exp & 1) == 1 { + acc = acc * base; + } + exp /= 2; + base = base * base; } - exp /= 2; - base = base * base; - } - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + acc * base + } } /// Calculates the quotient of Euclidean division of `self` by `rhs`. diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 81148c7cc51f7..969ebdcbdab08 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1958,25 +1958,77 @@ macro_rules! uint_impl { #[inline] #[rustc_inherit_overflow_checks] pub const fn pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; + // LLVM now knows that `self` is a constant value, but not a + // constant in Rust. This allows us to compute the power used at + // compile-time. + // + // This will likely add a branch in debug builds, but this should + // be ok. + // + // This is a massive performance boost in release builds as you can + // get the power of a power of two and the exponent through a `shl` + // instruction, but we must add a couple more checks for parity with + // our own `pow`. + #[cfg(not(bootstrap))] + if intrinsics::is_constant(self) && self.is_power_of_two() { + let power_used = match self.checked_ilog2() { + Some(v) => v, + // SAFETY: We just checked this is a power of two. `0` is not a + // power of two. + None => unsafe { core::hint::unreachable_unchecked() }, + }; + // So it panics. Have to use `overflowing_mul` to efficiently set the + // result to 0 if not. + #[cfg(debug_assertions)] + power_used * exp; + let (num_shl, overflowed) = power_used.overflowing_mul(exp); + let fine = !overflowed + & (power_used < (mem::size_of::() * 8) as u32); + (1 << num_shl) * fine as Self + } else { + if exp == 0 { + return 1; + } + let mut base = self; + let mut acc = 1; + + while exp > 1 { + if (exp & 1) == 1 { + acc = acc * base; + } + exp /= 2; + base = base * base; + } + + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + acc * base } - let mut base = self; - let mut acc = 1; - while exp > 1 { - if (exp & 1) == 1 { - acc = acc * base; + #[cfg(bootstrap)] + { + if exp == 0 { + return 1; + } + let mut base = self; + let mut acc = 1; + + while exp > 1 { + if (exp & 1) == 1 { + acc = acc * base; + } + exp /= 2; + base = base * base; } - exp /= 2; - base = base * base; - } - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + acc * base + } } /// Performs Euclidean division. diff --git a/tests/codegen/is_constant.rs b/tests/codegen/is_constant.rs new file mode 100644 index 0000000000000..33b6553940f42 --- /dev/null +++ b/tests/codegen/is_constant.rs @@ -0,0 +1,47 @@ +// compile-flags: --crate-type=lib +#![feature(core_intrinsics)] + +use std::intrinsics::is_constant; + +pub struct A(u32); +pub enum B { + Ye(u32), +} + +#[inline] +pub fn tuple_struct(a: A) -> i32 { + if is_constant(a) { 1 } else { 0 } +} + +// CHECK-LABEL: @tuple_struct_true( +#[no_mangle] +pub fn tuple_struct_true() -> i32 { + // CHECK: ret i32 1 + tuple_struct(A(1)) +} + +// CHECK-LABEL: @tuple_struct_false( +#[no_mangle] +pub fn tuple_struct_false(a: A) -> i32 { + // CHECK: ret i32 0 + tuple_struct(a) +} + +#[inline] +pub fn r#enum(b: B) -> i32 { + if is_constant(b) { 3 } else { 2 } +} + +// CHECK-LABEL: @enum_true( +#[no_mangle] +pub fn enum_true() -> i32 { + // CHECK: ret i32 3 + r#enum(B::Ye(2)) +} + +// CHECK-LABEL: @enum_false( +#[no_mangle] +pub fn enum_false(b: B) -> i32 { + // CHECK: ret i32 2 + r#enum(b) +} diff --git a/tests/codegen/pow_of_two.rs b/tests/codegen/pow_of_two.rs new file mode 100644 index 0000000000000..7cfc08d2936b0 --- /dev/null +++ b/tests/codegen/pow_of_two.rs @@ -0,0 +1,57 @@ +// compile-flags: --crate-type=lib + +// CHECK: @b = unnamed_addr alias i64 (i32), ptr @a +// CHECK-LABEL: @a( +#[no_mangle] +pub fn a(exp: u32) -> u64 { + // CHECK: %[[R:.+]] = and i32 %exp, 63 + // CHECK: %[[R:.+]] = zext i32 %[[R:.+]] to i64 + // CHECK: %[[R:.+]].i = shl nuw i64 1, %[[R:.+]] + // CHECK: ret i64 %[[R:.+]].i + 2u64.pow(exp) +} + +#[no_mangle] +pub fn b(exp: u32) -> i64 { + 2i64.pow(exp) +} + +// CHECK-LABEL: @c( +#[no_mangle] +pub fn c(exp: u32) -> u32 { + // CHECK: %[[R:.+]].0.i = shl i32 %exp, 1 + // CHECK: %[[R:.+]].1.i = icmp sgt i32 %exp, -1 + // CHECK: %[[R:.+]] = and i32 %[[R:.+]].0.i, 30 + // CHECK: %[[R:.+]].i = zext i1 %[[R:.+]].1.i to i32 + // CHECK: %[[R:.+]] = shl nuw nsw i32 %[[R:.+]].i, %[[R:.+]] + // CHECK: ret i32 %[[R:.+]] + 4u32.pow(exp) +} + +// CHECK-LABEL: @d( +#[no_mangle] +pub fn d(exp: u32) -> u32 { + // CHECK: %[[R:.+]] = tail call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %exp, i32 5) + // CHECK: %[[R:.+]].0.i = extractvalue { i32, i1 } %[[R:.+]], 0 + // CHECK: %[[R:.+]].1.i = extractvalue { i32, i1 } %[[R:.+]], 1 + // CHECK: %[[R:.+]].i = xor i1 %[[R:.+]].1.i, true + // CHECK: %[[R:.+]] = and i32 %[[R:.+]].0.i, 31 + // CHECK: %[[R:.+]].i = zext i1 %[[R:.+]].i to i32 + // CHECK: %[[R:.+]] = shl nuw i32 %[[R:.+]].i, %[[R:.+]] + // CHECK: ret i32 %[[R:.+]] + 32u32.pow(exp) +} + +// CHECK-LABEL: @e( +#[no_mangle] +pub fn e(exp: u32) -> i32 { + // CHECK: %[[R:.+]] = tail call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %exp, i32 5) + // CHECK: %[[R:.+]].1.i = extractvalue { i32, i1 } %[[R:.+]], 1 + // CHECK: %[[R:.+]].i = xor i1 %[[R:.+]].1.i, true + // CHECK: %[[R:.+]].i = zext i1 %[[R:.+]].i to i32 + // CHECK: %[[R:.+]].0.i = extractvalue { i32, i1 } %[[R:.+]], 0 + // CHECK: %[[R:.+]] = and i32 %[[R:.+]].0.i, 31 + // CHECK: %[[R:.+]].010.i = shl nuw i32 %[[R:.+]].i, %[[R:.+]] + // CHECK: ret i32 %[[R:.+]].010.i + 32i32.pow(exp) +} From 4aa0608d12e2c26df7e0492d4b3fea3f2eb9b9ce Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Tue, 1 Aug 2023 15:54:53 +0000 Subject: [PATCH 02/11] Fix overflow check --- library/core/src/num/int_macros.rs | 2 +- library/core/src/num/uint_macros.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 52bce730f91bd..d9cd6c71a9e92 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2053,7 +2053,7 @@ macro_rules! int_impl { power_used * exp; let (num_shl, overflowed) = power_used.overflowing_mul(exp); let fine = !overflowed - & (power_used < (mem::size_of::() * 8) as u32); + & (num_shl < (mem::size_of::() * 8) as u32); (1 << num_shl) * fine as Self } else { if exp == 0 { diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 969ebdcbdab08..1a851621f5e01 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1983,7 +1983,7 @@ macro_rules! uint_impl { power_used * exp; let (num_shl, overflowed) = power_used.overflowing_mul(exp); let fine = !overflowed - & (power_used < (mem::size_of::() * 8) as u32); + & (num_shl < (mem::size_of::() * 8) as u32); (1 << num_shl) * fine as Self } else { if exp == 0 { From 2605ea763f48c4d1daaa0dce8912c9c7c6601d07 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Wed, 2 Aug 2023 23:17:12 +0000 Subject: [PATCH 03/11] Make MIRI choose the path randomly and rename the intrinsic --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 4 +- .../src/const_eval/machine.rs | 1 + .../src/interpret/intrinsics.rs | 2 +- .../rustc_hir_analysis/src/check/intrinsic.rs | 8 +-- compiler/rustc_span/src/symbol.rs | 2 +- library/core/src/intrinsics.rs | 33 +++++---- library/core/src/lib.rs | 1 + library/core/src/num/int_macros.rs | 11 ++- library/core/src/num/uint_macros.rs | 8 ++- src/tools/miri/src/shims/intrinsics/mod.rs | 67 +++++++++++++++++++ tests/codegen/is_constant.rs | 47 ------------- tests/codegen/pow_of_two.rs | 32 +++++---- 12 files changed, 129 insertions(+), 87 deletions(-) delete mode 100644 tests/codegen/is_constant.rs diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index d38cb299e69dd..ee4ccd0359e09 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -119,7 +119,9 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { sym::likely => { self.call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(true)]) } - sym::is_constant => self.call_intrinsic("llvm.is.constant", &[args[0].immediate()]), + sym::is_compile_time_known => { + self.call_intrinsic("llvm.is.constant", &[args[0].immediate()]) + } sym::unlikely => self .call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(false)]), kw::Try => { diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index b740b79d16241..12ba7ad894735 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -542,6 +542,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, )?; } } + sym::is_compile_time_known => ecx.write_scalar(Scalar::from_bool(false), dest)?, _ => { throw_unsup_format!( "intrinsic `{intrinsic_name}` is not supported at compile-time" diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 5a589e52f44e5..5add0f7cccde0 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -258,7 +258,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { sym::copy => { self.copy_intrinsic(&args[0], &args[1], &args[2], /*nonoverlapping*/ false)?; } - sym::is_constant => self.write_scalar(Scalar::from_bool(true), dest)?, + sym::is_compile_time_known => self.write_scalar(Scalar::from_bool(false), dest)?, sym::write_bytes => { self.write_bytes_intrinsic(&args[0], &args[1], &args[2])?; } diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 15add6503caf3..0019e35384271 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -114,8 +114,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: DefId) -> hir | sym::forget | sym::black_box | sym::variant_count - | sym::ptr_mask - | sym::is_constant => hir::Unsafety::Normal, + | sym::ptr_mask => hir::Unsafety::Normal, _ => hir::Unsafety::Unsafe, }; @@ -478,10 +477,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { sym::black_box => (1, vec![param(0)], param(0)), - sym::is_constant => { - // FIXME: ZSTs cause an ICE. We should check for this. - (1, vec![param(0)], tcx.types.bool) - } + sym::is_compile_time_known => (1, vec![param(0)], tcx.types.bool), sym::const_eval_select => (4, vec![param(0), param(1), param(2)], param(3)), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 8f583ef8c7894..638b2a42a61ab 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -861,7 +861,7 @@ symbols! { intra_doc_pointers, intrinsics, irrefutable_let_patterns, - is_constant, + is_compile_time_known, isa_attribute, isize, issue, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 2b9a4980b4bed..e078fdebee5fa 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2490,24 +2490,29 @@ extern "rust-intrinsic" { #[rustc_nounwind] pub fn option_payload_ptr(arg: *const Option) -> *const T; - /// Returns whether the argument is known at compile-time. This opens the - /// door for additional optimizations, in that LLVM can then optimize - /// following checks to either `true` or `false`. This is often paired with - /// an `if-else` statement, as LLVM will only keep one branch (if - /// optimizations are on). + /// Returns whether the argument is known at compile-time. /// - /// "Constant" in this context is not the same as a constant in Rust. As - /// such, this should only be used for optimizations. + /// This is useful when there is a way of writing the code that will + /// be *faster* when some variables have known values, but *slower* + /// in the general case: an `if is_compile_time_known(var)` can be used + /// to select between these two variants. The `if` will be optimized away + /// and only the desired branch remains. /// - /// Note that, unlike most intrinsics, this is safe to call; - /// it does not require an `unsafe` block. - /// Therefore, implementations must not require the user to uphold - /// any safety invariants. - #[rustc_const_stable(feature = "todo", since = "never")] - #[rustc_safe_intrinsic] + /// Formally speaking, this function non-deterministically returns `true` + /// or `false`, and the caller has to ensure sound behavior for both cases. + /// In other words, the following code has *Undefined Behavior*: + /// + /// ```rust + /// if !is_compile_time_known(0) { unreachable_unchecked(); } + /// ``` + /// + /// Unsafe code may not rely on `is_compile_time_known` returning any + /// particular value, ever. However, the compiler will generally make it + /// return `true` only if the value of the argument is actually known. + #[rustc_const_unstable(feature = "is_compile_time_known", issue = "none")] #[rustc_nounwind] #[cfg(not(bootstrap))] - pub fn is_constant(arg: T) -> bool; + pub fn is_compile_time_known(arg: T) -> bool; } // Some functions are defined here because they accidentally got made diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index a2729b3743cc2..1befe2ca04021 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -187,6 +187,7 @@ // // Language features: // tidy-alphabetical-start +#![cfg_attr(not(bootstrap), feature(is_compile_time_known))] #![feature(abi_unadjusted)] #![feature(adt_const_params)] #![feature(allow_internal_unsafe)] diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index d9cd6c71a9e92..dae8e7846bd80 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2039,9 +2039,14 @@ macro_rules! int_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] + #[rustc_allow_const_fn_unstable(is_compile_time_known)] pub const fn pow(self, mut exp: u32) -> Self { #[cfg(not(bootstrap))] - if intrinsics::is_constant(self) && self > 0 && (self & (self - 1) == 0) { + // SAFETY: This path has the same behavior as the other. + if unsafe { intrinsics::is_compile_time_known(self) } + && self > 0 + && (self & (self - 1) == 0) + { let power_used = match self.checked_ilog2() { Some(v) => v, // SAFETY: We just checked this is a power of two. and above zero. @@ -2050,7 +2055,9 @@ macro_rules! int_impl { // So it panics. Have to use `overflowing_mul` to efficiently set the // result to 0 if not. #[cfg(debug_assertions)] - power_used * exp; + { + _ = power_used * exp; + } let (num_shl, overflowed) = power_used.overflowing_mul(exp); let fine = !overflowed & (num_shl < (mem::size_of::() * 8) as u32); diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 1a851621f5e01..2ca61c38c628a 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1957,6 +1957,7 @@ macro_rules! uint_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] + #[rustc_allow_const_fn_unstable(is_compile_time_known)] pub const fn pow(self, mut exp: u32) -> Self { // LLVM now knows that `self` is a constant value, but not a // constant in Rust. This allows us to compute the power used at @@ -1970,7 +1971,8 @@ macro_rules! uint_impl { // instruction, but we must add a couple more checks for parity with // our own `pow`. #[cfg(not(bootstrap))] - if intrinsics::is_constant(self) && self.is_power_of_two() { + // SAFETY: This path has the same behavior as the other. + if unsafe { intrinsics::is_compile_time_known(self) } && self.is_power_of_two() { let power_used = match self.checked_ilog2() { Some(v) => v, // SAFETY: We just checked this is a power of two. `0` is not a @@ -1980,7 +1982,9 @@ macro_rules! uint_impl { // So it panics. Have to use `overflowing_mul` to efficiently set the // result to 0 if not. #[cfg(debug_assertions)] - power_used * exp; + { + _ = power_used * exp; + } let (num_shl, overflowed) = power_used.overflowing_mul(exp); let fine = !overflowed & (num_shl < (mem::size_of::() * 8) as u32); diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index b2c297fe542c5..6e77d8a6ae153 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -131,6 +131,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?; } + // The default implementation always returns `false`, but we want + // to return either `true` or `false` at random. + "is_comptile_time_known" => { + let rand = 0; + _ = getrandom::getrandom(&mut [rand]); + + this.write_scalar( + Scalar::from_bool(if (rand % 1) == 1 { true } else { false }), + dest, + )?; + } + // Floating-point operations "fabsf32" => { let [f] = check_arg_count(args)?; @@ -401,4 +413,59 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(()) } +<<<<<<< HEAD +======= + + fn float_to_int_unchecked( + &self, + f: F, + dest_ty: Ty<'tcx>, + ) -> InterpResult<'tcx, Scalar> + where + F: Float + Into>, + { + let this = self.eval_context_ref(); + + // Step 1: cut off the fractional part of `f`. The result of this is + // guaranteed to be precisely representable in IEEE floats. + let f = f.round_to_integral(Round::TowardZero).value; + + // Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step. + Ok(match dest_ty.kind() { + // Unsigned + ty::Uint(t) => { + let size = Integer::from_uint_ty(this, *t).size(); + let res = f.to_u128(size.bits_usize()); + if res.status.is_empty() { + // No status flags means there was no further rounding or other loss of precision. + Scalar::from_uint(res.value, size) + } else { + // `f` was not representable in this integer type. + throw_ub_format!( + "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", + ); + } + } + // Signed + ty::Int(t) => { + let size = Integer::from_int_ty(this, *t).size(); + let res = f.to_i128(size.bits_usize()); + if res.status.is_empty() { + // No status flags means there was no further rounding or other loss of precision. + Scalar::from_int(res.value, size) + } else { + // `f` was not representable in this integer type. + throw_ub_format!( + "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", + ); + } + } + // Nothing else + _ => span_bug!( + this.cur_span(), + "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" + ), + }) + } +>>>>>>> acbf90a9489 (Make MIRI choose the path randomly and rename the intrinsic) } diff --git a/tests/codegen/is_constant.rs b/tests/codegen/is_constant.rs deleted file mode 100644 index 33b6553940f42..0000000000000 --- a/tests/codegen/is_constant.rs +++ /dev/null @@ -1,47 +0,0 @@ -// compile-flags: --crate-type=lib -#![feature(core_intrinsics)] - -use std::intrinsics::is_constant; - -pub struct A(u32); -pub enum B { - Ye(u32), -} - -#[inline] -pub fn tuple_struct(a: A) -> i32 { - if is_constant(a) { 1 } else { 0 } -} - -// CHECK-LABEL: @tuple_struct_true( -#[no_mangle] -pub fn tuple_struct_true() -> i32 { - // CHECK: ret i32 1 - tuple_struct(A(1)) -} - -// CHECK-LABEL: @tuple_struct_false( -#[no_mangle] -pub fn tuple_struct_false(a: A) -> i32 { - // CHECK: ret i32 0 - tuple_struct(a) -} - -#[inline] -pub fn r#enum(b: B) -> i32 { - if is_constant(b) { 3 } else { 2 } -} - -// CHECK-LABEL: @enum_true( -#[no_mangle] -pub fn enum_true() -> i32 { - // CHECK: ret i32 3 - r#enum(B::Ye(2)) -} - -// CHECK-LABEL: @enum_false( -#[no_mangle] -pub fn enum_false(b: B) -> i32 { - // CHECK: ret i32 2 - r#enum(b) -} diff --git a/tests/codegen/pow_of_two.rs b/tests/codegen/pow_of_two.rs index 7cfc08d2936b0..db19b71fe4f34 100644 --- a/tests/codegen/pow_of_two.rs +++ b/tests/codegen/pow_of_two.rs @@ -6,8 +6,8 @@ pub fn a(exp: u32) -> u64 { // CHECK: %[[R:.+]] = and i32 %exp, 63 // CHECK: %[[R:.+]] = zext i32 %[[R:.+]] to i64 - // CHECK: %[[R:.+]].i = shl nuw i64 1, %[[R:.+]] - // CHECK: ret i64 %[[R:.+]].i + // CHECK: %[[R:.+]] = shl nuw i64 %[[R:.+]].i, %[[R:.+]] + // CHECK: ret i64 %[[R:.+]] 2u64.pow(exp) } @@ -21,9 +21,11 @@ pub fn b(exp: u32) -> i64 { pub fn c(exp: u32) -> u32 { // CHECK: %[[R:.+]].0.i = shl i32 %exp, 1 // CHECK: %[[R:.+]].1.i = icmp sgt i32 %exp, -1 - // CHECK: %[[R:.+]] = and i32 %[[R:.+]].0.i, 30 - // CHECK: %[[R:.+]].i = zext i1 %[[R:.+]].1.i to i32 - // CHECK: %[[R:.+]] = shl nuw nsw i32 %[[R:.+]].i, %[[R:.+]] + // CHECK: %[[R:.+]].i = icmp ult i32 %[[R:.+]].0.i, 32 + // CHECK: %fine.i = and i1 %[[R:.+]].1.i, %[[R:.+]].i + // CHECK: %0 = and i32 %[[R:.+]].0.i, 30 + // CHECK: %[[R:.+]].i = zext i1 %fine.i to i32 + // CHECK: %[[R:.+]] = shl nuw nsw i32 %[[R:.+]].i, %0 // CHECK: ret i32 %[[R:.+]] 4u32.pow(exp) } @@ -31,13 +33,15 @@ pub fn c(exp: u32) -> u32 { // CHECK-LABEL: @d( #[no_mangle] pub fn d(exp: u32) -> u32 { - // CHECK: %[[R:.+]] = tail call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %exp, i32 5) + // CHECK: tail call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %exp, i32 5) // CHECK: %[[R:.+]].0.i = extractvalue { i32, i1 } %[[R:.+]], 0 // CHECK: %[[R:.+]].1.i = extractvalue { i32, i1 } %[[R:.+]], 1 // CHECK: %[[R:.+]].i = xor i1 %[[R:.+]].1.i, true + // CHECK: %[[R:.+]].i = icmp ult i32 %[[R:.+]].0.i, 32 + // CHECK: %fine.i = and i1 %[[R:.+]].i, %[[R:.+]].i // CHECK: %[[R:.+]] = and i32 %[[R:.+]].0.i, 31 - // CHECK: %[[R:.+]].i = zext i1 %[[R:.+]].i to i32 - // CHECK: %[[R:.+]] = shl nuw i32 %[[R:.+]].i, %[[R:.+]] + // CHECK: %[[R:.+]].i = zext i1 %fine.i to i32 + // CHECK: %[[R:.+]] = shl nuw i32 %[[R:.+]].i, %1 // CHECK: ret i32 %[[R:.+]] 32u32.pow(exp) } @@ -45,13 +49,15 @@ pub fn d(exp: u32) -> u32 { // CHECK-LABEL: @e( #[no_mangle] pub fn e(exp: u32) -> i32 { - // CHECK: %[[R:.+]] = tail call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %exp, i32 5) + // CHECK: tail call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %exp, i32 5) + // CHECK: %[[R:.+]].0.i = extractvalue { i32, i1 } %[[R:.+]], 0 + // CHECK: %[[R:.+]].i = icmp ult i32 %[[R:.+]].0.i, 32 // CHECK: %[[R:.+]].1.i = extractvalue { i32, i1 } %[[R:.+]], 1 // CHECK: %[[R:.+]].i = xor i1 %[[R:.+]].1.i, true - // CHECK: %[[R:.+]].i = zext i1 %[[R:.+]].i to i32 - // CHECK: %[[R:.+]].0.i = extractvalue { i32, i1 } %[[R:.+]], 0 + // CHECK: %fine.i = and i1 %[[R:.+]].i, %[[R:.+]].i + // CHECK: %[[R:.+]].i = zext i1 %fine.i to i32 // CHECK: %[[R:.+]] = and i32 %[[R:.+]].0.i, 31 - // CHECK: %[[R:.+]].010.i = shl nuw i32 %[[R:.+]].i, %[[R:.+]] - // CHECK: ret i32 %[[R:.+]].010.i + // CHECK: %[[R:.+]] = shl nuw i32 %[[R:.+]].i, %1 + // CHECK: ret i32 %[[R:.+]] 32i32.pow(exp) } From f569ceac74b77c55ca48a6784159afbec345dee4 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Wed, 2 Aug 2023 23:28:13 +0000 Subject: [PATCH 04/11] Add back test --- tests/codegen/is_compile_time_known.rs | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/codegen/is_compile_time_known.rs diff --git a/tests/codegen/is_compile_time_known.rs b/tests/codegen/is_compile_time_known.rs new file mode 100644 index 0000000000000..c3713b94b610f --- /dev/null +++ b/tests/codegen/is_compile_time_known.rs @@ -0,0 +1,47 @@ +// compile-flags: --crate-type=lib +#![feature(core_intrinsics)] + +use std::intrinsics::is_compile_time_known; + +pub struct A(u32); +pub enum B { + Ye(u32), +} + +#[inline] +pub fn tuple_struct(a: A) -> i32 { + if unsafe { is_compile_time_known(a) } { 1 } else { 0 } +} + +// CHECK-LABEL: @tuple_struct_true( +#[no_mangle] +pub fn tuple_struct_true() -> i32 { + // CHECK: ret i32 1 + tuple_struct(A(1)) +} + +// CHECK-LABEL: @tuple_struct_false( +#[no_mangle] +pub fn tuple_struct_false(a: A) -> i32 { + // CHECK: ret i32 0 + tuple_struct(a) +} + +#[inline] +pub fn r#enum(b: B) -> i32 { + if unsafe { is_compile_time_known(b) } { 3 } else { 2 } +} + +// CHECK-LABEL: @enum_true( +#[no_mangle] +pub fn enum_true() -> i32 { + // CHECK: ret i32 3 + r#enum(B::Ye(2)) +} + +// CHECK-LABEL: @enum_false( +#[no_mangle] +pub fn enum_false(b: B) -> i32 { + // CHECK: ret i32 2 + r#enum(b) +} From 1b756695907a4f50bb5a53ea4951e62a22b6ad33 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Thu, 3 Aug 2023 13:32:52 +0000 Subject: [PATCH 05/11] Add miri test and make it operate on `ptr` --- compiler/rustc_codegen_llvm/src/context.rs | 5 ++- compiler/rustc_codegen_llvm/src/intrinsic.rs | 4 +-- .../src/const_eval/machine.rs | 7 +++- .../src/interpret/intrinsics.rs | 1 - .../rustc_hir_analysis/src/check/intrinsic.rs | 4 ++- compiler/rustc_span/src/symbol.rs | 2 +- library/core/src/intrinsics.rs | 32 +++++++++++++++---- library/core/src/lib.rs | 2 +- library/core/src/num/int_macros.rs | 28 ++-------------- library/core/src/num/mod.rs | 1 + library/core/src/num/uint_macros.rs | 30 +++-------------- src/tools/miri/src/shims/intrinsics/mod.rs | 29 +++++++++-------- src/tools/miri/tests/pass/intrinsics.rs | 6 ++++ 13 files changed, 71 insertions(+), 80 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 0ccdbe9b193f9..f0723123c14d1 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -892,7 +892,10 @@ impl<'ll> CodegenCx<'ll, '_> { ifn!("llvm.lifetime.start.p0i8", fn(t_i64, ptr) -> void); ifn!("llvm.lifetime.end.p0i8", fn(t_i64, ptr) -> void); - ifn!("llvm.is.constant", fn(...) -> i1); + // Defining only the `ptr` variant of this overloaded intrinsic means + // we can call this on any type we want (that doesn't ICE), but at the + // slight cost of needing to write `addr_of!` everywhere. + ifn!("llvm.is.constant.ptr", fn(ptr) -> i1); ifn!("llvm.expect.i1", fn(i1, i1) -> i1); ifn!("llvm.eh.typeid.for", fn(ptr) -> t_i32); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index ee4ccd0359e09..43ebec15e22ee 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -119,8 +119,8 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { sym::likely => { self.call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(true)]) } - sym::is_compile_time_known => { - self.call_intrinsic("llvm.is.constant", &[args[0].immediate()]) + sym::is_val_statically_known => { + self.call_intrinsic("llvm.is.constant.ptr", &[args[0].immediate()]) } sym::unlikely => self .call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(false)]), diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 12ba7ad894735..0f510d8fd7117 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -542,7 +542,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, )?; } } - sym::is_compile_time_known => ecx.write_scalar(Scalar::from_bool(false), dest)?, + // The name of this intrinsic is actually misleading. It actually + // represents whether the value is known to the optimizer (LLVM). + // The value of `arg`, while known to the machine, should be + // considered unknown to the optimizer as we haven't gotten to the + // codegen stage or ran any sort of optimizations yet. + sym::is_val_statically_known => ecx.write_scalar(Scalar::from_bool(false), dest)?, _ => { throw_unsup_format!( "intrinsic `{intrinsic_name}` is not supported at compile-time" diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 5add0f7cccde0..f22cd919c3695 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -258,7 +258,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { sym::copy => { self.copy_intrinsic(&args[0], &args[1], &args[2], /*nonoverlapping*/ false)?; } - sym::is_compile_time_known => self.write_scalar(Scalar::from_bool(false), dest)?, sym::write_bytes => { self.write_bytes_intrinsic(&args[0], &args[1], &args[2])?; } diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 0019e35384271..ad6a2c3794dcd 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -477,7 +477,9 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { sym::black_box => (1, vec![param(0)], param(0)), - sym::is_compile_time_known => (1, vec![param(0)], tcx.types.bool), + sym::is_val_statically_known => { + (1, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.bool) + } sym::const_eval_select => (4, vec![param(0), param(1), param(2)], param(3)), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 638b2a42a61ab..5544572ff4b96 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -861,7 +861,7 @@ symbols! { intra_doc_pointers, intrinsics, irrefutable_let_patterns, - is_compile_time_known, + is_val_statically_known, isa_attribute, isize, issue, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index e078fdebee5fa..2d1d6e320a2cb 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2490,11 +2490,12 @@ extern "rust-intrinsic" { #[rustc_nounwind] pub fn option_payload_ptr(arg: *const Option) -> *const T; - /// Returns whether the argument is known at compile-time. + /// Returns whether the argument's value is statically known at + /// compile-time. /// /// This is useful when there is a way of writing the code that will /// be *faster* when some variables have known values, but *slower* - /// in the general case: an `if is_compile_time_known(var)` can be used + /// in the general case: an `if is_val_statically_known(var)` can be used /// to select between these two variants. The `if` will be optimized away /// and only the desired branch remains. /// @@ -2503,16 +2504,35 @@ extern "rust-intrinsic" { /// In other words, the following code has *Undefined Behavior*: /// /// ```rust - /// if !is_compile_time_known(0) { unreachable_unchecked(); } + /// if !is_val_statically_known(0) { unreachable_unchecked(); } /// ``` /// - /// Unsafe code may not rely on `is_compile_time_known` returning any + /// This also means that the following code's behavior is unspecified; it + /// may panic, or it may not: + /// + /// ```rust,no_run + /// assert_eq!(is_val_statically_known(0), black_box(is_val_statically_known(0))) + /// ``` + /// + /// Unsafe code may not rely on `is_val_statically_known` returning any /// particular value, ever. However, the compiler will generally make it /// return `true` only if the value of the argument is actually known. - #[rustc_const_unstable(feature = "is_compile_time_known", issue = "none")] + /// + /// When calling this in a `const fn`, both paths must be semantically + /// equivalent, that is, the result of the `true` branch and the `false` + /// branch return the same value *no matter what*. + #[rustc_const_unstable(feature = "is_val_statically_known", issue = "none")] #[rustc_nounwind] #[cfg(not(bootstrap))] - pub fn is_compile_time_known(arg: T) -> bool; + pub fn is_val_statically_known(arg: *const T) -> bool; +} + +// FIXME: Seems using `unstable` here completely ignores `rustc_allow_const_fn_unstable` +// and thus compiling stage0 core doesn't work. +#[rustc_const_stable(feature = "is_val_statically_known", since = "never")] +#[cfg(bootstrap)] +pub const unsafe fn is_val_statically_known(_: *const T) -> bool { + false } // Some functions are defined here because they accidentally got made diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 1befe2ca04021..94831f15d1a81 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -187,7 +187,7 @@ // // Language features: // tidy-alphabetical-start -#![cfg_attr(not(bootstrap), feature(is_compile_time_known))] +#![cfg_attr(not(bootstrap), feature(is_val_statically_known))] #![feature(abi_unadjusted)] #![feature(adt_const_params)] #![feature(allow_internal_unsafe)] diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index dae8e7846bd80..2c12217f31bd6 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2039,11 +2039,10 @@ macro_rules! int_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] - #[rustc_allow_const_fn_unstable(is_compile_time_known)] + #[rustc_allow_const_fn_unstable(is_val_statically_known)] pub const fn pow(self, mut exp: u32) -> Self { - #[cfg(not(bootstrap))] // SAFETY: This path has the same behavior as the other. - if unsafe { intrinsics::is_compile_time_known(self) } + if unsafe { intrinsics::is_val_statically_known(ptr::addr_of!(self)) } && self > 0 && (self & (self - 1) == 0) { @@ -2083,29 +2082,6 @@ macro_rules! int_impl { // needless overflow. acc * base } - - #[cfg(bootstrap)] - { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc = 1; - - while exp > 1 { - if (exp & 1) == 1 { - acc = acc * base; - } - exp /= 2; - base = base * base; - } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base - } } /// Calculates the quotient of Euclidean division of `self` by `rhs`. diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 95dcaf5dd7397..2d8ea1633b975 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -6,6 +6,7 @@ use crate::ascii; use crate::intrinsics; use crate::mem; use crate::ops::{Add, Mul, Sub}; +use crate::ptr; use crate::str::FromStr; // Used because the `?` operator is not allowed in a const context. diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 2ca61c38c628a..cac78ba557841 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1957,7 +1957,7 @@ macro_rules! uint_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] - #[rustc_allow_const_fn_unstable(is_compile_time_known)] + #[rustc_allow_const_fn_unstable(is_val_statically_known)] pub const fn pow(self, mut exp: u32) -> Self { // LLVM now knows that `self` is a constant value, but not a // constant in Rust. This allows us to compute the power used at @@ -1970,9 +1970,10 @@ macro_rules! uint_impl { // get the power of a power of two and the exponent through a `shl` // instruction, but we must add a couple more checks for parity with // our own `pow`. - #[cfg(not(bootstrap))] // SAFETY: This path has the same behavior as the other. - if unsafe { intrinsics::is_compile_time_known(self) } && self.is_power_of_two() { + if unsafe { intrinsics::is_val_statically_known(ptr::addr_of!(self)) } + && self.is_power_of_two() + { let power_used = match self.checked_ilog2() { Some(v) => v, // SAFETY: We just checked this is a power of two. `0` is not a @@ -2010,29 +2011,6 @@ macro_rules! uint_impl { // needless overflow. acc * base } - - #[cfg(bootstrap)] - { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc = 1; - - while exp > 1 { - if (exp & 1) == 1 { - acc = acc * base; - } - exp /= 2; - base = base * base; - } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base - } } /// Performs Euclidean division. diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index 6e77d8a6ae153..f4dfa11553f92 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -5,6 +5,7 @@ use std::iter; use log::trace; +use rand::Rng; use rustc_apfloat::{Float, Round}; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::{ @@ -131,16 +132,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?; } - // The default implementation always returns `false`, but we want - // to return either `true` or `false` at random. - "is_comptile_time_known" => { - let rand = 0; - _ = getrandom::getrandom(&mut [rand]); - - this.write_scalar( - Scalar::from_bool(if (rand % 1) == 1 { true } else { false }), - dest, - )?; + // We want to return either `true` or `false` at random, or else something like + // ``` + // if !is_val_statically_known(0) { unreachable_unchecked(); } + // ``` + // Would not be considered UB, or the other way around (`is_val_statically_known(0)`). + "is_val_statically_known" => { + let [_] = check_arg_count(args)?; + let branch = this.machine.rng.get_mut().gen(); + this.write_scalar(Scalar::from_bool(branch), dest)?; } // Floating-point operations @@ -461,10 +461,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } // Nothing else - _ => span_bug!( - this.cur_span(), - "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" - ), + _ => + span_bug!( + this.cur_span(), + "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" + ), }) } >>>>>>> acbf90a9489 (Make MIRI choose the path randomly and rename the intrinsic) diff --git a/src/tools/miri/tests/pass/intrinsics.rs b/src/tools/miri/tests/pass/intrinsics.rs index 8c6eeab22195c..a8cb1f4430dc7 100644 --- a/src/tools/miri/tests/pass/intrinsics.rs +++ b/src/tools/miri/tests/pass/intrinsics.rs @@ -33,6 +33,12 @@ fn main() { assert_eq!(intrinsics::likely(false), false); assert_eq!(intrinsics::unlikely(true), true); + if unsafe { intrinsics::is_val_statically_known(&0 as _) } { + 0 + } else { + 0 + }; + intrinsics::forget(Bomb); let _v = intrinsics::discriminant_value(&Some(())); From 79016f81be714f7e4d280cd420032d61037e7ae1 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Thu, 3 Aug 2023 14:22:26 +0000 Subject: [PATCH 06/11] Define `llvm.is.constant` for primitives --- compiler/rustc_codegen_llvm/src/context.rs | 17 +++++++++++++---- compiler/rustc_codegen_llvm/src/intrinsic.rs | 7 ++++--- .../rustc_hir_analysis/src/check/intrinsic.rs | 4 +--- library/core/src/intrinsics.rs | 4 ++-- library/core/src/num/int_macros.rs | 2 +- library/core/src/num/mod.rs | 1 - library/core/src/num/uint_macros.rs | 2 +- ...time_known.rs => is_val_statically_known.rs} | 6 +++--- 8 files changed, 25 insertions(+), 18 deletions(-) rename tests/codegen/{is_compile_time_known.rs => is_val_statically_known.rs} (80%) diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index f0723123c14d1..36502f2d2f0b4 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -892,10 +892,19 @@ impl<'ll> CodegenCx<'ll, '_> { ifn!("llvm.lifetime.start.p0i8", fn(t_i64, ptr) -> void); ifn!("llvm.lifetime.end.p0i8", fn(t_i64, ptr) -> void); - // Defining only the `ptr` variant of this overloaded intrinsic means - // we can call this on any type we want (that doesn't ICE), but at the - // slight cost of needing to write `addr_of!` everywhere. - ifn!("llvm.is.constant.ptr", fn(ptr) -> i1); + // FIXME: This is an infinitesimally small portion of the types you can + // pass to this intrinsic, if we can ever lazily register intrinsics we + // should register these when they're used, that way any type can be + // passed. + ifn!("llvm.is.constant.i1", fn(i1) -> i1); + ifn!("llvm.is.constant.i8", fn(t_i8) -> i1); + ifn!("llvm.is.constant.i16", fn(t_i16) -> i1); + ifn!("llvm.is.constant.i32", fn(t_i32) -> i1); + ifn!("llvm.is.constant.i64", fn(t_i64) -> i1); + ifn!("llvm.is.constant.i128", fn(t_i128) -> i1); + ifn!("llvm.is.constant.isize", fn(t_isize) -> i1); + ifn!("llvm.is.constant.f32", fn(t_f32) -> i1); + ifn!("llvm.is.constant.f64", fn(t_f64) -> i1); ifn!("llvm.expect.i1", fn(i1, i1) -> i1); ifn!("llvm.eh.typeid.for", fn(ptr) -> t_i32); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 43ebec15e22ee..1392a94c9e88c 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -119,9 +119,10 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { sym::likely => { self.call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(true)]) } - sym::is_val_statically_known => { - self.call_intrinsic("llvm.is.constant.ptr", &[args[0].immediate()]) - } + sym::is_val_statically_known => self.call_intrinsic( + &format!("llvm.is.constant.{:?}", args[0].layout.llvm_type(self.cx)), + &[args[0].immediate()], + ), sym::unlikely => self .call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(false)]), kw::Try => { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index ad6a2c3794dcd..929c76f8a574c 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -477,9 +477,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { sym::black_box => (1, vec![param(0)], param(0)), - sym::is_val_statically_known => { - (1, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.bool) - } + sym::is_val_statically_known => (1, vec![param(0)], tcx.types.bool), sym::const_eval_select => (4, vec![param(0), param(1), param(2)], param(3)), diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 2d1d6e320a2cb..35991d75c4e4d 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2524,14 +2524,14 @@ extern "rust-intrinsic" { #[rustc_const_unstable(feature = "is_val_statically_known", issue = "none")] #[rustc_nounwind] #[cfg(not(bootstrap))] - pub fn is_val_statically_known(arg: *const T) -> bool; + pub fn is_val_statically_known(arg: T) -> bool; } // FIXME: Seems using `unstable` here completely ignores `rustc_allow_const_fn_unstable` // and thus compiling stage0 core doesn't work. #[rustc_const_stable(feature = "is_val_statically_known", since = "never")] #[cfg(bootstrap)] -pub const unsafe fn is_val_statically_known(_: *const T) -> bool { +pub const unsafe fn is_val_statically_known(_: T) -> bool { false } diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 2c12217f31bd6..b8672668862f3 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2042,7 +2042,7 @@ macro_rules! int_impl { #[rustc_allow_const_fn_unstable(is_val_statically_known)] pub const fn pow(self, mut exp: u32) -> Self { // SAFETY: This path has the same behavior as the other. - if unsafe { intrinsics::is_val_statically_known(ptr::addr_of!(self)) } + if unsafe { intrinsics::is_val_statically_known(self) } && self > 0 && (self & (self - 1) == 0) { diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 2d8ea1633b975..95dcaf5dd7397 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -6,7 +6,6 @@ use crate::ascii; use crate::intrinsics; use crate::mem; use crate::ops::{Add, Mul, Sub}; -use crate::ptr; use crate::str::FromStr; // Used because the `?` operator is not allowed in a const context. diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index cac78ba557841..1e29d426ae54f 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1971,7 +1971,7 @@ macro_rules! uint_impl { // instruction, but we must add a couple more checks for parity with // our own `pow`. // SAFETY: This path has the same behavior as the other. - if unsafe { intrinsics::is_val_statically_known(ptr::addr_of!(self)) } + if unsafe { intrinsics::is_val_statically_known(self) } && self.is_power_of_two() { let power_used = match self.checked_ilog2() { diff --git a/tests/codegen/is_compile_time_known.rs b/tests/codegen/is_val_statically_known.rs similarity index 80% rename from tests/codegen/is_compile_time_known.rs rename to tests/codegen/is_val_statically_known.rs index c3713b94b610f..64035af73c7fa 100644 --- a/tests/codegen/is_compile_time_known.rs +++ b/tests/codegen/is_val_statically_known.rs @@ -1,7 +1,7 @@ // compile-flags: --crate-type=lib #![feature(core_intrinsics)] -use std::intrinsics::is_compile_time_known; +use std::intrinsics::is_val_statically_known; pub struct A(u32); pub enum B { @@ -10,7 +10,7 @@ pub enum B { #[inline] pub fn tuple_struct(a: A) -> i32 { - if unsafe { is_compile_time_known(a) } { 1 } else { 0 } + if unsafe { is_val_statically_known(a) } { 1 } else { 0 } } // CHECK-LABEL: @tuple_struct_true( @@ -29,7 +29,7 @@ pub fn tuple_struct_false(a: A) -> i32 { #[inline] pub fn r#enum(b: B) -> i32 { - if unsafe { is_compile_time_known(b) } { 3 } else { 2 } + if unsafe { is_val_statically_known(b) } { 3 } else { 2 } } // CHECK-LABEL: @enum_true( From 4f31013a39b0aec2b8c49c41c9a53539d6c0ab22 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Tue, 8 Aug 2023 22:50:28 +0000 Subject: [PATCH 07/11] Update MIRI comment and fix test in stage2 --- .../src/const_eval/machine.rs | 9 +++--- compiler/rustc_hir_analysis/messages.ftl | 2 -- tests/codegen/is_val_statically_known.rs | 28 +++++++++---------- tests/codegen/pow_of_two.rs | 2 +- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 0f510d8fd7117..00c95dae604ff 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -542,11 +542,10 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, )?; } } - // The name of this intrinsic is actually misleading. It actually - // represents whether the value is known to the optimizer (LLVM). - // The value of `arg`, while known to the machine, should be - // considered unknown to the optimizer as we haven't gotten to the - // codegen stage or ran any sort of optimizations yet. + // The intrinsic represents whether the value is known to the optimizer (LLVM). + // We're not doing any optimizations here, so there is no optimizer that could know the value. + // (We know the value here in the machine of course, but this is the runtime of that code, + // not the optimization stage.) sym::is_val_statically_known => ecx.write_scalar(Scalar::from_bool(false), dest)?, _ => { throw_unsup_format!( diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 63296a31e44c0..597cae6ff33ca 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -102,8 +102,6 @@ hir_analysis_invalid_union_field = hir_analysis_invalid_union_field_sugg = wrap the field type in `ManuallyDrop<...>` -hir_analysis_is_constant_zst = parameter for `is_constant` cannot be zero-sized - hir_analysis_late_bound_const_in_apit = `impl Trait` can only mention const parameters from an fn or impl .label = const parameter declared here diff --git a/tests/codegen/is_val_statically_known.rs b/tests/codegen/is_val_statically_known.rs index 64035af73c7fa..4d378b833f0d2 100644 --- a/tests/codegen/is_val_statically_known.rs +++ b/tests/codegen/is_val_statically_known.rs @@ -9,39 +9,39 @@ pub enum B { } #[inline] -pub fn tuple_struct(a: A) -> i32 { +pub fn _u32(a: u32) -> i32 { if unsafe { is_val_statically_known(a) } { 1 } else { 0 } } -// CHECK-LABEL: @tuple_struct_true( +// CHECK-LABEL: @_u32_true( #[no_mangle] -pub fn tuple_struct_true() -> i32 { +pub fn _u32_true() -> i32 { // CHECK: ret i32 1 - tuple_struct(A(1)) + _u32(1) } -// CHECK-LABEL: @tuple_struct_false( +// CHECK-LABEL: @_u32_false( #[no_mangle] -pub fn tuple_struct_false(a: A) -> i32 { +pub fn _u32_false(a: u32) -> i32 { // CHECK: ret i32 0 - tuple_struct(a) + _u32(a) } #[inline] -pub fn r#enum(b: B) -> i32 { +pub fn _bool(b: bool) -> i32 { if unsafe { is_val_statically_known(b) } { 3 } else { 2 } } -// CHECK-LABEL: @enum_true( +// CHECK-LABEL: @_bool_true( #[no_mangle] -pub fn enum_true() -> i32 { +pub fn _bool_true() -> i32 { // CHECK: ret i32 3 - r#enum(B::Ye(2)) + _bool(true) } -// CHECK-LABEL: @enum_false( +// CHECK-LABEL: @_bool_false( #[no_mangle] -pub fn enum_false(b: B) -> i32 { +pub fn _bool_false(b: bool) -> i32 { // CHECK: ret i32 2 - r#enum(b) + _bool(b) } diff --git a/tests/codegen/pow_of_two.rs b/tests/codegen/pow_of_two.rs index db19b71fe4f34..7fe0387561060 100644 --- a/tests/codegen/pow_of_two.rs +++ b/tests/codegen/pow_of_two.rs @@ -1,6 +1,5 @@ // compile-flags: --crate-type=lib -// CHECK: @b = unnamed_addr alias i64 (i32), ptr @a // CHECK-LABEL: @a( #[no_mangle] pub fn a(exp: u32) -> u64 { @@ -11,6 +10,7 @@ pub fn a(exp: u32) -> u64 { 2u64.pow(exp) } +// This is sometimes an alias to `a`, but this isn't guaranteed #[no_mangle] pub fn b(exp: u32) -> i64 { 2i64.pow(exp) From cf9406f1fc513393f0dd9f9b6d119eeb99e2bde0 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Sun, 20 Aug 2023 12:43:15 +0000 Subject: [PATCH 08/11] Add const eval test --- src/tools/miri/src/shims/intrinsics/mod.rs | 11 +++++------ src/tools/miri/tests/pass/intrinsics.rs | 19 ++++++++++++++----- tests/codegen/is_val_statically_known.rs | 5 ++++- tests/codegen/pow_of_two.rs | 9 +++++++-- tests/ui/consts/is_val_statically_known.rs | 14 ++++++++++++++ 5 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 tests/ui/consts/is_val_statically_known.rs diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index f4dfa11553f92..6b984737bb515 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -139,7 +139,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Would not be considered UB, or the other way around (`is_val_statically_known(0)`). "is_val_statically_known" => { let [_] = check_arg_count(args)?; - let branch = this.machine.rng.get_mut().gen(); + let branch: bool = this.machine.rng.get_mut().gen(); this.write_scalar(Scalar::from_bool(branch), dest)?; } @@ -461,11 +461,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } // Nothing else - _ => - span_bug!( - this.cur_span(), - "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" - ), + _ => span_bug!( + this.cur_span(), + "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" + ), }) } >>>>>>> acbf90a9489 (Make MIRI choose the path randomly and rename the intrinsic) diff --git a/src/tools/miri/tests/pass/intrinsics.rs b/src/tools/miri/tests/pass/intrinsics.rs index a8cb1f4430dc7..8e46bd7ad48fb 100644 --- a/src/tools/miri/tests/pass/intrinsics.rs +++ b/src/tools/miri/tests/pass/intrinsics.rs @@ -33,11 +33,20 @@ fn main() { assert_eq!(intrinsics::likely(false), false); assert_eq!(intrinsics::unlikely(true), true); - if unsafe { intrinsics::is_val_statically_known(&0 as _) } { - 0 - } else { - 0 - }; + let mut saw_true = false; + let mut saw_false = false; + + for _ in 0..50 { + if unsafe { intrinsics::is_val_statically_known(0) } { + saw_true = true; + } else { + saw_false = true; + } + } + assert!( + saw_true && saw_false, + "`is_val_statically_known` failed to return both true and false. Congrats, you won the lottery!" + ); intrinsics::forget(Bomb); diff --git a/tests/codegen/is_val_statically_known.rs b/tests/codegen/is_val_statically_known.rs index 4d378b833f0d2..4dcab7442356b 100644 --- a/tests/codegen/is_val_statically_known.rs +++ b/tests/codegen/is_val_statically_known.rs @@ -1,4 +1,7 @@ -// compile-flags: --crate-type=lib +// #[cfg(bootstrap)] +// ignore-stage1 +// compile-flags: --crate-type=lib -Zmerge-functions=disabled + #![feature(core_intrinsics)] use std::intrinsics::is_val_statically_known; diff --git a/tests/codegen/pow_of_two.rs b/tests/codegen/pow_of_two.rs index 7fe0387561060..3bce5535c66ec 100644 --- a/tests/codegen/pow_of_two.rs +++ b/tests/codegen/pow_of_two.rs @@ -1,4 +1,6 @@ -// compile-flags: --crate-type=lib +// #[cfg(bootstrap)] +// ignore-stage1 +// compile-flags: --crate-type=lib -Zmerge-functions=disabled // CHECK-LABEL: @a( #[no_mangle] @@ -10,9 +12,12 @@ pub fn a(exp: u32) -> u64 { 2u64.pow(exp) } -// This is sometimes an alias to `a`, but this isn't guaranteed #[no_mangle] pub fn b(exp: u32) -> i64 { + // CHECK: %[[R:.+]] = and i32 %exp, 63 + // CHECK: %[[R:.+]] = zext i32 %[[R:.+]] to i64 + // CHECK: %[[R:.+]] = shl nuw i64 %[[R:.+]].i, %[[R:.+]] + // CHECK: ret i64 %[[R:.+]] 2i64.pow(exp) } diff --git a/tests/ui/consts/is_val_statically_known.rs b/tests/ui/consts/is_val_statically_known.rs new file mode 100644 index 0000000000000..9098427de1cbd --- /dev/null +++ b/tests/ui/consts/is_val_statically_known.rs @@ -0,0 +1,14 @@ +// run-pass + +#![feature(core_intrinsics)] +#![feature(is_val_statically_known)] + +use std::intrinsics::is_val_statically_known; + +const CONST_TEST: bool = unsafe { is_val_statically_known(0) }; + +fn main() { + if CONST_TEST { + unreachable!("guaranteed to return false during const eval"); + } +} From be3538128c00a54b8a013d4e73652baa412f0de0 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Sun, 20 Aug 2023 12:50:57 +0000 Subject: [PATCH 09/11] Clarify that both branches must have the same side effects --- library/core/src/intrinsics.rs | 3 +- src/tools/miri/src/shims/intrinsics/mod.rs | 55 ---------------------- 2 files changed, 2 insertions(+), 56 deletions(-) diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 35991d75c4e4d..ffa0e16351f13 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2520,7 +2520,8 @@ extern "rust-intrinsic" { /// /// When calling this in a `const fn`, both paths must be semantically /// equivalent, that is, the result of the `true` branch and the `false` - /// branch return the same value *no matter what*. + /// branch must return the same value and have the same side-effects *no + /// matter what*. #[rustc_const_unstable(feature = "is_val_statically_known", issue = "none")] #[rustc_nounwind] #[cfg(not(bootstrap))] diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index 6b984737bb515..0da6c22326a27 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -413,59 +413,4 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(()) } -<<<<<<< HEAD -======= - - fn float_to_int_unchecked( - &self, - f: F, - dest_ty: Ty<'tcx>, - ) -> InterpResult<'tcx, Scalar> - where - F: Float + Into>, - { - let this = self.eval_context_ref(); - - // Step 1: cut off the fractional part of `f`. The result of this is - // guaranteed to be precisely representable in IEEE floats. - let f = f.round_to_integral(Round::TowardZero).value; - - // Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step. - Ok(match dest_ty.kind() { - // Unsigned - ty::Uint(t) => { - let size = Integer::from_uint_ty(this, *t).size(); - let res = f.to_u128(size.bits_usize()); - if res.status.is_empty() { - // No status flags means there was no further rounding or other loss of precision. - Scalar::from_uint(res.value, size) - } else { - // `f` was not representable in this integer type. - throw_ub_format!( - "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", - ); - } - } - // Signed - ty::Int(t) => { - let size = Integer::from_int_ty(this, *t).size(); - let res = f.to_i128(size.bits_usize()); - if res.status.is_empty() { - // No status flags means there was no further rounding or other loss of precision. - Scalar::from_int(res.value, size) - } else { - // `f` was not representable in this integer type. - throw_ub_format!( - "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", - ); - } - } - // Nothing else - _ => span_bug!( - this.cur_span(), - "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" - ), - }) - } ->>>>>>> acbf90a9489 (Make MIRI choose the path randomly and rename the intrinsic) } From c1adc30bc775b89495939e9e84241dc33c95c8f0 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 5 Sep 2023 10:17:37 +0200 Subject: [PATCH 10/11] guaranteed non guarantee Co-authored-by: Ralf Jung --- tests/ui/consts/is_val_statically_known.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ui/consts/is_val_statically_known.rs b/tests/ui/consts/is_val_statically_known.rs index 9098427de1cbd..b0565842eb4e2 100644 --- a/tests/ui/consts/is_val_statically_known.rs +++ b/tests/ui/consts/is_val_statically_known.rs @@ -9,6 +9,7 @@ const CONST_TEST: bool = unsafe { is_val_statically_known(0) }; fn main() { if CONST_TEST { - unreachable!("guaranteed to return false during const eval"); + unreachable!("currently expected to return false during const eval"); + // but note that this is not a guarantee! } } From 87b5cea9a57b08aa4144d466f8821bacfa6a0ded Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Tue, 5 Sep 2023 12:57:38 +0000 Subject: [PATCH 11/11] use immediate type instead --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 2 +- library/core/src/intrinsics.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 1392a94c9e88c..033680f9e140a 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -120,7 +120,7 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { self.call_intrinsic("llvm.expect.i1", &[args[0].immediate(), self.const_bool(true)]) } sym::is_val_statically_known => self.call_intrinsic( - &format!("llvm.is.constant.{:?}", args[0].layout.llvm_type(self.cx)), + &format!("llvm.is.constant.{:?}", args[0].layout.immediate_llvm_type(self.cx)), &[args[0].immediate()], ), sym::unlikely => self diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index ffa0e16351f13..653eee91c124a 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2532,7 +2532,8 @@ extern "rust-intrinsic" { // and thus compiling stage0 core doesn't work. #[rustc_const_stable(feature = "is_val_statically_known", since = "never")] #[cfg(bootstrap)] -pub const unsafe fn is_val_statically_known(_: T) -> bool { +pub const unsafe fn is_val_statically_known(t: T) -> bool { + mem::forget(t); false }