Skip to content

Remove zerocopy from rand #1579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 16, 2025
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md).
You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful.

## [Unreleased]
- Remove `zerocopy` dependency (#1579)
- Fix feature `simd_support` for recent nightly rust (#1586)
- Add `Alphabetic` distribution. (#1587)
- Re-export `rand_core` (#1602)
Expand Down
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ alloc = []
os_rng = ["rand_core/os_rng"]

# Option (requires nightly Rust): experimental SIMD support
simd_support = ["zerocopy/simd-nightly"]
simd_support = []

# Option (enabled by default): enable StdRng
std_rng = ["dep:rand_chacha"]
Expand Down Expand Up @@ -75,7 +75,6 @@ rand_core = { path = "rand_core", version = "0.9.0", default-features = false }
log = { version = "0.4.4", optional = true }
serde = { version = "1.0.103", features = ["derive"], optional = true }
rand_chacha = { path = "rand_chacha", version = "0.9.0", default-features = false, optional = true }
zerocopy = { version = "0.8.0", default-features = false, features = ["simd"] }

[dev-dependencies]
rand_pcg = { path = "rand_pcg", version = "0.9.0" }
Expand Down
5 changes: 4 additions & 1 deletion benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ publish = false
# Option (requires nightly Rust): experimental SIMD support
simd_support = ["rand/simd_support"]


[dependencies]

[dev-dependencies]
Expand Down Expand Up @@ -38,6 +37,10 @@ harness = false
name = "shuffle"
harness = false

[[bench]]
name = "simd"
harness = false

[[bench]]
name = "standard"
harness = false
Expand Down
76 changes: 76 additions & 0 deletions benches/benches/simd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2018-2023 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Generating SIMD / wide types

#![cfg_attr(feature = "simd_support", feature(portable_simd))]

use criterion::{criterion_group, criterion_main, Criterion};

criterion_group!(
name = benches;
config = Criterion::default();
targets = simd
);
criterion_main!(benches);

#[cfg(not(feature = "simd_support"))]
pub fn simd(_: &mut Criterion) {}

#[cfg(feature = "simd_support")]
pub fn simd(c: &mut Criterion) {
use rand::prelude::*;
use rand_pcg::Pcg64Mcg;

let mut g = c.benchmark_group("random_simd");

g.bench_function("u128", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<u128>());
});

g.bench_function("m128i", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::arch::x86_64::__m128i>());
});

g.bench_function("m256i", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::arch::x86_64::__m256i>());
});

g.bench_function("m512i", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::arch::x86_64::__m512i>());
});

g.bench_function("u64x2", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::simd::u64x2>());
});

g.bench_function("u32x4", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::simd::u64x4>());
});

g.bench_function("u32x8", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::simd::u8x32>());
});

g.bench_function("u16x8", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::simd::u8x32>());
});

g.bench_function("u8x16", |b| {
let mut rng = Pcg64Mcg::from_rng(&mut rand::rng());
b.iter(|| rng.random::<core::simd::u8x32>());
});
}
1 change: 1 addition & 0 deletions rand_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
)]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(clippy::undocumented_unsafe_blocks)]
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std]
Expand Down
77 changes: 44 additions & 33 deletions src/distr/integer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,50 @@ impl_nzint!(NonZeroI64, NonZeroI64::new);
impl_nzint!(NonZeroI128, NonZeroI128::new);

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
macro_rules! x86_intrinsic_impl {
($meta:meta, $($intrinsic:ident),+) => {$(
#[cfg($meta)]
impl Distribution<$intrinsic> for StandardUniform {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $intrinsic {
// On proper hardware, this should compile to SIMD instructions
// Verified on x86 Haswell with __m128i, __m256i
let mut buf = [0_u8; core::mem::size_of::<$intrinsic>()];
rng.fill_bytes(&mut buf);
// x86 is little endian so no need for conversion
zerocopy::transmute!(buf)
}
}
)+};
impl Distribution<__m128i> for StandardUniform {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> __m128i {
// NOTE: It's tempting to use the u128 impl here, but confusingly this
// results in different code (return via rdx, r10 instead of rax, rdx
// with u128 impl) and is much slower (+130 time). This version calls
// impls::fill_bytes_via_next but performs well.

let mut buf = [0_u8; core::mem::size_of::<__m128i>()];
rng.fill_bytes(&mut buf);
// x86 is little endian so no need for conversion

// SAFETY: All byte sequences of `buf` represent values of the output type.
unsafe { core::mem::transmute(buf) }
}
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl Distribution<__m256i> for StandardUniform {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> __m256i {
let mut buf = [0_u8; core::mem::size_of::<__m256i>()];
rng.fill_bytes(&mut buf);
// x86 is little endian so no need for conversion

// SAFETY: All byte sequences of `buf` represent values of the output type.
unsafe { core::mem::transmute(buf) }
}
}

#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "simd_support"
))]
impl Distribution<__m512i> for StandardUniform {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> __m512i {
let mut buf = [0_u8; core::mem::size_of::<__m512i>()];
rng.fill_bytes(&mut buf);
// x86 is little endian so no need for conversion

// SAFETY: All byte sequences of `buf` represent values of the output type.
unsafe { core::mem::transmute(buf) }
}
}

#[cfg(feature = "simd_support")]
Expand All @@ -148,24 +177,6 @@ macro_rules! simd_impl {
#[cfg(feature = "simd_support")]
simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64);

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
x86_intrinsic_impl!(
any(target_arch = "x86", target_arch = "x86_64"),
__m128i,
__m256i
);
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "simd_support"
))]
x86_intrinsic_impl!(
all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "simd_support"
),
__m512i
);

#[cfg(test)]
mod tests {
use super::*;
Expand Down
8 changes: 7 additions & 1 deletion src/distr/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ impl Distribution<char> for StandardUniform {
if n <= 0xDFFF {
n -= GAP_SIZE;
}
// SAFETY: We ensure above that `n` represents a `char`.
unsafe { char::from_u32_unchecked(n) }
}
}
Expand Down Expand Up @@ -166,9 +167,14 @@ impl Distribution<u8> for Alphabetic {
#[cfg(feature = "alloc")]
impl SampleString for Alphanumeric {
fn append_string<R: Rng + ?Sized>(&self, rng: &mut R, string: &mut String, len: usize) {
// SAFETY: `self` only samples alphanumeric characters, which are valid UTF-8.
unsafe {
let v = string.as_mut_vec();
v.extend(self.sample_iter(rng).take(len));
v.extend(
self.sample_iter(rng)
.take(len)
.inspect(|b| debug_assert!(b.is_ascii_alphanumeric())),
);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
clippy::neg_cmp_op_on_partial_ord,
clippy::nonminimal_bool
)]
#![deny(clippy::undocumented_unsafe_blocks)]

#[cfg(feature = "alloc")]
extern crate alloc;
Expand Down
60 changes: 48 additions & 12 deletions src/rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
use crate::distr::uniform::{SampleRange, SampleUniform};
use crate::distr::{self, Distribution, StandardUniform};
use core::num::Wrapping;
use core::{mem, slice};
use rand_core::RngCore;
use zerocopy::IntoBytes;

/// User-level interface for RNGs
///
Expand Down Expand Up @@ -393,14 +393,36 @@ impl Fill for [u8] {
}
}

/// Call target for unsafe macros
const unsafe fn __unsafe() {}

/// Implement `Fill` for given type `$t`.
///
/// # Safety
/// All bit patterns of `[u8; size_of::<$t>()]` must represent values of `$t`.
macro_rules! impl_fill {
() => {};
($t:ty) => {
($t:ty) => {{
// Force caller to wrap with an `unsafe` block
__unsafe();

impl Fill for [$t] {
#[inline(never)] // in micro benchmarks, this improves performance
fn fill<R: Rng + ?Sized>(&mut self, rng: &mut R) {
if self.len() > 0 {
rng.fill_bytes(self.as_mut_bytes());
let size = mem::size_of_val(self);
rng.fill_bytes(
// SAFETY: `self` non-null and valid for reads and writes within its `size`
// bytes. `self` meets the alignment requirements of `&mut [u8]`.
// The contents of `self` are initialized. Both `[u8]` and `[$t]` are valid
// for all bit-patterns of their contents (note that the SAFETY requirement
// on callers of this macro). `self` is not borrowed.
unsafe {
slice::from_raw_parts_mut(self.as_mut_ptr()
as *mut u8,
size
)
}
);
for x in self {
*x = x.to_le();
}
Expand All @@ -409,27 +431,41 @@ macro_rules! impl_fill {
}

impl Fill for [Wrapping<$t>] {
#[inline(never)]
fn fill<R: Rng + ?Sized>(&mut self, rng: &mut R) {
if self.len() > 0 {
rng.fill_bytes(self.as_mut_bytes());
let size = self.len() * mem::size_of::<$t>();
rng.fill_bytes(
// SAFETY: `self` non-null and valid for reads and writes within its `size`
// bytes. `self` meets the alignment requirements of `&mut [u8]`.
// The contents of `self` are initialized. Both `[u8]` and `[$t]` are valid
// for all bit-patterns of their contents (note that the SAFETY requirement
// on callers of this macro). `self` is not borrowed.
unsafe {
slice::from_raw_parts_mut(self.as_mut_ptr()
as *mut u8,
size
)
}
);
for x in self {
*x = Wrapping(x.0.to_le());
*x = Wrapping(x.0.to_le());
}
}
}
}
}}
};
($t:ty, $($tt:ty,)*) => {
($t:ty, $($tt:ty,)*) => {{
impl_fill!($t);
// TODO: this could replace above impl once Rust #32463 is fixed
// impl_fill!(Wrapping<$t>);
impl_fill!($($tt,)*);
}
}}
}

impl_fill!(u16, u32, u64, u128,);
impl_fill!(i8, i16, i32, i64, i128,);
// SAFETY: All bit patterns of `[u8; size_of::<$t>()]` represent values of `u*`.
const _: () = unsafe { impl_fill!(u16, u32, u64, u128,) };
// SAFETY: All bit patterns of `[u8; size_of::<$t>()]` represent values of `i*`.
const _: () = unsafe { impl_fill!(i8, i16, i32, i64, i128,) };

impl<T, const N: usize> Fill for [T; N]
where
Expand Down
4 changes: 4 additions & 0 deletions src/seq/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ pub trait IteratorRandom: Iterator + Sized {
/// force every element to be created regardless call `.inspect(|e| ())`.
///
/// [`choose`]: IteratorRandom::choose
//
// Clippy is wrong here: we need to iterate over all entries with the RNG to
// ensure that choosing is *stable*.
#[allow(clippy::double_ended_iterator_last)]
fn choose_stable<R>(mut self, rng: &mut R) -> Option<Self::Item>
where
R: Rng + ?Sized,
Expand Down