From 4e0c1e1de9b6a1f3c6d39990d09ac80551e5afce Mon Sep 17 00:00:00 2001 From: Gareth Daniel Smith Date: Thu, 17 May 2012 19:52:49 +0100 Subject: [PATCH 1/3] separate the rand::rng gen_* methods out into an iface-less-impl so that the gen_* methods can be reused with different rng implementations (for https://github.com/mozilla/rust/issues/2379) --- src/libcore/core.rs | 1 + src/libcore/rand.rs | 104 ++++++++++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/src/libcore/core.rs b/src/libcore/core.rs index 8681f7d2a1ca7..4b07e2b29b7b6 100644 --- a/src/libcore/core.rs +++ b/src/libcore/core.rs @@ -11,6 +11,7 @@ import vec_iter::extensions; import option::extensions; import option_iter::extensions; import ptr::extensions; +import rand::extensions; export path, option, some, none, unreachable; export extensions; diff --git a/src/libcore/rand.rs b/src/libcore/rand.rs index a2e985ba00889..30ec179909216 100644 --- a/src/libcore/rand.rs +++ b/src/libcore/rand.rs @@ -1,6 +1,6 @@ #[doc = "Random number generation"]; -export rng; +export rng, extensions; enum rctx {} @@ -15,55 +15,56 @@ native mod rustrt { iface rng { #[doc = "Return the next random integer"] fn next() -> u32; +} + +#[doc = "Extension methods for random number generators"] +impl extensions for rng { - #[doc = "Return the next random float"] - fn next_float() -> float; + #[doc = "Return a random float"] + fn gen_float() -> float { + let u1 = self.next() as float; + let u2 = self.next() as float; + let u3 = self.next() as float; + let scale = u32::max_value as float; + ret ((u1 / scale + u2) / scale + u3) / scale; + } #[doc = "Return a random string composed of A-Z, a-z, 0-9."] - fn gen_str(len: uint) -> str; + fn gen_str(len: uint) -> str { + let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz" + + "0123456789"; + let mut s = ""; + let mut i = 0u; + while (i < len) { + let n = self.next() as uint % charset.len(); + s = s + str::from_char(str::char_at(charset, n)); + i += 1u; + } + s + } #[doc = "Return a random byte string."] - fn gen_bytes(len: uint) -> [u8]; + fn gen_bytes(len: uint) -> [u8] { + let mut v = []; + let mut i = 0u; + while i < len { + let n = self.next() as uint; + v += [(n % (u8::max_value as uint)) as u8]; + i += 1u; + } + v + } } -resource rand_res(c: *rctx) { rustrt::rand_free(c); } - #[doc = "Create a random number generator"] fn rng() -> rng { + resource rand_res(c: *rctx) { rustrt::rand_free(c); } + impl of rng for @rand_res { fn next() -> u32 { ret rustrt::rand_next(**self); } - fn next_float() -> float { - let u1 = rustrt::rand_next(**self) as float; - let u2 = rustrt::rand_next(**self) as float; - let u3 = rustrt::rand_next(**self) as float; - let scale = u32::max_value as float; - ret ((u1 / scale + u2) / scale + u3) / scale; - } - fn gen_str(len: uint) -> str { - let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "abcdefghijklmnopqrstuvwxyz" + - "0123456789"; - let mut s = ""; - let mut i = 0u; - while (i < len) { - let n = rustrt::rand_next(**self) as uint % - str::len(charset); - s = s + str::from_char(str::char_at(charset, n)); - i += 1u; - } - s - } - fn gen_bytes(len: uint) -> [u8] { - let mut v = []; - let mut i = 0u; - while i < len { - let n = rustrt::rand_next(**self) as uint; - v += [(n % (u8::max_value as uint)) as u8]; - i += 1u; - } - v - } } + @rand_res(rustrt::rand_new()) as rng } @@ -72,7 +73,7 @@ mod tests { #[test] fn test() { - let r1: rand::rng = rand::rng(); + let r1 = rand::rng(); log(debug, r1.next()); log(debug, r1.next()); { @@ -95,13 +96,30 @@ mod tests { } #[test] - fn genstr() { - let r: rand::rng = rand::rng(); + fn gen_float() { + let r = rand::rng(); + let a = r.gen_float(); + let b = r.gen_float(); + log(debug, (a, b)); + } + + #[test] + fn gen_str() { + let r = rand::rng(); log(debug, r.gen_str(10u)); log(debug, r.gen_str(10u)); log(debug, r.gen_str(10u)); - assert(str::len(r.gen_str(10u)) == 10u); - assert(str::len(r.gen_str(16u)) == 16u); + assert r.gen_str(0u).len() == 0u; + assert r.gen_str(10u).len() == 10u; + assert r.gen_str(16u).len() == 16u; + } + + #[test] + fn gen_bytes() { + let r = rand::rng(); + assert r.gen_bytes(0u).len() == 0u; + assert r.gen_bytes(10u).len() == 10u; + assert r.gen_bytes(16u).len() == 16u; } } From 344dbbd46e40a6706eb3a79fef9d85d8a569af2b Mon Sep 17 00:00:00 2001 From: Gareth Daniel Smith Date: Sat, 19 May 2012 19:25:45 +0100 Subject: [PATCH 2/3] add a bunch more rand::rng extension methods - one for each primitive type and also some choose/shuffle functions (for https://github.com/mozilla/rust/issues/2379) --- src/libcore/rand.rs | 313 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 275 insertions(+), 38 deletions(-) diff --git a/src/libcore/rand.rs b/src/libcore/rand.rs index 30ec179909216..2dac731f5671b 100644 --- a/src/libcore/rand.rs +++ b/src/libcore/rand.rs @@ -1,6 +1,6 @@ #[doc = "Random number generation"]; -export rng, extensions; +export rng, weighted, extensions; enum rctx {} @@ -17,19 +17,123 @@ iface rng { fn next() -> u32; } +#[doc = "A value with a particular weight compared to other values"] +type weighted = { weight: uint, item: T }; + #[doc = "Extension methods for random number generators"] impl extensions for rng { + #[doc = "Return a random int"] + fn gen_int() -> int { + self.gen_i64() as int + } + + #[doc = "Return an int randomly chosen from the range [start, end], \ + failing if start > end"] + fn gen_int_from(start: int, end: int) -> int { + assert start <= end; + start + int::abs(self.gen_int() % (end - start + 1)) + } + + #[doc = "Return a random i8"] + fn gen_i8() -> i8 { + self.next() as i8 + } + + #[doc = "Return a random i16"] + fn gen_i16() -> i16 { + self.next() as i16 + } + + #[doc = "Return a random i32"] + fn gen_i32() -> i32 { + self.next() as i32 + } + + #[doc = "Return a random i64"] + fn gen_i64() -> i64 { + (self.next() as i64 << 32) | self.next() as i64 + } + + #[doc = "Return a random uint"] + fn gen_uint() -> uint { + self.gen_u64() as u64 + } + + #[doc = "Return a uint randomly chosen from the range [start, end], \ + failing if start > end"] + fn gen_uint_from(start: uint, end: uint) -> uint { + assert start <= end; + start + (self.gen_uint() % (end - start + 1u)) + } + + #[doc = "Return a random u8"] + fn gen_u8() -> u8 { + self.next() as u8 + } + + #[doc = "Return a random u16"] + fn gen_u16() -> u16 { + self.next() as u16 + } + + #[doc = "Return a random u32"] + fn gen_u32() -> u32 { + self.next() + } + + #[doc = "Return a random u64"] + fn gen_u64() -> u64 { + (self.next() as u64 << 32) | self.next() as u64 + } + #[doc = "Return a random float"] fn gen_float() -> float { - let u1 = self.next() as float; - let u2 = self.next() as float; - let u3 = self.next() as float; - let scale = u32::max_value as float; - ret ((u1 / scale + u2) / scale + u3) / scale; + self.gen_f64() as float } - #[doc = "Return a random string composed of A-Z, a-z, 0-9."] + #[doc = "Return a random f32"] + fn gen_f32() -> f32 { + self.gen_f64() as f32 + } + + #[doc = "Return a random f64"] + fn gen_f64() -> f64 { + let u1 = self.next() as f64; + let u2 = self.next() as f64; + let u3 = self.next() as f64; + let scale = u32::max_value as f64; + ret ((u1 / scale + u2) / scale + u3) / scale; + } + + #[doc = "Return a random char"] + fn gen_char() -> char { + self.next() as char + } + + #[doc = "Return a char randomly chosen from chars, failing if chars is \ + empty"] + fn gen_char_from(chars: str) -> char { + assert !chars.is_empty(); + self.choose(str::chars(chars)) + } + + #[doc = "Return a random bool"] + fn gen_bool() -> bool { + self.next() & 1u32 == 1u32 + } + + #[doc = "Return a bool with a 1 in n chance of true"] + fn gen_weighted_bool(n: uint) -> bool { + if n == 0u { + true + } else { + self.gen_uint_from(1u, n) == 1u + } + } + + #[doc = "Return a random string of the specified length composed of A-Z, \ + a-z, 0-9"] fn gen_str(len: uint) -> str { let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + @@ -37,24 +141,90 @@ impl extensions for rng { let mut s = ""; let mut i = 0u; while (i < len) { - let n = self.next() as uint % charset.len(); - s = s + str::from_char(str::char_at(charset, n)); + s = s + str::from_char(self.gen_char_from(charset)); i += 1u; } s } - #[doc = "Return a random byte string."] + #[doc = "Return a random byte string of the specified length"] fn gen_bytes(len: uint) -> [u8] { - let mut v = []; - let mut i = 0u; - while i < len { - let n = self.next() as uint; - v += [(n % (u8::max_value as uint)) as u8]; - i += 1u; + vec::from_fn(len) {|_i| + self.gen_u8() + } + } + + #[doc = "Choose an item randomly, failing if values is empty"] + fn choose(values: [T]) -> T { + self.choose_option(values).get() + } + + #[doc = "Choose some(item) randomly, returning none if values is empty"] + fn choose_option(values: [T]) -> option { + if values.is_empty() { + none + } else { + some(values[self.gen_uint_from(0u, values.len() - 1u)]) } - v } + + #[doc = "Choose an item respecting the relative weights, failing if \ + the sum of the weights is 0"] + fn choose_weighted(v : [weighted]) -> T { + self.choose_weighted_option(v).get() + } + + #[doc = "Choose some(item) respecting the relative weights, returning \ + none if the sum of the weights is 0"] + fn choose_weighted_option(v: [weighted]) -> option { + let mut total = 0u; + for v.each {|item| + total += item.weight; + } + if total == 0u { + ret none; + } + let chosen = self.gen_uint_from(0u, total - 1u); + let mut so_far = 0u; + for v.each {|item| + so_far += item.weight; + if so_far > chosen { + ret some(item.item); + } + } + unreachable(); + } + + #[doc = "Return a vec containing copies of the items, in order, where \ + the weight of the item determines how many copies there are"] + fn weighted_vec(v: [weighted]) -> [T] { + let mut r = []; + for v.each {|item| + uint::range(0u, item.weight) {|_i| + r += [item.item]; + } + } + r + } + + #[doc = "Shuffle a vec"] + fn shuffle(values: [T]) -> [T] { + let mut m = vec::to_mut(values); + self.shuffle_mut(m); + ret vec::from_mut(m); + } + + #[doc = "Shuffle a mutable vec in place"] + fn shuffle_mut(&values: [mut T]) { + let mut i = values.len(); + while i >= 2u { + // invariant: elements with index >= i have been locked in place. + i -= 1u; + // lock element i in place. + vec::swap(values, i, self.gen_uint_from(0u, i)); + } + } + } #[doc = "Create a random number generator"] @@ -72,27 +242,33 @@ fn rng() -> rng { mod tests { #[test] - fn test() { - let r1 = rand::rng(); - log(debug, r1.next()); - log(debug, r1.next()); - { - let r2 = rand::rng(); - log(debug, r1.next()); - log(debug, r2.next()); - log(debug, r1.next()); - log(debug, r1.next()); - log(debug, r2.next()); - log(debug, r2.next()); - log(debug, r1.next()); - log(debug, r1.next()); - log(debug, r1.next()); - log(debug, r2.next()); - log(debug, r2.next()); - log(debug, r2.next()); - } - log(debug, r1.next()); - log(debug, r1.next()); + fn gen_int_from() { + let r = rand::rng(); + let a = r.gen_int_from(-3, 42); + assert a >= -3 && a <= 42; + assert r.gen_int_from(0, 0) == 0; + assert r.gen_int_from(-12, -12) == -12; + } + + #[test] + #[should_fail] + fn gen_int_from_fail() { + rand::rng().gen_int_from(5, -2); + } + + #[test] + fn gen_uint_from() { + let r = rand::rng(); + let a = r.gen_uint_from(3u, 42u); + assert a >= 3u && a <= 42u; + assert r.gen_uint_from(0u, 0u) == 0u; + assert r.gen_uint_from(12u, 12u) == 12u; + } + + #[test] + #[should_fail] + fn gen_uint_from_fail() { + rand::rng().gen_uint_from(5u, 2u); } #[test] @@ -103,6 +279,13 @@ mod tests { log(debug, (a, b)); } + #[test] + fn gen_weighted_bool() { + let r = rand::rng(); + assert r.gen_weighted_bool(0u) == true; + assert r.gen_weighted_bool(1u) == true; + } + #[test] fn gen_str() { let r = rand::rng(); @@ -121,6 +304,60 @@ mod tests { assert r.gen_bytes(10u).len() == 10u; assert r.gen_bytes(16u).len() == 16u; } + + #[test] + fn choose() { + let r = rand::rng(); + assert r.choose([1, 1, 1]) == 1; + } + + #[test] + fn choose_option() { + let r = rand::rng(); + assert r.choose_option([]) == none::; + assert r.choose_option([1, 1, 1]) == some(1); + } + + #[test] + fn choose_weighted() { + let r = rand::rng(); + assert r.choose_weighted([{weight: 1u, item: 42}]) == 42; + assert r.choose_weighted([ + {weight: 0u, item: 42}, + {weight: 1u, item: 43} + ]) == 43; + } + + #[test] + fn choose_weighted_option() { + let r = rand::rng(); + assert r.choose_weighted_option([{weight: 1u, item: 42}]) == some(42); + assert r.choose_weighted_option([ + {weight: 0u, item: 42}, + {weight: 1u, item: 43} + ]) == some(43); + assert r.choose_weighted_option([]) == none::; + } + + #[test] + fn weighted_vec() { + let r = rand::rng(); + let empty: [int] = []; + assert r.weighted_vec([]) == empty; + assert r.weighted_vec([ + {weight: 0u, item: 3u}, + {weight: 1u, item: 2u}, + {weight: 2u, item: 1u} + ]) == [2u, 1u, 1u]; + } + + #[test] + fn shuffle() { + let r = rand::rng(); + let empty: [int] = []; + assert r.shuffle([]) == empty; + assert r.shuffle([1, 1, 1]) == [1, 1, 1]; + } } From 269bb50e048b8e1d1dd5c2847560a0ceb5c3aff4 Mon Sep 17 00:00:00 2001 From: Gareth Daniel Smith Date: Sun, 20 May 2012 14:06:54 +0100 Subject: [PATCH 3/3] add a seeded random number generator so that sequences of random numbers can be easily reproduced (for https://github.com/mozilla/rust/issues/2379) --- src/libcore/rand.rs | 46 +++++++++++++++++--- src/rt/rust_builtin.cpp | 29 ++++++++++++- src/rt/rust_sched_loop.cpp | 2 +- src/rt/rust_util.h | 87 ++++++++++++++++++++------------------ src/rt/rustrt.def.in | 2 + 5 files changed, 116 insertions(+), 50 deletions(-) diff --git a/src/libcore/rand.rs b/src/libcore/rand.rs index 2dac731f5671b..a6ce71fbf9dbc 100644 --- a/src/libcore/rand.rs +++ b/src/libcore/rand.rs @@ -1,12 +1,14 @@ #[doc = "Random number generation"]; -export rng, weighted, extensions; +export rng, seed, seeded_rng, weighted, extensions; enum rctx {} #[abi = "cdecl"] native mod rustrt { + fn rand_seed() -> [u8]; fn rand_new() -> *rctx; + fn rand_new_seeded(seed: [u8]) -> *rctx; fn rand_next(c: *rctx) -> u32; fn rand_free(c: *rctx); } @@ -227,20 +229,50 @@ impl extensions for rng { } -#[doc = "Create a random number generator"] -fn rng() -> rng { - resource rand_res(c: *rctx) { rustrt::rand_free(c); } +resource rand_res(c: *rctx) { rustrt::rand_free(c); } - impl of rng for @rand_res { - fn next() -> u32 { ret rustrt::rand_next(**self); } - } +impl of rng for @rand_res { + fn next() -> u32 { ret rustrt::rand_next(**self); } +} +#[doc = "Create a new random seed for seeded_rng"] +fn seed() -> [u8] { + rustrt::rand_seed() +} + +#[doc = "Create a random number generator with a system specified seed"] +fn rng() -> rng { @rand_res(rustrt::rand_new()) as rng } +#[doc = "Create a random number generator using the specified seed. A \ + generator constructed with a given seed will generate the same \ + sequence of values as all other generators constructed with the \ + same seed. The seed may be any length."] +fn seeded_rng(seed: [u8]) -> rng { + @rand_res(rustrt::rand_new_seeded(seed)) as rng +} + #[cfg(test)] mod tests { + #[test] + fn rng_seeded() { + let seed = rand::seed(); + let ra = rand::seeded_rng(seed); + let rb = rand::seeded_rng(seed); + assert ra.gen_str(100u) == rb.gen_str(100u); + } + + #[test] + fn rng_seeded_custom_seed() { + // much shorter than generated seeds which are 1024 bytes + let seed = [2u8, 32u8, 4u8, 32u8, 51u8]; + let ra = rand::seeded_rng(seed); + let rb = rand::seeded_rng(seed); + assert ra.gen_str(100u) == rb.gen_str(100u); + } + #[test] fn gen_int_from() { let r = rand::rng(); diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp index 8f5c3c6fa14ae..3e6995c872ce6 100644 --- a/src/rt/rust_builtin.cpp +++ b/src/rt/rust_builtin.cpp @@ -178,16 +178,41 @@ rust_str_push(rust_vec** sp, uint8_t byte) { (*sp)->fill = fill + 1; } +extern "C" CDECL rust_vec* +rand_seed() { + size_t size = sizeof(ub4) * RANDSIZ; + rust_task *task = rust_get_current_task(); + rust_vec *v = (rust_vec *) task->kernel->malloc(vec_size(size), + "rand_seed"); + v->fill = v->alloc = size; + isaac_seed((uint8_t*) &v->data); + return v; +} + extern "C" CDECL void * rand_new() { rust_task *task = rust_get_current_task(); rust_sched_loop *thread = task->sched_loop; - randctx *rctx = (randctx *) task->malloc(sizeof(randctx), "randctx"); + randctx *rctx = (randctx *) task->malloc(sizeof(randctx), "rand_new"); + if (!rctx) { + task->fail(); + return NULL; + } + isaac_init(thread->kernel, rctx, NULL); + return rctx; +} + +extern "C" CDECL void * +rand_new_seeded(rust_vec* seed) { + rust_task *task = rust_get_current_task(); + rust_sched_loop *thread = task->sched_loop; + randctx *rctx = (randctx *) task->malloc(sizeof(randctx), + "rand_new_seeded"); if (!rctx) { task->fail(); return NULL; } - isaac_init(thread->kernel, rctx); + isaac_init(thread->kernel, rctx, seed); return rctx; } diff --git a/src/rt/rust_sched_loop.cpp b/src/rt/rust_sched_loop.cpp index e8942f37733e5..697f460d3a64b 100644 --- a/src/rt/rust_sched_loop.cpp +++ b/src/rt/rust_sched_loop.cpp @@ -29,7 +29,7 @@ rust_sched_loop::rust_sched_loop(rust_scheduler *sched,int id) : name("main") { LOGPTR(this, "new dom", (uintptr_t)this); - isaac_init(kernel, &rctx); + isaac_init(kernel, &rctx, NULL); if (!tls_initialized) init_tls(); diff --git a/src/rt/rust_util.h b/src/rt/rust_util.h index 9928a05c05780..35bd724255737 100644 --- a/src/rt/rust_util.h +++ b/src/rt/rust_util.h @@ -32,46 +32,6 @@ align_to(T size, size_t alignment) { return x; } -// Initialization helper for ISAAC RNG - -inline void -isaac_init(rust_kernel *kernel, randctx *rctx) -{ - memset(rctx, 0, sizeof(randctx)); - - char *rust_seed = kernel->env->rust_seed; - if (rust_seed != NULL) { - ub4 seed = (ub4) atoi(rust_seed); - for (size_t i = 0; i < RANDSIZ; i ++) { - memcpy(&rctx->randrsl[i], &seed, sizeof(ub4)); - seed = (seed + 0x7ed55d16) + (seed << 12); - } - } else { -#ifdef __WIN32__ - HCRYPTPROV hProv; - kernel->win32_require - (_T("CryptAcquireContext"), - CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT|CRYPT_SILENT)); - kernel->win32_require - (_T("CryptGenRandom"), - CryptGenRandom(hProv, sizeof(rctx->randrsl), - (BYTE*)(&rctx->randrsl))); - kernel->win32_require - (_T("CryptReleaseContext"), - CryptReleaseContext(hProv, 0)); -#else - int fd = open("/dev/urandom", O_RDONLY); - assert(fd > 0); - assert(read(fd, (void*) &rctx->randrsl, sizeof(rctx->randrsl)) - == sizeof(rctx->randrsl)); - assert(close(fd) == 0); -#endif - } - - randinit(rctx, 1); -} - // Interior vectors (rust-user-code level). struct @@ -136,6 +96,53 @@ make_str_vec(rust_kernel* kernel, size_t nstrs, char **strs) { return v; } +// Initialization helpers for ISAAC RNG + +inline void isaac_seed(uint8_t* dest) +{ + size_t size = sizeof(ub4) * RANDSIZ; +#ifdef __WIN32__ + HCRYPTPROV hProv; + kernel->win32_require + (_T("CryptAcquireContext"), + CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT|CRYPT_SILENT)); + kernel->win32_require + (_T("CryptGenRandom"), CryptGenRandom(hProv, size, (BYTE*) dest)); + kernel->win32_require + (_T("CryptReleaseContext"), CryptReleaseContext(hProv, 0)); +#else + int fd = open("/dev/urandom", O_RDONLY); + assert(fd > 0); + assert(read(fd, dest, size) == (int) size); + assert(close(fd) == 0); +#endif +} + +inline void +isaac_init(rust_kernel *kernel, randctx *rctx, rust_vec* user_seed) +{ + memset(rctx, 0, sizeof(randctx)); + + char *env_seed = kernel->env->rust_seed; + if (user_seed != NULL) { + // ignore bytes after the required length + size_t seed_len = user_seed->fill < sizeof(rctx->randrsl) + ? user_seed->fill : sizeof(rctx->randrsl); + memcpy(&rctx->randrsl, user_seed->data, seed_len); + } else if (env_seed != NULL) { + ub4 seed = (ub4) atoi(env_seed); + for (size_t i = 0; i < RANDSIZ; i ++) { + memcpy(&rctx->randrsl[i], &seed, sizeof(ub4)); + seed = (seed + 0x7ed55d16) + (seed << 12); + } + } else { + isaac_seed((uint8_t*) &rctx->randrsl); + } + + randinit(rctx, 1); +} + // // Local Variables: // mode: C++ diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index ed7fb146928f5..c8328eac06990 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -25,7 +25,9 @@ rust_port_id_send rust_port_select rand_free rand_new +rand_new_seeded rand_next +rand_seed refcount rust_get_sched_id rust_new_sched