LibJS/AK: Move XorShift128PlusRNG to AK

This will be useful for CSS random functions so it should be in a
reusable place.

This does require us to use `AK::get_random` instead of
`Crypto::get_secure_random`, but this is fine since the latter is in the
process of being removed (see #6564).
This commit is contained in:
Callum Law 2025-11-04 17:37:21 +13:00 committed by Sam Atkins
parent 6d9f10ba36
commit 1f7fe97ac3
Notes: github-actions[bot] 2025-12-01 11:02:26 +00:00
3 changed files with 51 additions and 45 deletions

View file

@ -97,4 +97,41 @@ u64 get_random_uniform_64(u64 max_bounds)
return random_value % max_bounds;
}
XorShift128PlusRNG::XorShift128PlusRNG()
{
// Splitmix64 is used as xorshift is sensitive to being seeded with all 0s
u64 seed = get_random<u64>();
m_low = splitmix64(seed);
seed = get_random<u64>();
m_high = splitmix64(seed);
}
double XorShift128PlusRNG::get()
{
u64 value = advance() & ((1ULL << 53) - 1);
return value * (1.0 / (1ULL << 53));
}
u64 XorShift128PlusRNG::splitmix64(u64& state)
{
u64 z = (state += 0x9e3779b97f4a7c15ULL);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
return z ^ (z >> 31);
}
// Apparently this set of constants is better: https://stackoverflow.com/a/34432126
u64 XorShift128PlusRNG::advance()
{
u64 s1 = m_low;
u64 const s0 = m_high;
u64 const result = s0 + s1;
m_low = s0;
s1 ^= s1 << 23;
s1 ^= s1 >> 18;
s1 ^= s0 ^ (s0 >> 5);
m_high = s1;
return result + s1;
}
}

View file

@ -31,6 +31,19 @@ inline T get_random()
u32 get_random_uniform(u32 max_bounds);
u64 get_random_uniform_64(u64 max_bounds);
// http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf
class XorShift128PlusRNG {
public:
XorShift128PlusRNG();
double get();
private:
u64 splitmix64(u64& state);
u64 advance();
u64 m_low { 0 };
u64 m_high { 0 };
};
template<typename Collection>
inline void shuffle(Collection& collection)
{
@ -48,4 +61,5 @@ using AK::fill_with_random;
using AK::get_random;
using AK::get_random_uniform;
using AK::shuffle;
using AK::XorShift128PlusRNG;
#endif

View file

@ -809,51 +809,6 @@ JS_DEFINE_NATIVE_FUNCTION(MathObject::pow)
return pow_impl(vm, vm.argument(0), vm.argument(1));
}
// http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf
class XorShift128PlusRNG {
public:
XorShift128PlusRNG()
{
// Splitmix64 is used as xorshift is sensitive to being seeded with all 0s
u64 seed = Crypto::get_secure_random<u64>();
m_low = splitmix64(seed);
seed = Crypto::get_secure_random<u64>();
m_high = splitmix64(seed);
}
double get()
{
u64 value = advance() & ((1ULL << 53) - 1);
return value * (1.0 / (1ULL << 53));
}
private:
u64 splitmix64(u64& state)
{
u64 z = (state += 0x9e3779b97f4a7c15ULL);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
return z ^ (z >> 31);
}
// Apparently this set of constants is better: https://stackoverflow.com/a/34432126
u64 advance()
{
u64 s1 = m_low;
u64 const s0 = m_high;
u64 const result = s0 + s1;
m_low = s0;
s1 ^= s1 << 23;
s1 ^= s1 >> 18;
s1 ^= s0 ^ (s0 >> 5);
m_high = s1;
return result + s1;
}
u64 m_low { 0 };
u64 m_high { 0 };
};
Value MathObject::random_impl()
{
// This function returns a Number value with positive sign, greater than or equal to +0𝔽 but strictly less than 1𝔽,