aboutsummaryrefslogtreecommitdiff
path: root/core/math/rand/rand_xoshiro256.odin
blob: 7326ba8d5a77e4f6624c0689008b6c73bc33d8b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package rand

import "base:intrinsics"
import "base:runtime"

import "core:math/bits"

/*
The state for a xoshiro256** pseudorandom generator.
*/
Xoshiro256_Random_State :: struct {
	s: [4]u64,
}

xoshiro256_random_generator_proc :: proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
	@(require_results)
	read_u64 :: proc "contextless" (r: ^Xoshiro256_Random_State) -> u64 {
		// xoshiro256** output function and state transition

		result := bits.rotate_left64(r.s[1] * 5, 7) * 9
		t := r.s[1] << 17

		r.s[2] = r.s[2] ~ r.s[0]
		r.s[3] = r.s[3] ~ r.s[1]
		r.s[1] = r.s[1] ~ r.s[2]
		r.s[0] = r.s[0] ~ r.s[3]
		r.s[2] = r.s[2] ~ t
		r.s[3] = bits.rotate_left64(r.s[3], 45)

		return result
	}

	@(thread_local)
	global_rand_seed: Xoshiro256_Random_State

	init :: proc "contextless" (r: ^Xoshiro256_Random_State, seed: u64) {
		// splitmix64 to expand a 64-bit seed into 256 bits of state
		sm64_next :: proc "contextless" (s: ^u64) -> u64 {
			s^ += 0x9E3779B97F4A7C15
			z := s^
			z = (z ~ (z >> 30)) * 0xBF58476D1CE4E5B9
			z = (z ~ (z >> 27)) * 0x94D049BB133111EB
			return z ~ (z >> 31)
		}

		local_seed := seed
		r.s[0] = sm64_next(&local_seed)
		r.s[1] = sm64_next(&local_seed)
		r.s[2] = sm64_next(&local_seed)
		r.s[3] = sm64_next(&local_seed)
		// Extremely unlikely all zero; ensure non-zero state
		if (r.s[0] | r.s[1] | r.s[2] | r.s[3]) == 0 {
			// force a minimal non-zero tweak
			r.s[0] = 1
		}
	}

	r: ^Xoshiro256_Random_State = ---
	if data == nil {
		r = &global_rand_seed
	} else {
		r = cast(^Xoshiro256_Random_State)data
	}

	switch mode {
	case .Read:
		if (r.s[0] | r.s[1] | r.s[2] | r.s[3]) == 0 {
			init(r, u64(intrinsics.read_cycle_counter()))
		}

		switch len(p) {
		case size_of(u64):
			// Fast path for a 64-bit destination.
			intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
		case:
			// All other cases.
			n := len(p) / size_of(u64)
			buff := ([^]u64)(raw_data(p))[:n]
			for &e in buff {
				intrinsics.unaligned_store(&e, read_u64(r))
			}
			// Handle remaining bytes
			rem := len(p) % size_of(u64)
			if rem > 0 {
				val := read_u64(r)
				tail := p[len(p) - rem:]
				for &b in tail {
					b = byte(val)
					val >>= 8
				}
			}
		}

	case .Reset:
		seed: u64 = 0
		runtime.mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
		init(r, seed)

	case .Query_Info:
		if len(p) != size_of(Generator_Query_Info) {
			return
		}
		info := (^Generator_Query_Info)(raw_data(p))
		info^ += {.Uniform, .Resettable}
	}
}

/*
Returns an instance of the xoshiro256** pseudorandom generator.  If no
initial state is provided, the PRNG will be lazily initialized with the
system timestamp counter on first-use.

WARNING: This random number generator is NOT cryptographically secure.

Inputs:
- state: Optional initial PRNG state.

Returns:
- A `Generator` instance.
*/
@(require_results)
xoshiro256_random_generator :: proc "contextless" (state: ^Xoshiro256_Random_State = nil) -> Generator {
	return {
		procedure = xoshiro256_random_generator_proc,
		data = state,
	}
}