Hi,

Here is a simple implementation of what I think is xorshift128+, a
simple random number generator.  I have verified the first 10 outputs
against
https://github.com/AndreasMadsen/xorshift/blob/master/reference.c.

(define (xorshift128+ s0 s1)
  (define (u64 x) (logand #xffffFFFFffffFFFF x))
  (define (ulogxor x y) (u64 (logxor x y)))
  (define (ulsh x y) (u64 (ash x y)))
  (define (ursh x y) (u64 (ash x (- y))))
  (define (uadd x y) (u64 (+ x y)))
  (let ((state (u64vector s0 s1)))
    (lambda ()
      (let* ((x (u64vector-ref state 0))
             (y (u64vector-ref state 1))
             (x* y)
             (y* (ulogxor x (ulsh x 23)))
             (y* (ulogxor y* (ursh y* 17)))
             (y* (ulogxor y* (ulogxor y (ursh y 26)))))
        (u64vector-set! state 0 x*)
        (u64vector-set! state 1 y*)
        (let ((res (uadd y* y)))
          (values (logand res #xffffFFFF) (ash res -32)))))))

This function returns 2 32-bit values instead of one 64-bit value, to
avoid allocating bignums.  In fact with the latest Guile master, this
function does not allocate at all.  It can produce around 20M random
numbers per second on this old desktop.

The disassembly looks like:

   ----------------------------------------
   Disassembly of anonymous procedure at #x7fdb8320f168:

      0    (assert-nargs-ee/locals 1 5)    ;; 6 slots (0 args)   at 
/tmp/xorshift128+.scm:8:4
      1    (load-u64 4 0 0)                                      at 
/tmp/xorshift128+.scm:9:16
      4    (free-ref 5 5 0)                ;; free var 0
      6    (bv-u64-ref 3 5 4)              
      7    (load-u64 2 0 8)                                      at 
/tmp/xorshift128+.scm:10:16
     10    (bv-u64-ref 1 5 2)              
     11    (ulsh/immediate 0 3 23)                               at 
/tmp/xorshift128+.scm:4:26
     12    (ulogxor 3 3 0)                                       at 
/tmp/xorshift128+.scm:3:29
     13    (ursh/immediate 0 3 17)                               at 
/tmp/xorshift128+.scm:5:26
     14    (ulogxor 3 3 0)                                       at 
/tmp/xorshift128+.scm:3:29
     15    (ursh/immediate 0 1 26)                               at 
/tmp/xorshift128+.scm:5:26
     16    (ulogxor 0 1 0)                                       at 
/tmp/xorshift128+.scm:3:29
     17    (ulogxor 3 3 0)                 
     18    (bv-u64-set! 5 4 1)                                   at 
/tmp/xorshift128+.scm:15:8
     19    (bv-u64-set! 5 2 3)                                   at 
/tmp/xorshift128+.scm:16:8
     20    (uadd 5 3 1)                                          at 
/tmp/xorshift128+.scm:6:26
     21    (load-u64 4 0 4294967295)                             at 
/tmp/xorshift128+.scm:18:18
     24    (ulogand 4 5 4)                 
     25    (u64->scm 4 4)                  
     26    (ursh/immediate 5 5 32)                               at 
/tmp/xorshift128+.scm:18:42
     27    (u64->scm 3 5)                  
     28    (return-values 3)               ;; 2 values           at 
/tmp/xorshift128+.scm:18:10

As you can see, all unboxed operations.  To get this, I had to do a few
things to Guile:

 * Introduce unboxed logxor.

 * Compute the set of bits needed in any integer result, as a backwards
   flow problem.

 * Use that information to back-propagate constraints from
   (logand #xffffffffffffffff EXP) back to EXP, allowing EXP to unbox
   even if its output is out of the u64 range.

 * Elide (logand #xffffffffffffffff EXP) once EXP has been unboxed.

Regards,

Andy

Reply via email to