Auto merge of #85828 - scottmcm:raw-eq, r=oli-obk
Stop generating `alloca`s & `memcmp` for simple short array equality Example: ```rust pub fn demo(x: [u16; 6], y: [u16; 6]) -> bool { x == y } ``` Before: ```llvm define zeroext i1 `@_ZN10playground4demo17h48537f7eac23948fE(i96` %0, i96 %1) unnamed_addr #0 { start: %y = alloca [6 x i16], align 8 %x = alloca [6 x i16], align 8 %.0..sroa_cast = bitcast [6 x i16]* %x to i96* store i96 %0, i96* %.0..sroa_cast, align 8 %.0..sroa_cast3 = bitcast [6 x i16]* %y to i96* store i96 %1, i96* %.0..sroa_cast3, align 8 %_11.i.i.i = bitcast [6 x i16]* %x to i8* %_14.i.i.i = bitcast [6 x i16]* %y to i8* %bcmp.i.i.i = call i32 `@bcmp(i8*` nonnull dereferenceable(12) %_11.i.i.i, i8* nonnull dereferenceable(12) %_14.i.i.i, i64 12) #2, !alias.scope !2 %2 = icmp eq i32 %bcmp.i.i.i, 0 ret i1 %2 } ``` ```x86 playground::demo: # `@playground::demo` sub rsp, 32 mov qword ptr [rsp], rdi mov dword ptr [rsp + 8], esi mov qword ptr [rsp + 16], rdx mov dword ptr [rsp + 24], ecx xor rdi, rdx xor esi, ecx or rsi, rdi sete al add rsp, 32 ret ``` After: ```llvm define zeroext i1 `@_ZN4mini4demo17h7a8994aaa314c981E(i96` %0, i96 %1) unnamed_addr #0 { start: %2 = icmp eq i96 %0, %1 ret i1 %2 } ``` ```x86 _ZN4mini4demo17h7a8994aaa314c981E: xor rcx, r8 xor edx, r9d or rdx, rcx sete al ret ```
This commit is contained in:
commit
ee86f96ba1
15 changed files with 410 additions and 114 deletions
|
@ -1115,6 +1115,40 @@ pub(crate) fn codegen_intrinsic_call<'tcx>(
|
|||
);
|
||||
ret.write_cvalue(fx, CValue::by_val(res, ret.layout()));
|
||||
};
|
||||
|
||||
raw_eq, <T>(v lhs_ref, v rhs_ref) {
|
||||
fn type_by_size(size: Size) -> Option<Type> {
|
||||
Type::int(size.bits().try_into().ok()?)
|
||||
}
|
||||
|
||||
let size = fx.layout_of(T).layout.size;
|
||||
let is_eq_value =
|
||||
if size == Size::ZERO {
|
||||
// No bytes means they're trivially equal
|
||||
fx.bcx.ins().iconst(types::I8, 1)
|
||||
} else if let Some(clty) = type_by_size(size) {
|
||||
// Can't use `trusted` for these loads; they could be unaligned.
|
||||
let mut flags = MemFlags::new();
|
||||
flags.set_notrap();
|
||||
let lhs_val = fx.bcx.ins().load(clty, flags, lhs_ref, 0);
|
||||
let rhs_val = fx.bcx.ins().load(clty, flags, rhs_ref, 0);
|
||||
let eq = fx.bcx.ins().icmp(IntCC::Equal, lhs_val, rhs_val);
|
||||
fx.bcx.ins().bint(types::I8, eq)
|
||||
} else {
|
||||
// Just call `memcmp` (like slices do in core) when the
|
||||
// size is too large or it's not a power-of-two.
|
||||
let ptr_ty = pointer_ty(fx.tcx);
|
||||
let signed_bytes = i64::try_from(size.bytes()).unwrap();
|
||||
let bytes_val = fx.bcx.ins().iconst(ptr_ty, signed_bytes);
|
||||
let params = vec![AbiParam::new(ptr_ty); 3];
|
||||
let returns = vec![AbiParam::new(types::I32)];
|
||||
let args = &[lhs_ref, rhs_ref, bytes_val];
|
||||
let cmp = fx.lib_call("memcmp", params, returns, args)[0];
|
||||
let eq = fx.bcx.ins().icmp_imm(IntCC::Equal, cmp, 0);
|
||||
fx.bcx.ins().bint(types::I8, eq)
|
||||
};
|
||||
ret.write_cvalue(fx, CValue::by_val(is_eq_value, ret.layout()));
|
||||
};
|
||||
}
|
||||
|
||||
if let Some((_, dest)) = destination {
|
||||
|
|
|
@ -453,6 +453,10 @@ impl<'tcx> CPlace<'tcx> {
|
|||
ptr.store(fx, data, MemFlags::trusted());
|
||||
ptr.load(fx, dst_ty, MemFlags::trusted())
|
||||
}
|
||||
|
||||
// `CValue`s should never contain SSA-only types, so if you ended
|
||||
// up here having seen an error like `B1 -> I8`, then before
|
||||
// calling `write_cvalue` you need to add a `bint` instruction.
|
||||
_ => unreachable!("write_cvalue_transmute: {:?} -> {:?}", src_ty, dst_ty),
|
||||
};
|
||||
//fx.bcx.set_val_label(data, cranelift_codegen::ir::ValueLabel::new(var.index()));
|
||||
|
|
|
@ -500,6 +500,7 @@ impl CodegenCx<'b, 'tcx> {
|
|||
let t_i32 = self.type_i32();
|
||||
let t_i64 = self.type_i64();
|
||||
let t_i128 = self.type_i128();
|
||||
let t_isize = self.type_isize();
|
||||
let t_f32 = self.type_f32();
|
||||
let t_f64 = self.type_f64();
|
||||
|
||||
|
@ -712,6 +713,10 @@ impl CodegenCx<'b, 'tcx> {
|
|||
ifn!("llvm.assume", fn(i1) -> void);
|
||||
ifn!("llvm.prefetch", fn(i8p, t_i32, t_i32, t_i32) -> void);
|
||||
|
||||
// This isn't an "LLVM intrinsic", but LLVM's optimization passes
|
||||
// recognize it like one and we assume it exists in `core::slice::cmp`
|
||||
ifn!("memcmp", fn(i8p, i8p, t_isize) -> t_i32);
|
||||
|
||||
// variadic intrinsics
|
||||
ifn!("llvm.va_start", fn(i8p) -> void);
|
||||
ifn!("llvm.va_end", fn(i8p) -> void);
|
||||
|
|
|
@ -296,6 +296,44 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
sym::raw_eq => {
|
||||
use abi::Abi::*;
|
||||
let tp_ty = substs.type_at(0);
|
||||
let layout = self.layout_of(tp_ty).layout;
|
||||
let use_integer_compare = match layout.abi {
|
||||
Scalar(_) | ScalarPair(_, _) => true,
|
||||
Uninhabited | Vector { .. } => false,
|
||||
Aggregate { .. } => {
|
||||
// For rusty ABIs, small aggregates are actually passed
|
||||
// as `RegKind::Integer` (see `FnAbi::adjust_for_abi`),
|
||||
// so we re-use that same threshold here.
|
||||
layout.size <= self.data_layout().pointer_size * 2
|
||||
}
|
||||
};
|
||||
|
||||
let a = args[0].immediate();
|
||||
let b = args[1].immediate();
|
||||
if layout.size.bytes() == 0 {
|
||||
self.const_bool(true)
|
||||
} else if use_integer_compare {
|
||||
let integer_ty = self.type_ix(layout.size.bits());
|
||||
let ptr_ty = self.type_ptr_to(integer_ty);
|
||||
let a_ptr = self.bitcast(a, ptr_ty);
|
||||
let a_val = self.load(a_ptr, layout.align.abi);
|
||||
let b_ptr = self.bitcast(b, ptr_ty);
|
||||
let b_val = self.load(b_ptr, layout.align.abi);
|
||||
self.icmp(IntPredicate::IntEQ, a_val, b_val)
|
||||
} else {
|
||||
let i8p_ty = self.type_i8p();
|
||||
let a_ptr = self.bitcast(a, i8p_ty);
|
||||
let b_ptr = self.bitcast(b, i8p_ty);
|
||||
let n = self.const_usize(layout.size.bytes());
|
||||
let llfn = self.get_intrinsic("memcmp");
|
||||
let cmp = self.call(llfn, &[a_ptr, b_ptr, n], None);
|
||||
self.icmp(IntPredicate::IntEQ, cmp, self.const_i32(0))
|
||||
}
|
||||
}
|
||||
|
||||
_ if name_str.starts_with("simd_") => {
|
||||
match generic_simd_intrinsic(self, name, callee_ty, args, ret_ty, llret_ty, span) {
|
||||
Ok(llval) => llval,
|
||||
|
|
|
@ -472,6 +472,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
throw_ub_format!("`assume` intrinsic called with `false`");
|
||||
}
|
||||
}
|
||||
sym::raw_eq => {
|
||||
let result = self.raw_eq_intrinsic(&args[0], &args[1])?;
|
||||
self.write_scalar(result, dest)?;
|
||||
}
|
||||
_ => return Ok(false),
|
||||
}
|
||||
|
||||
|
@ -559,4 +563,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
|
||||
self.memory.copy(src, align, dst, align, size, nonoverlapping)
|
||||
}
|
||||
|
||||
pub(crate) fn raw_eq_intrinsic(
|
||||
&mut self,
|
||||
lhs: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::PointerTag>,
|
||||
rhs: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::PointerTag>,
|
||||
) -> InterpResult<'tcx, Scalar<M::PointerTag>> {
|
||||
let layout = self.layout_of(lhs.layout.ty.builtin_deref(true).unwrap().ty)?;
|
||||
assert!(!layout.is_unsized());
|
||||
|
||||
let lhs = self.read_scalar(lhs)?.check_init()?;
|
||||
let rhs = self.read_scalar(rhs)?.check_init()?;
|
||||
let lhs_bytes = self.memory.read_bytes(lhs, layout.size)?;
|
||||
let rhs_bytes = self.memory.read_bytes(rhs, layout.size)?;
|
||||
Ok(Scalar::from_bool(lhs_bytes == rhs_bytes))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -934,6 +934,7 @@ symbols! {
|
|||
quote,
|
||||
range_inclusive_new,
|
||||
raw_dylib,
|
||||
raw_eq,
|
||||
raw_identifiers,
|
||||
raw_ref_op,
|
||||
re_rebalance_coherence,
|
||||
|
|
|
@ -380,6 +380,13 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
|
|||
|
||||
sym::nontemporal_store => (1, vec![tcx.mk_mut_ptr(param(0)), param(0)], tcx.mk_unit()),
|
||||
|
||||
sym::raw_eq => {
|
||||
let br = ty::BoundRegion { var: ty::BoundVar::from_u32(0), kind: ty::BrAnon(0) };
|
||||
let param_ty =
|
||||
tcx.mk_imm_ref(tcx.mk_region(ty::ReLateBound(ty::INNERMOST, br)), param(0));
|
||||
(1, vec![param_ty; 2], tcx.types.bool)
|
||||
}
|
||||
|
||||
other => {
|
||||
tcx.sess.emit_err(UnrecognizedIntrinsicFunction { span: it.span, name: other });
|
||||
return;
|
||||
|
|
160
library/core/src/array/equality.rs
Normal file
160
library/core/src/array/equality.rs
Normal file
|
@ -0,0 +1,160 @@
|
|||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[B; N]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[B; N]) -> bool {
|
||||
SpecArrayEq::spec_eq(self, other)
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[B; N]) -> bool {
|
||||
SpecArrayEq::spec_ne(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[B]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[B]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[B]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
|
||||
where
|
||||
B: PartialEq<A>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[A; N]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[A; N]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<&[B]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &&[B]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &&[B]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[A; N]> for &[B]
|
||||
where
|
||||
B: PartialEq<A>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[A; N]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[A; N]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<&mut [B]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &&mut [B]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &&mut [B]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[A; N]> for &mut [B]
|
||||
where
|
||||
B: PartialEq<A>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[A; N]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[A; N]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: some less important impls are omitted to reduce code bloat
|
||||
// __impl_slice_eq2! { [A; $N], &'b [B; $N] }
|
||||
// __impl_slice_eq2! { [A; $N], &'b mut [B; $N] }
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<T: Eq, const N: usize> Eq for [T; N] {}
|
||||
|
||||
trait SpecArrayEq<Other, const N: usize>: Sized {
|
||||
fn spec_eq(a: &[Self; N], b: &[Other; N]) -> bool;
|
||||
fn spec_ne(a: &[Self; N], b: &[Other; N]) -> bool;
|
||||
}
|
||||
|
||||
impl<T: PartialEq<Other>, Other, const N: usize> SpecArrayEq<Other, N> for T {
|
||||
default fn spec_eq(a: &[Self; N], b: &[Other; N]) -> bool {
|
||||
a[..] == b[..]
|
||||
}
|
||||
default fn spec_ne(a: &[Self; N], b: &[Other; N]) -> bool {
|
||||
a[..] != b[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq<U> + IsRawEqComparable<U>, U, const N: usize> SpecArrayEq<U, N> for T {
|
||||
#[cfg(bootstrap)]
|
||||
fn spec_eq(a: &[T; N], b: &[U; N]) -> bool {
|
||||
a[..] == b[..]
|
||||
}
|
||||
#[cfg(not(bootstrap))]
|
||||
fn spec_eq(a: &[T; N], b: &[U; N]) -> bool {
|
||||
// SAFETY: This is why `IsRawEqComparable` is an `unsafe trait`.
|
||||
unsafe {
|
||||
let b = &*b.as_ptr().cast::<[T; N]>();
|
||||
crate::intrinsics::raw_eq(a, b)
|
||||
}
|
||||
}
|
||||
fn spec_ne(a: &[T; N], b: &[U; N]) -> bool {
|
||||
!Self::spec_eq(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
/// `U` exists on here mostly because `min_specialization` didn't let me
|
||||
/// repeat the `T` type parameter in the above specialization, so instead
|
||||
/// the `T == U` constraint comes from the impls on this.
|
||||
/// # Safety
|
||||
/// - Neither `Self` nor `U` has any padding.
|
||||
/// - `Self` and `U` have the same layout.
|
||||
/// - `Self: PartialEq<U>` is byte-wise (this means no floats, among other things)
|
||||
#[rustc_specialization_trait]
|
||||
unsafe trait IsRawEqComparable<U> {}
|
||||
|
||||
macro_rules! is_raw_comparable {
|
||||
($($t:ty),+) => {$(
|
||||
unsafe impl IsRawEqComparable<$t> for $t {}
|
||||
)+};
|
||||
}
|
||||
is_raw_comparable!(bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
|
|
@ -14,6 +14,7 @@ use crate::mem::{self, MaybeUninit};
|
|||
use crate::ops::{Index, IndexMut};
|
||||
use crate::slice::{Iter, IterMut};
|
||||
|
||||
mod equality;
|
||||
mod iter;
|
||||
|
||||
#[stable(feature = "array_value_iter", since = "1.51.0")]
|
||||
|
@ -230,118 +231,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[B; N]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[B; N]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[B; N]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[B]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[B]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[B]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
|
||||
where
|
||||
B: PartialEq<A>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[A; N]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[A; N]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<&[B]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &&[B]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &&[B]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[A; N]> for &[B]
|
||||
where
|
||||
B: PartialEq<A>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[A; N]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[A; N]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<&mut [B]> for [A; N]
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &&mut [B]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &&mut [B]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<A, B, const N: usize> PartialEq<[A; N]> for &mut [B]
|
||||
where
|
||||
B: PartialEq<A>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &[A; N]) -> bool {
|
||||
self[..] == other[..]
|
||||
}
|
||||
#[inline]
|
||||
fn ne(&self, other: &[A; N]) -> bool {
|
||||
self[..] != other[..]
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: some less important impls are omitted to reduce code bloat
|
||||
// __impl_slice_eq2! { [A; $N], &'b [B; $N] }
|
||||
// __impl_slice_eq2! { [A; $N], &'b mut [B; $N] }
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<T: Eq, const N: usize> Eq for [T; N] {}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<T: PartialOrd, const N: usize> PartialOrd for [T; N] {
|
||||
#[inline]
|
||||
|
|
|
@ -1913,6 +1913,26 @@ extern "rust-intrinsic" {
|
|||
/// Allocate at compile time. Should not be called at runtime.
|
||||
#[rustc_const_unstable(feature = "const_heap", issue = "79597")]
|
||||
pub fn const_allocate(size: usize, align: usize) -> *mut u8;
|
||||
|
||||
/// Determines whether the raw bytes of the two values are equal.
|
||||
///
|
||||
/// The is particularly handy for arrays, since it allows things like just
|
||||
/// comparing `i96`s instead of forcing `alloca`s for `[6 x i16]`.
|
||||
///
|
||||
/// Above some backend-decided threshold this will emit calls to `memcmp`,
|
||||
/// like slice equality does, instead of causing massive code size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It's UB to call this if any of the *bytes* in `*a` or `*b` are uninitialized.
|
||||
/// Note that this is a stricter criterion than just the *values* being
|
||||
/// fully-initialized: if `T` has padding, it's UB to call this intrinsic.
|
||||
///
|
||||
/// (The implementation is allowed to branch on the results of comparisons,
|
||||
/// which is UB if any of their inputs are `undef`.)
|
||||
#[cfg(not(bootstrap))]
|
||||
#[rustc_const_unstable(feature = "const_intrinsic_raw_eq", issue = "none")]
|
||||
pub fn raw_eq<T>(a: &T, b: &T) -> bool;
|
||||
}
|
||||
|
||||
// Some functions are defined here because they accidentally got made
|
||||
|
|
57
src/test/codegen/array-equality.rs
Normal file
57
src/test/codegen/array-equality.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
// compile-flags: -O
|
||||
// only-x86_64
|
||||
|
||||
#![crate_type = "lib"]
|
||||
|
||||
// CHECK-LABEL: @array_eq_value
|
||||
#[no_mangle]
|
||||
pub fn array_eq_value(a: [u16; 6], b: [u16; 6]) -> bool {
|
||||
// CHECK-NEXT: start:
|
||||
// CHECK-NEXT: %2 = icmp eq i96 %0, %1
|
||||
// CHECK-NEXT: ret i1 %2
|
||||
a == b
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @array_eq_ref
|
||||
#[no_mangle]
|
||||
pub fn array_eq_ref(a: &[u16; 6], b: &[u16; 6]) -> bool {
|
||||
// CHECK: start:
|
||||
// CHECK: load i96, i96* %{{.+}}, align 2
|
||||
// CHECK: load i96, i96* %{{.+}}, align 2
|
||||
// CHECK: icmp eq i96
|
||||
// CHECK-NEXT: ret
|
||||
a == b
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @array_eq_value_still_passed_by_pointer
|
||||
#[no_mangle]
|
||||
pub fn array_eq_value_still_passed_by_pointer(a: [u16; 9], b: [u16; 9]) -> bool {
|
||||
// CHECK-NEXT: start:
|
||||
// CHECK-NEXT: bitcast
|
||||
// CHECK-NEXT: bitcast
|
||||
// CHECK-NEXT: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(i8* nonnull dereferenceable(18) %{{.+}}, i8* nonnull dereferenceable(18) %{{.+}}, i64 18)
|
||||
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0
|
||||
// CHECK-NEXT: ret i1 %[[EQ]]
|
||||
a == b
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @array_eq_long
|
||||
#[no_mangle]
|
||||
pub fn array_eq_long(a: &[u16; 1234], b: &[u16; 1234]) -> bool {
|
||||
// CHECK-NEXT: start:
|
||||
// CHECK-NEXT: bitcast
|
||||
// CHECK-NEXT: bitcast
|
||||
// CHECK-NEXT: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(i8* nonnull dereferenceable(2468) %{{.+}}, i8* nonnull dereferenceable(2468) %{{.+}}, i64 2468)
|
||||
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0
|
||||
// CHECK-NEXT: ret i1 %[[EQ]]
|
||||
a == b
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @array_eq_zero(i128 %0)
|
||||
#[no_mangle]
|
||||
pub fn array_eq_zero(x: [u16; 8]) -> bool {
|
||||
// CHECK-NEXT: start:
|
||||
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i128 %0, 0
|
||||
// CHECK-NEXT: ret i1 %[[EQ]]
|
||||
x == [0; 8]
|
||||
}
|
|
@ -2,15 +2,30 @@
|
|||
|
||||
#![crate_type = "lib"]
|
||||
|
||||
// #71602: check that slice equality just generates a single bcmp
|
||||
// #71602 reported a simple array comparison just generating a loop.
|
||||
// This was originally fixed by ensuring it generates a single bcmp,
|
||||
// but we now generate it as a load instead. `is_zero_slice` was
|
||||
// tweaked to still test the case of comparison against a slice,
|
||||
// and `is_zero_array` tests the new array-specific behaviour.
|
||||
|
||||
// CHECK-LABEL: @is_zero_slice
|
||||
#[no_mangle]
|
||||
pub fn is_zero_slice(data: &[u8; 4]) -> bool {
|
||||
// CHECK: start:
|
||||
// CHECK: :
|
||||
// CHECK-NEXT: %{{.+}} = getelementptr {{.+}}
|
||||
// CHECK-NEXT: %[[BCMP:.+]] = tail call i32 @{{bcmp|memcmp}}({{.+}})
|
||||
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[BCMP]], 0
|
||||
// CHECK-NEXT: ret i1 %[[EQ]]
|
||||
&data[..] == [0; 4]
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @is_zero_array
|
||||
#[no_mangle]
|
||||
pub fn is_zero_array(data: &[u8; 4]) -> bool {
|
||||
// CHECK: start:
|
||||
// CHECK-NEXT: %[[PTR:.+]] = bitcast [4 x i8]* {{.+}} to i32*
|
||||
// CHECK-NEXT: %[[LOAD:.+]] = load i32, i32* %[[PTR]], align 1
|
||||
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[LOAD]], 0
|
||||
// CHECK-NEXT: ret i1 %[[EQ]]
|
||||
*data == [0; 4]
|
||||
}
|
||||
|
|
11
src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.rs
Normal file
11
src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#![feature(core_intrinsics)]
|
||||
#![feature(const_intrinsic_raw_eq)]
|
||||
#![deny(const_err)]
|
||||
|
||||
const BAD_RAW_EQ_CALL: bool = unsafe {
|
||||
std::intrinsics::raw_eq(&(1_u8, 2_u16), &(1_u8, 2_u16))
|
||||
//~^ ERROR evaluation of constant value failed
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
error[E0080]: evaluation of constant value failed
|
||||
--> $DIR/intrinsic-raw_eq-const-padding.rs:6:5
|
||||
|
|
||||
LL | std::intrinsics::raw_eq(&(1_u8, 2_u16), &(1_u8, 2_u16))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading 4 bytes of memory starting at alloc2, but 1 byte is uninitialized starting at alloc2+0x1, and this operation requires initialized memory
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0080`.
|
27
src/test/ui/intrinsics/intrinsic-raw_eq-const.rs
Normal file
27
src/test/ui/intrinsics/intrinsic-raw_eq-const.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// run-pass
|
||||
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(const_intrinsic_raw_eq)]
|
||||
#![deny(const_err)]
|
||||
|
||||
pub fn main() {
|
||||
use std::intrinsics::raw_eq;
|
||||
|
||||
const RAW_EQ_I32_TRUE: bool = unsafe { raw_eq(&42_i32, &42) };
|
||||
assert!(RAW_EQ_I32_TRUE);
|
||||
|
||||
const RAW_EQ_I32_FALSE: bool = unsafe { raw_eq(&4_i32, &2) };
|
||||
assert!(!RAW_EQ_I32_FALSE);
|
||||
|
||||
const RAW_EQ_CHAR_TRUE: bool = unsafe { raw_eq(&'a', &'a') };
|
||||
assert!(RAW_EQ_CHAR_TRUE);
|
||||
|
||||
const RAW_EQ_CHAR_FALSE: bool = unsafe { raw_eq(&'a', &'A') };
|
||||
assert!(!RAW_EQ_CHAR_FALSE);
|
||||
|
||||
const RAW_EQ_ARRAY_TRUE: bool = unsafe { raw_eq(&[13_u8, 42], &[13, 42]) };
|
||||
assert!(RAW_EQ_ARRAY_TRUE);
|
||||
|
||||
const RAW_EQ_ARRAY_FALSE: bool = unsafe { raw_eq(&[13_u8, 42], &[42, 13]) };
|
||||
assert!(!RAW_EQ_ARRAY_FALSE);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue