#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#define NEED_newCONSTSUB
#define NEED_sv_2pv_flags
#include "ppport.h"
#if defined __GLIBC__ && defined __linux__
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/random.h>
#endif
#include "mtwist/mtwist.c"
#include "mtwist/randistrs.c"
/* Some guesswork for older Perls */
#ifndef NVMANTBITS
#if NVSIZE <= 4
#define NVMANTBITS 23
#elif NVSIZE <= 8
#define NVMANTBITS 52
#elif defined(USE_QUADMATH)
#define NVMANTBITS 112
#elif defined(__LDBL_MANT_DIG__)
#define NVMANTBITS __LDBL_MANT_DIG__
#else
#define NVMANTBITS 64
#endif
#endif
#ifdef UINT64_MAX
#define HAS_UINT64_T 1
#else
#define HAS_UINT64_T 0
#endif
#define MT_HAS_INT128 defined(UINT64_MAX) && defined(__SIZEOF_INT128__)
#define MT_USE_QUADMATH NVMANTBITS > 64 && defined(USE_QUADMATH)
#define MT_USE_LONG_DOUBLE NVMANTBITS > 53 && defined(HAS_LONG_DOUBLE) && defined(USE_LONG_DOUBLE)
typedef union {
double dbl;
char str[8];
uint32_t i32[2];
#if HAS_UINT64_T
uint64_t i64;
#endif
} int2dbl;
#define I2D_SIZE sizeof(int2dbl)
#if HAS_UINT64_T
/* based on libowfat */
static inline U8 fmt_uint64(char* dest, uint64_t u) {
U8 len, len2;
uint64_t tmp;
/* count digits */
for(len = 1, tmp = u; tmp > 9; tmp /= 10)
len++;
if (dest) {
len2 = len;
dest += len;
do {
*--dest = (char)((u%10) + '0');
u /= 10;
} while (--len2);
}
return len;
}
#if IVSIZE < 8
/* newSVpvf doesn't handle "%llu" correctly. */
static SV* svpv_uint64(uint64_t u) {
SV* retval;
char* buf;
size_t length;
const size_t bufsize = 24;
dTHX;
Newxc(buf, bufsize, char, char);
if (!buf)
return NULL;
retval = newSV(0);
if (!retval) {
Safefree(buf);
return NULL;
}
length = fmt_uint64(buf, u);
sv_upgrade(retval, SVt_PV);
SvCUR_set(retval, length);
SvLEN_set(retval, bufsize);
SvPV_set(retval, buf);
SvPOK_only(retval);
return retval;
}
#endif /* IVSIZE < 8 */
#endif /* UINT64_MAX */
#if MT_HAS_INT128
typedef unsigned __int128 mt_uint128_t;
static inline mt_uint128_t mts_u128rand(register mt_state* state) {
unsigned i;
union u128 {
uint32_t u32[4];
mt_uint128_t u128;
} rv;
uint32_t* u32 = rv.u32 + 4;
for (i = 4; i > 0; i--) {
if (state->stateptr <= 0)
mts_refresh(state);
*--u32 = state->statevec[--state->stateptr];
MT_TEMPER(*u32);
}
return rv.u128;
}
static inline mt_uint128_t mt_u128rand(void) {
return mts_u128rand(&mt_default_state);
}
/* 128-bit division and modulo are very time-consuming because there is no
* native 128-bit integer arithmetic. We speed it up a bit by dividing by 1e9
* instead of 10.
*/
static inline void fmt_nsec(char* dest, uint64_t u) {
if (dest && u < 1000000000) {
do {
*--dest = (char)((u%10) + '0');
u /= 10;
} while (u);
}
}
static inline U8 fmt_uint128(char* dest, mt_uint128_t u) {
U8 len, len2;
mt_uint128_t tmp;
uint64_t mod;
/* count digits */
for (len = 0, tmp = u; tmp > 999999999; tmp /= 1000000000)
len += 9;
len += fmt_uint64(NULL, (uint64_t)tmp);
if (dest) {
memset(dest, '0', len);
len2 = len;
dest += len;
while (1) {
mod = u % 1000000000;
if (mod)
fmt_nsec(dest, mod);
len2 -= 9;
if ((I8)len2 <= 0)
break;
dest -= 9;
u /= 1000000000;
}
}
return len;
}
static SV* svpv_uint128(mt_uint128_t u) {
SV* retval;
char* buf;
size_t length;
const size_t bufsize = 40;
dTHX;
Newxc(buf, bufsize, char, char);
if (!buf)
return NULL;
retval = newSV(0);
if (!retval) {
Safefree(buf);
return NULL;
}
length = fmt_uint128(buf, u);
sv_upgrade(retval, SVt_PV);
SvCUR_set(retval, length);
SvLEN_set(retval, bufsize);
SvPV_set(retval, buf);
SvPOK_only(retval);
return retval;
}
#endif
/*
* We copy the seeds from an array reference to a buffer so that mtwist can
* copy the buffer to another buffer. No wonder that computer power must double
* every two years ...
*
*
* We assume that mt_seeds[] has been initialized with zeros.
*/
static void get_seeds_from_av(AV* av_seeds, uint32_t* mt_seeds) {
SSize_t i;
SV** av_seed;
uint32_t had_nz = 0;
dTHX;
/*
* Two classic examples of badly chosen identifier names:
* av_len() does not return the array length but the top array index.
* MT_STATE_SIZE is not sizeof(mt_state) but the length of the state vector.
*/
i = av_len(av_seeds);
if (i >= MT_STATE_SIZE)
i = MT_STATE_SIZE - 1;
for (; i >= 0; i--)
if ((av_seed = av_fetch(av_seeds, i, 0)))
had_nz |= mt_seeds[i] = SvUV(*av_seed);
if (! had_nz)
croak("seedfull(): Need at least one non-zero seed value");
}
/*
* We calculate clock_gettime() in nanoseconds or gettimeofday() in
* microseconds XORed with a memory address and use the lower 32 bit as the
* seed.
*/
static uint32_t timeseed(mt_state* state) {
UV seed;
#ifdef CLOCK_MONOTONIC
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
seed = ts.tv_sec*1000000000 + ts.tv_nsec;
#else
I32 return_count;
/* Who invented those silly cryptic macro names? */
dTHX;
dSP;
PUSHMARK(SP);
return_count = call_pv("Time::HiRes::gettimeofday", G_ARRAY);
if (return_count != 2)
croak("Time::HiRes::gettimeofday() returned %d instead of 2 values",
return_count);
SPAGAIN;
seed = POPu;
seed += POPu * 1000000;
PUTBACK;
#endif
/* Hopefully Address Space Layout Randomization gives us some additional
randomness. */
seed ^= PTR2UV(&get_seeds_from_av);
if (state)
mts_seed32new(state, seed);
else
mt_seed32new(seed);
return seed;
}
#ifdef SYS_getrandom
static inline int mrmt_getrandom(void *buf, size_t buflen, unsigned flags) {
#if defined(__GLIBC__) && ( __GLIBC__ > 2 || __GLIBC_MINOR__ > 24 )
return getrandom(buf, buflen, flags);
#else
return syscall(SYS_getrandom, buf, buflen, flags);
#endif
}
#endif
static inline uint32_t devseed(mt_state* state, bool want_goodseed) {
#ifdef SYS_getrandom
uint32_t seed;
unsigned flags = want_goodseed ? GRND_RANDOM : 0;
while (mrmt_getrandom(&seed, sizeof(seed), flags) < 0) {}
if (state)
mts_seed32new(state, seed);
else
mt_seed32new(seed);
return seed;
#else
if (want_goodseed)
return state ? mts_goodseed(state) : mt_goodseed();
else
return state ? mts_seed(state) : mt_seed();
#endif
}
static inline uint32_t fastseed(mt_state* state) {
#ifdef WIN32
return timeseed(state);
#else
return devseed(state, 0);
#endif
}
static inline uint32_t goodseed(mt_state* state) {
#ifdef WIN32
return timeseed(state);
#else
return devseed(state, 1);
#endif
}
static inline void bestseed(mt_state* state) {
#ifdef WIN32
timeseed(state);
#else
state ? mts_bestseed(state) : mt_bestseed();
#endif
}
static inline void seedfull(mt_state* state, AV* seeds) {
uint32_t mt_seeds[MT_STATE_SIZE] = {0};
get_seeds_from_av(seeds, mt_seeds);
if (state)
mts_seedfull(state, mt_seeds);
else
mt_seedfull(mt_seeds);
}
static inline uint32_t srand50c(mt_state* state, uint32_t* seed) {
if (seed == NULL)
return fastseed(state);
if (state)
mts_seed32new(state, (uint32_t)*seed);
else
mt_seed32new((uint32_t)*seed);
return *seed;
}
static inline int2dbl rd_double(mt_state* state) {
int2dbl i2d;
#if HAS_UINT64_T
i2d.i64 = state ? mts_llrand(state) : mt_llrand();
#else
if (state) {
i2d.i32[0] = mts_lrand(state);
i2d.i32[1] = mts_lrand(state);
}
else {
i2d.i32[0] = mt_lrand();
i2d.i32[1] = mt_lrand();
}
#endif
return i2d;
}
static SV* randstr(mt_state* state, size_t length) {
size_t bufsize, i;
int2dbl* buf;
SV* retval;
dTHX;
if (length == 0)
return newSVpvn("", 0);
/* Make bufsize an integer multiple of I2D_SIZE */
bufsize = length + ((-length) % I2D_SIZE);
if (bufsize < length)
return NULL;
Newxc(buf, bufsize, char, int2dbl);
if (!buf)
return NULL;
i = bufsize / I2D_SIZE;
buf += i;
do {
*--buf = rd_double(state);
} while (--i);
retval = newSV(0);
if (!retval) {
Safefree(buf);
return NULL;
}
sv_upgrade(retval, SVt_PV);
SvCUR_set(retval, length);
SvLEN_set(retval, bufsize);
SvPV_set(retval, (char*)buf);
SvPOK_only(retval);
return retval;
}
static FILE* open_file_from_sv(SV* file_sv, char* mode, PerlIO** pio) {
FILE* fh = NULL;
dTHX;
if (! SvOK(file_sv)) {
/* file_sv is undef */
warn("Filename or handle expected");
}
else if (SvROK(file_sv) && SvTYPE(SvRV(file_sv)) == SVt_PVGV) {
/* file_sv is a Perl filehandle */
*pio = IoIFP(sv_2io(file_sv));
fh = PerlIO_exportFILE(*pio, NULL);
}
else {
/* file_sv is a filename */
fh = fopen(SvPV_nolen(file_sv), mode);
}
return fh;
}
static int savestate(mt_state* state, SV* file_sv) {
PerlIO* pio = NULL;
FILE* fh = NULL;
int RETVAL = 0;
dTHX;
fh = open_file_from_sv(file_sv, "w", &pio);
if (fh) {
RETVAL = state ? mts_savestate(fh, state) : mt_savestate(fh);
if (pio) {
fflush(fh);
PerlIO_releaseFILE(pio, fh);
}
else
fclose(fh);
}
return RETVAL;
}
static int loadstate(mt_state* state, SV* file_sv) {
PerlIO* pio = NULL;
FILE* fh = NULL;
int RETVAL = 0;
dTHX;
fh = open_file_from_sv(file_sv, "r", &pio);
if (fh) {
RETVAL = state ? mts_loadstate(fh, state) : mt_loadstate(fh);
if (pio)
PerlIO_releaseFILE(pio, fh);
else
fclose(fh);
}
return RETVAL;
}
static void set_state_from_sv(SV* sv_state, mt_state* state) {
STRLEN len;
dTHX;
if (!SvPOK(sv_state))
croak("State must be a string");
len = SvCUR(sv_state);
if (len != sizeof(mt_state))
croak("Need exactly %d state bytes, not %d", sizeof(mt_state), len);
*state = *(mt_state*)SvPV_nolen(sv_state);
if (state->stateptr < 0 || state->stateptr > MT_STATE_SIZE) {
warn("stateptr value %d outside valid range [0, %d], using 0 instead",
state->stateptr, MT_STATE_SIZE);
state->stateptr = 0;
}
}
MODULE = Math::Random::MTwist PACKAGE = Math::Random::MTwist
PROTOTYPES: ENABLE
mt_state*
new_state(char* CLASS)
CODE:
Newxz(RETVAL, 1, mt_state);
if (RETVAL == NULL)
croak("Could not allocate state memory");
OUTPUT:
RETVAL
void
DESTROY(mt_state* state)
PPCODE:
Safefree(state);
UV
seed32(mt_state* state, uint32_t seed)
CODE:
mts_seed32new(state, seed);
RETVAL = seed;
OUTPUT:
RETVAL
UV
_seed32(uint32_t seed)
CODE:
mt_seed32new(seed);
RETVAL = seed;
OUTPUT:
RETVAL
UV
srand(mt_state* state, uint32_t seed = 0)
CODE:
RETVAL = srand50c(state, items == 1 ? NULL : &seed);
OUTPUT:
RETVAL
UV
_srand(uint32_t seed = 0)
CODE:
RETVAL = srand50c(NULL, items == 0 ? NULL : &seed);
OUTPUT:
RETVAL
UV
timeseed(mt_state* state)
UV
_timeseed()
CODE:
RETVAL = timeseed(NULL);
OUTPUT:
RETVAL
UV
fastseed(mt_state* state)
UV
_fastseed()
CODE:
RETVAL = fastseed(NULL);
OUTPUT:
RETVAL
UV
goodseed(mt_state* state)
UV
_goodseed()
CODE:
RETVAL = goodseed(NULL);
OUTPUT:
RETVAL
void
bestseed(mt_state* state)
void
_bestseed()
PPCODE:
bestseed(NULL);
void
seedfull(mt_state* state, AV* seeds)
PPCODE:
seedfull(state, seeds);
void
_seedfull(AV* seeds)
PPCODE:
seedfull(NULL, seeds);
SV*
irand32(mt_state* state);
ALIAS:
irand64 = 1
irand = 2
CODE:
#if IVSIZE >= 8
if (ix)
RETVAL = newSVuv(mts_llrand(state));
#elif defined(UINT64_MAX)
if (ix)
RETVAL = svpv_uint64(mts_llrand(state));
#else
if (ix == 1)
XSRETURN_UNDEF;
#endif
else
RETVAL = newSVuv(state ? mts_lrand(state) : mt_lrand());
OUTPUT:
RETVAL
SV*
_irand32();
ALIAS:
_irand64 = 1
_irand = 2
CODE:
#if IVSIZE >= 8
if (ix)
RETVAL = newSVuv(mt_llrand());
#elif defined(UINT64_MAX)
if (ix)
RETVAL = svpv_uint64(mt_llrand());
#else
if (ix == 1)
XSRETURN_UNDEF;
#endif
else
RETVAL = newSVuv(mt_lrand());
OUTPUT:
RETVAL
SV*
irand128(mt_state* state);
CODE:
#if IVSIZE >= 16
RETVAL = newSVuv(mts_u128rand(state));
#elif MT_HAS_INT128
RETVAL = svpv_uint128(mts_u128rand(state));
#else
XSRETURN_UNDEF;
#endif
OUTPUT:
RETVAL
SV*
_irand128();
CODE:
#if IVSIZE >= 16
RETVAL = newSVuv(mt_u128rand());
#elif MT_HAS_INT128
RETVAL = svpv_uint128(mt_u128rand());
#else
XSRETURN_UNDEF;
#endif
OUTPUT:
RETVAL
NV
rand(mt_state* state, NV bound = 0)
ALIAS:
rand32 = 1
CODE:
if (NVMANTBITS <= 32 || ix)
RETVAL = mts_drand(state);
else
#if NVMANTBITS <= 64
RETVAL = mts_ldrand(state);
#else
RETVAL = mts_lldrand(state);
#endif
if (bound)
RETVAL *= bound;
OUTPUT:
RETVAL
NV
_rand(NV bound = 0)
ALIAS:
_rand32 = 1
CODE:
if (NVMANTBITS <= 32 || ix)
RETVAL = mt_drand();
else
#if NVMANTBITS <= 64
RETVAL = mt_ldrand();
#else
RETVAL = mt_lldrand();
#endif
if (bound)
RETVAL *= bound;
OUTPUT:
RETVAL
### Interestingly, randstr() is faster than rd_double(2).
SV*
randstr(mt_state* state, STRLEN length = I2D_SIZE)
CODE:
RETVAL = randstr(state, length);
OUTPUT:
RETVAL
SV*
_randstr(STRLEN length = I2D_SIZE)
CODE:
RETVAL = randstr(NULL, length);
OUTPUT:
RETVAL
#define RETURN_I2D(have_index) { \
if (have_index) { \
switch(index) { \
case 0: \
XSRETURN_NV(i2d.dbl); \
case 1: \
if (IVSIZE >= 8) \
XSRETURN_UV(i2d.i64); \
else \
XSRETURN_UNDEF; \
case 2: \
XSRETURN_PVN(i2d.str, 8); \
default: \
XSRETURN_UNDEF; \
} \
} \
else { \
mPUSHn(i2d.dbl); \
if (GIMME_V == G_ARRAY) { \
EXTEND(SP, 2); \
if (IVSIZE >= 8) \
mPUSHu(i2d.i64); \
else \
PUSHs(&PL_sv_undef); \
mPUSHp(i2d.str, 8); \
} \
} \
} \
void
rd_double(mt_state* state, int index = 0)
PREINIT:
int2dbl i2d;
PPCODE:
i2d = rd_double(state);
RETURN_I2D(items > 1);
void
_rd_double(int index = 0)
PREINIT:
int2dbl i2d;
PPCODE:
i2d = rd_double(NULL);
RETURN_I2D(items != 0);
IV
rd_iuniform32(mt_state* state, IV lower, IV upper);
ALIAS:
rd_iuniform64 = 1
rd_iuniform = 2
CODE:
#if IVSIZE >= 8
if (ix)
RETVAL = rds_liuniform(state, lower, upper);
#else
if (ix == 1)
XSRETURN_UNDEF;
#endif
else
RETVAL = rds_iuniform(state, (int32_t)lower, (int32_t)upper);
OUTPUT:
RETVAL
IV
_rd_iuniform32(IV lower, IV upper);
ALIAS:
_rd_iuniform64 = 1
_rd_iuniform = 2
CODE:
#if IVSIZE >= 8
if (ix)
RETVAL = rd_liuniform(lower, upper);
#else
if (ix == 1)
XSRETURN_UNDEF;
#endif
else
RETVAL = rd_iuniform((int32_t)lower, (int32_t)upper);
OUTPUT:
RETVAL
NV
rd_uniform(mt_state* state, NV lower, NV upper);
ALIAS:
rd_luniform = 1
CODE:
RETVAL = (ix == 0) ? rds_uniform(state, lower, upper)
: rds_luniform(state, lower, upper);
OUTPUT:
RETVAL
NV
_rd_uniform(NV lower, NV upper);
ALIAS:
_rd_luniform = 1
CODE:
RETVAL = (ix == 0) ? rd_uniform(lower, upper)
: rd_luniform(lower, upper);
OUTPUT:
RETVAL
NV
rd_exponential(mt_state* state, NV mean)
ALIAS:
rd_lexponential = 1
CODE:
RETVAL = (ix == 0) ? rds_exponential(state, mean)
: rds_lexponential(state, mean);
OUTPUT:
RETVAL
NV
_rd_exponential(NV mean)
ALIAS:
_rd_lexponential = 1
CODE:
RETVAL = (ix == 0) ? rd_exponential(mean)
: rd_lexponential(mean);
OUTPUT:
RETVAL
NV
rd_erlang(mt_state* state, IV k, NV mean)
ALIAS:
rd_lerlang = 1
CODE:
RETVAL = (ix == 0) ? rds_erlang(state, k, mean)
: rds_lerlang(state, k, mean);
OUTPUT:
RETVAL
NV
_rd_erlang(int k, NV mean)
ALIAS:
_rd_lerlang = 1
CODE:
RETVAL = (ix == 0) ? rd_erlang(k, mean)
: rd_lerlang(k, mean);
OUTPUT:
RETVAL
NV
rd_weibull(mt_state* state, NV shape, NV scale)
ALIAS:
rd_lweibull = 1
rd_lognormal = 2
rd_llognormal = 3
CODE:
switch (ix) {
case 0: RETVAL = rds_weibull(state, shape, scale); break;
case 1: RETVAL = rds_lweibull(state, shape, scale); break;
case 2: RETVAL = rds_lognormal(state, shape, scale); break;
default: RETVAL = rds_llognormal(state, shape, scale);
}
OUTPUT:
RETVAL
NV
_rd_weibull(NV shape, NV scale)
ALIAS:
_rd_lweibull = 1
_rd_lognormal = 2
_rd_llognormal = 3
CODE:
switch (ix) {
case 0: RETVAL = rd_weibull(shape, scale); break;
case 1: RETVAL = rd_lweibull(shape, scale); break;
case 2: RETVAL = rd_lognormal(shape, scale); break;
default: RETVAL = rd_llognormal(shape, scale);
}
OUTPUT:
RETVAL
NV
rd_normal(mt_state* state, NV mean, NV sigma)
ALIAS:
rd_lnormal = 1
CODE:
RETVAL = (ix == 0) ? rds_normal(state, mean, sigma)
: rds_lnormal(state, mean, sigma);
OUTPUT:
RETVAL
NV
_rd_normal(NV mean, NV sigma)
ALIAS:
_rd_lnormal = 1
CODE:
RETVAL = (ix == 0) ? rd_normal(mean, sigma)
: rd_lnormal(mean, sigma);
OUTPUT:
RETVAL
NV
rd_triangular(mt_state* state, NV lower, NV upper, NV mode)
ALIAS:
rd_ltriangular = 1
CODE:
RETVAL = (ix == 0) ? rds_triangular(state, lower, upper, mode)
: rds_ltriangular(state, lower, upper, mode);
OUTPUT:
RETVAL
NV
_rd_triangular(NV lower, NV upper, NV mode)
ALIAS:
_rd_ltriangular = 1
CODE:
RETVAL = (ix == 0) ? rd_triangular(lower, upper, mode)
: rd_ltriangular(lower, upper, mode);
OUTPUT:
RETVAL
int
savestate(mt_state* state, SV* file_sv)
int
_savestate(SV* file_sv)
CODE:
RETVAL = savestate(NULL, file_sv);
OUTPUT:
RETVAL
int
loadstate(mt_state* state, SV* file_sv)
int
_loadstate(SV* file_sv)
CODE:
RETVAL = loadstate(NULL, file_sv);
OUTPUT:
RETVAL
SV*
getstate(mt_state* state)
CODE:
RETVAL = newSVpvn((char*)state, sizeof(mt_state));
OUTPUT:
RETVAL
SV*
_getstate()
CODE:
RETVAL = newSVpvn((char*)&mt_default_state, sizeof(mt_state));
OUTPUT:
RETVAL
void
setstate(mt_state* state, SV* sv_state)
PPCODE:
set_state_from_sv(sv_state, state);
void
_setstate(SV* sv_state)
PPCODE:
set_state_from_sv(sv_state, &mt_default_state);
BOOT:
{
HV *stash;
stash = gv_stashpv("Math::Random::MTwist", TRUE);
newCONSTSUB(stash, "HAS_UINT64_T", newSViv(HAS_UINT64_T));
newCONSTSUB(stash, "NVMANTBITS", newSViv(NVMANTBITS));
}
# vim: set ts=2 sw=2 sts=2 expandtab: