hel_random/
lib.rs

1//! A simple pseudo non-cryptographic random number generator.
2//! Using xoshiro256++ under the hood.
3#![warn(missing_docs)]
4
5const STATE_SIZE: usize = 4;
6
7type Target = u64;
8type StateType = [Target; STATE_SIZE];
9
10static mut STATE: StateType = [0, 0, 0, 0];
11
12/// Gets current state of generator
13pub fn get_state() -> StateType {
14	unsafe { STATE }
15}
16
17#[used]
18#[cfg_attr(target_os = "linux", link_section = ".init_array")]
19#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
20#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
21static INIT: extern "C" fn() = {
22	extern "C" fn init() {
23		unsafe {
24			use std::alloc::*;
25
26			let mut res = STATE;
27
28			const ALLOC: usize = STATE_SIZE * STATE_SIZE;
29
30			let layout = Layout::array::<Target>(ALLOC).unwrap();
31			let ptr = alloc(layout);
32
33			if ptr.is_null() {
34				handle_alloc_error(layout);
35			}
36
37			let garbage_arr = &mut *(ptr as *mut [Target; ALLOC]);
38
39			// Will be used if there's no garbage on the heap
40			let addr = std::hint::black_box(ptr as Target);
41			let now = std::hint::black_box(
42				std::time::SystemTime::now()
43					.duration_since(std::time::UNIX_EPOCH)
44					.expect("to get system time")
45					.subsec_micros() as u64,
46			);
47			let now_bits = now ^ (now << 32) ^ (now.rotate_left(25));
48			let mut bits = addr ^ (addr >> 11) ^ (addr.rotate_right(30)) ^ now_bits;
49
50			// Looking for garbage on the heap, while writing some garbage back
51			for (i, garbage) in garbage_arr.iter_mut().enumerate() {
52				let current = &mut res[i % STATE_SIZE];
53
54				let mut val = std::hint::black_box(*garbage);
55				val ^= bits;
56
57				let msb = ((bits & 1) ^ ((bits >> 1) & 1)) << (Target::BITS - 1);
58				bits >>= 1;
59				bits |= msb;
60
61				*current ^= val;
62				std::ptr::write_volatile(garbage, val);
63			}
64
65			STATE = res;
66
67			dealloc(ptr, layout);
68		}
69	}
70
71	init
72};
73
74#[inline]
75fn xoshiro256pp() {
76	unsafe {
77		let s = STATE[1] << 17;
78
79		STATE[2] ^= STATE[0];
80		STATE[3] ^= STATE[1];
81		STATE[1] ^= STATE[2];
82		STATE[0] ^= STATE[3];
83
84		STATE[2] ^= s;
85
86		STATE[3] = STATE[3].rotate_left(45);
87	}
88}
89
90/// A helper trait to generate random values
91#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
92pub trait Random: Sized {
93	/// Will generate a random [`Self`]
94	fn random() -> Self;
95}
96
97/// Generic function that returns a random [`T`]
98///
99/// # Example
100/// ```
101/// use hel_random::generate;
102///
103/// let a: u64 = generate();
104/// let b: u32 = generate();
105/// let c = generate::<i128>();
106///
107/// println!("a = {a}");
108/// println!("b = {b}");
109/// println!("c = {c}");
110/// ```
111#[inline(always)]
112#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
113pub fn generate<T: Random>() -> T {
114	T::random()
115}
116
117macro_rules! make {
118	($type: ident, $code: block) => {
119		#[doc = concat!("Will generate a random ", stringify!($type))]
120		///
121		/// # Example
122		/// ```
123		#[doc = concat!("let a: ", stringify!($type), " = hel_random::", stringify!($type), "();")]
124		#[doc = concat!("let b: ", stringify!($type), " = hel_random::", stringify!($type), "();")]
125		/// // Examples generated by macro, so we check if type is `bool` or `i8` or `u8` to avoid collisions
126		#[doc = concat!("if std::mem::size_of::<", stringify!($type), ">() > 1 {")]
127		///     assert!(a != b);
128		/// }
129		/// ```
130		#[inline]
131		#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
132		pub fn $type() -> $type {
133			$code
134		}
135
136		impl Random for $type {
137			#[doc = concat!("Will generate a random ", stringify!($type))]
138			///
139			/// # Example
140			/// ```
141			/// use hel_random::Random;
142			///
143			#[doc = concat!("let r = ", stringify!($type), "::random();")]
144			/// println!("r = {r}");
145			/// ```
146			#[inline(always)]
147			fn random() -> Self {
148				$type()
149			}
150		}
151	};
152
153	($type: ident) => {
154		make!($type, { u64() as $type });
155	};
156}
157
158make!(u128, {
159	xoshiro256pp();
160
161	unsafe {
162		STATE[0].wrapping_add(STATE[2]) as u128 | (((STATE[1]).wrapping_add(STATE[3]) as u128) << 64)
163	}
164});
165make!(i128, { u128() as i128 });
166
167make!(u64, {
168	xoshiro256pp();
169	unsafe {
170		STATE[0]
171			.wrapping_add(STATE[3])
172			.rotate_left(23)
173			.wrapping_add(STATE[0])
174	}
175});
176make!(i64);
177make!(u32);
178make!(i32);
179make!(u16);
180make!(i16);
181make!(u8);
182make!(i8);
183
184make!(bool, {
185	unsafe {
186		// runtime check is necessary to avoid infinite loop
187		if STATE[0] == 0 {
188			return false;
189		}
190
191		loop {
192			xoshiro256pp();
193
194			let a = (STATE[0] & 1) == 1;
195			let b = (STATE[1] & 1) == 1;
196
197			if a != b {
198				return a;
199			}
200		}
201	}
202});
203
204// TODO floats