~ chicken-core (chicken-5) 4f9ff45ea7f17878dbb01cbd4f9760f2148547ce
commit 4f9ff45ea7f17878dbb01cbd4f9760f2148547ce Author: felix <felix@call-with-current-continuation.org> AuthorDate: Fri Nov 3 13:58:54 2017 +0100 Commit: felix <felix@call-with-current-continuation.org> CommitDate: Fri Nov 3 13:58:54 2017 +0100 initial attempt at new random numbers API diff --git a/chicken.h b/chicken.h index 400c80e2..357928b9 100644 --- a/chicken.h +++ b/chicken.h @@ -735,6 +735,8 @@ void *alloca (); # define C_MAX_PATH 1024 #endif +#define C_RANDOM_STATE_SIZE (16 * sizeof(C_u32)) + /* Types: */ typedef struct C_block_struct @@ -1232,8 +1234,7 @@ typedef void (C_ccall *C_proc)(C_word, C_word *) C_noret; C_unfix(end1) - C_unfix(start1) ), C_SCHEME_UNDEFINED) #define C_words(n) C_fix(C_bytestowords(C_unfix(n))) #define C_bytes(n) C_fix(C_wordstobytes(C_unfix(n))) -#define C_random_fixnum(n) C_fix((C_word)(((double)rand())/(RAND_MAX + 1.0) * C_unfix(n))) -#define C_randomize(n) (srand(C_unfix(n)), C_SCHEME_UNDEFINED) +#define C_rand(n) C_fix((C_word)(((double)rand())/(RAND_MAX + 1.0) * C_unfix(n))) #define C_block_size(x) C_fix(C_header_size(x)) #define C_u_i_bignum_size(b) C_fix(C_bignum_size(b)) #define C_a_u_i_big_to_flo(p, n, b) C_flonum(p, C_bignum_to_double(b)) @@ -2092,6 +2093,11 @@ C_fctexport C_word C_fcall C_i_foreign_unsigned_ranged_integer_argumentp(C_word C_fctexport C_char *C_lookup_procedure_id(void *ptr); C_fctexport void *C_lookup_procedure_ptr(C_char *id); +C_fctexport C_word C_random_fixnum(C_word n) C_regparm; +C_fctexport C_word C_fcall C_s_a_u_i_random_int(C_word **ptr, C_word n, C_word rn) C_regparm; +C_fctexport C_word C_random_bytes(C_word buf, C_word size); +C_fctexport C_word C_set_random_seed(C_word buf, C_word n); + #ifdef C_SIXTY_FOUR C_fctexport C_cpsproc(C_peek_signed_integer_32); C_fctexport C_cpsproc(C_peek_unsigned_integer_32); diff --git a/extras.scm b/extras.scm index 5c998600..63f49ed6 100644 --- a/extras.scm +++ b/extras.scm @@ -644,19 +644,64 @@ ;;; Random numbers: (module chicken.random - (randomize random) - -(import scheme chicken chicken.time) - -(define (randomize . n) - (let ((nn (if (null? n) - (quotient (current-seconds) 1000) ; wall clock time - (car n)))) - (##sys#check-fixnum nn 'randomize) - (##core#inline "C_randomize" nn))) - -(define (random n) - (##sys#check-fixnum n 'random) - (if (eq? n 0) - 0 - (##core#inline "C_random_fixnum" n)))) + (set-pseudo-random-seed! pseudo-random-integer random-bytes) + +(import scheme chicken chicken.time chicken.io foreign) + +(define (set-pseudo-random-seed! buf #!optional n) + (if n + (##sys#check-fixnum n 'set-pseudo-random-seed!) + (set! n (##sys#size buf))) + (unless (##core#inline "C_byteblockp" buf) + (##sys#error 'set-pseudo-random-seed! + "invalid buffer type" buf)) + (##core#inline "C_set_random_seed" buf + (##core#inline "C_i_fixnum_min" + n + (##sys#size buf)))) + +(define (pseudo-random-integer n) + (define (badrange) + (##sys#error 'pseudo-random-integer "invalid range" n)) + (cond ((##core#inline "C_fixnump" n) + (if (##core#inline "C_fixnum_lessp" n 0) + (badrange) + (##core#inline "C_random_fixnum" n))) + ((not (##core#inline "C_i_bignump" n)) + (##sys#error 'pseudo-random-integer "bad argument type" n)) + (else + (if (##core#inline "C_i_lessp" n 0) + (badrange) + (##core#inline_allocate ("C_s_a_u_i_random_int" 2) + n))))) + +(define random-bytes + (let ((in #f) + (nstate (foreign-value "C_RANDOM_STATE_SIZE" unsigned-int))) + (lambda (#!optional buf size) + (when size + (##sys#check-fixnum size 'random-bytes) + (when (or (< size 0) + (> size 256)) + (##sys#error 'random-bytes "size out of range" size))) + (let* ((dest (cond (buf + (unless (##core#inline "C_byteblockp" buf) + (##sys#error 'random-bytes + "invalid buffer type" buf)) + buf) + (else (make-string (or size nstate))))) + (r (##core#inline "C_random_bytes" dest + (or size (##sys#size dest))))) + (cond ((eq? -1 r) + (##sys#error 'random-bytes "error while obtaining random bytes")) + ((not r) ; no syscall or API function, read from /dev/urandom... + (unless in + (if (file-exists? "/dev/urandom") + (set! in (open-input-file "/dev/urandom" #:binary)) + (##sys#error 'random-bytes "no entropy source available"))) + (read-string! nstate dest in) + (unless (eq? buf dest) + (##core#inline "C_string_to_bytevector" dest)))) + dest)))) + +) diff --git a/runtime.c b/runtime.c index 9423993a..60803e01 100644 --- a/runtime.c +++ b/runtime.c @@ -479,6 +479,8 @@ static C_TLS int pending_interrupts[ MAX_PENDING_INTERRUPTS ], pending_interrupts_count, handling_interrupts; +static C_TLS C_u32 random_state[ C_RANDOM_STATE_SIZE / sizeof(C_u32) ]; +static C_TLS int random_state_index = 0; /* Prototypes: */ @@ -816,7 +818,11 @@ int CHICKEN_initialize(int heap, int stack, int symbols, void *toplevel) current_module_handle = NULL; callback_continuation_level = 0; gc_ms = 0; - (void)C_randomize(C_fix(time(NULL))); + srand(C_fix(time(NULL))); + + for(i = 0; i < C_RANDOM_STATE_SIZE / sizeof(C_u32); ++i) + random_state[ i ] = rand(); + initialize_symbol_table(); if (profiling) { @@ -12516,3 +12522,112 @@ C_i_pending_interrupt(C_word dummy) return C_SCHEME_FALSE; } } + + +/* random numbers, mostly lifted from + https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c +*/ + +#ifdef __linux__ +# include <sys/syscall.h> +#elif defined(__OpenBSD__) +# include <sys/systm.h> +#endif + +#ifdef _WIN32 +# define RtlGenRandom SystemFunction036 +# if defined(__cplusplus) +extern "C" +# endif +BOOLEAN WINAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength); +#endif + + +C_word C_random_bytes(C_word buf, C_word size) +{ + int count = C_unfix(size); + int r = 0; + +#ifdef __OpenBSD__ + arc4random_buf(C_data_pointer(buf), count); +#elif defined(SYS_getrandom) && defined(__NR_getrandom) + do { + r = getrandom(C_data_pointer(buf), count, 0); + } while(r < 0 && (errno == EINTR)); +#elif defined(_WIN32) + if(!RtlGenRandom((PVOID)C_data_pointer(buf), (LONG)count)) + r = -1; +#else + return C_SCHEME_FALSE; +#endif + return C_fix(r); +} + + +/* WELL512 pseudo random number generator, see also: + https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear + http://lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf +*/ + +static C_u32 random_word(void) +{ + C_u32 a, b, c, d, r; + a = random_state[random_state_index]; + c = random_state[(random_state_index+13)&15]; + b = a^c^(a<<16)^(c<<15); + c = random_state[(random_state_index+9)&15]; + c ^= (c>>11); + a = random_state[random_state_index] = b^c; + d = a^((a<<5)&0xDA442D24UL); + random_state_index = (random_state_index + 15)&15; + a = random_state[random_state_index]; + random_state[random_state_index] = a^b^d^(a<<2)^(b<<18)^(c<<28); + r = random_state[random_state_index]; + return r; +} + + +C_regparm C_word C_random_fixnum(C_word n) +{ + C_u32 r = random_word(); + return C_fix(((double)r / 0xffffffffUL) * C_unfix(n)); +} + + +C_regparm C_word C_fcall +C_s_a_u_i_random_int(C_word **ptr, C_word n, C_word rn) +{ + C_uword *start, *end; + int len = integer_length_abs(rn); + C_word size = C_fix(C_BIGNUM_BITS_TO_DIGITS(len)); + C_word result = C_allocate_scratch_bignum(ptr, size, C_SCHEME_FALSE, C_SCHEME_FALSE); + C_u32 *p, mask; + + start = C_bignum_digits(result); + end = start + C_bignum_size(result); + + for(p = (C_u32 *)start; p < ((C_u32*)end - 1); ++p) { + *p = random_word(); + len -= sizeof(C_u32); + } + + *end = random_word() >> len; + return C_bignum_simplify(result); +} + + +C_word C_set_random_seed(C_word buf, C_word n) +{ + int i, nsu = C_unfix(n) / sizeof(C_u32); + int off = 0; + + for(i = 0; i < C_RANDOM_STATE_SIZE; ++i) { + if(off >= nsu) off = 0; + + random_state[ i ] = *((C_u32 *)C_data_pointer(buf) + off); + ++off; + } + + random_state_index = 0; + return C_SCHEME_FALSE; +}Trap