diff --git a/inform6/Tests/Assistants/dumb-glulx/glulxe/LICENSE b/inform6/Tests/Assistants/dumb-glulx/glulxe/LICENSE index 3bda2fad2..674194492 100755 --- a/inform6/Tests/Assistants/dumb-glulx/glulxe/LICENSE +++ b/inform6/Tests/Assistants/dumb-glulx/glulxe/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 1999-2016, Andrew Plotkin +Copyright (c) 1999-2023, Andrew Plotkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/inform6/Tests/Assistants/dumb-glulx/glulxe/Makefile b/inform6/Tests/Assistants/dumb-glulx/glulxe/Makefile index 5ece3ef4c..ef9f643ff 100755 --- a/inform6/Tests/Assistants/dumb-glulx/glulxe/Makefile +++ b/inform6/Tests/Assistants/dumb-glulx/glulxe/Makefile @@ -27,11 +27,21 @@ GLKMAKEFILE = Make.cheapglk #GLKLIBDIR = ../gtkglk #GLKMAKEFILE = ../Make.gtkglk +# Also set an appropriate OS config in OPTIONS, below. +# -DOS_MAC for MacOS +# -DOS_WINDOWS for Windows +# -DOS_UNIX for Unix/Linux +# For OS_UNIX, you probably also want to set a random number generator +# option. These are unfortunately not very standardized across Unixes. +# We recommend -DUNIX_RAND_GETRANDOM on Linux and -DUNIX_RAND_ARC4 +# on NetBSD. +# (MacOS always uses ARC4, in case you were wondering.) + # Pick a C compiler. CC = cc #CC = gcc -OPTIONS = -g -Wall -Wmissing-prototypes -Wno-unused -DOS_UNIX +OPTIONS = -g -Wall -Wmissing-prototypes -Wno-unused -DOS_MAC # Locate the libxml2 library. You only need these lines if you are using # the VM_DEBUGGER option. If so, uncomment these and set appropriately. diff --git a/inform6/Tests/Assistants/dumb-glulx/glulxe/README.md b/inform6/Tests/Assistants/dumb-glulx/glulxe/README.md index 6c7a513ab..876c520b5 100755 --- a/inform6/Tests/Assistants/dumb-glulx/glulxe/README.md +++ b/inform6/Tests/Assistants/dumb-glulx/glulxe/README.md @@ -1,6 +1,6 @@ # Glulxe: the Glulx VM interpreter -- Version 0.6.0 +- Version 0.6.1 - Designed by Andrew Plotkin - [Glulx home page][glulx] @@ -14,8 +14,11 @@ the [Glk home page][glk]. The Unix Makefile that comes with this package is designed to link any of the Unix libraries (CheapGlk, GlkTerm, RemGlk, etc.) You'll have to go -into the Makefile and set three variables to find the library. There are -instructions at the top of the Makefile. Then just type +into the Makefile and set three variables to find the library. You'll also +want to set the appropriate OS_* constants on the OPTIONS line. There are +instructions at the top of the Makefile. + +Then just type make glulxe @@ -117,6 +120,15 @@ display the update, and then (without delay) exit. ## Version +0.6.1 (Oct 9, 2023) + +- Added a --rngseed argument to fix the RNG setting from the command + line. +- Changed the built-in RNG to xoshiro**. Added configuration defs to + use a native OS RNG where possible. +- Fixed obscure corner case in pow(), powf() on some platforms. +- Configuration improvements for Windows. + 0.6.0 (Jun 25, 2022): - Added @hasundo and @discardundo opcodes. (Glulx spec 3.1.3.) @@ -276,7 +288,7 @@ display the update, and then (without delay) exit. ## Permissions -The source code in this package is copyright 1999-2016 by Andrew Plotkin. +The source code in this package is copyright 1999-2023 by Andrew Plotkin. It is distributed under the MIT license; see the "[LICENSE][]" file. [LICENSE]: ./LICENSE diff --git a/inform6/Tests/Assistants/dumb-glulx/glulxe/exec.c b/inform6/Tests/Assistants/dumb-glulx/glulxe/exec.c index 5a8fa26b2..4016614da 100755 --- a/inform6/Tests/Assistants/dumb-glulx/glulxe/exec.c +++ b/inform6/Tests/Assistants/dumb-glulx/glulxe/exec.c @@ -1198,7 +1198,7 @@ void execute_loop() case op_dpow: vald1 = decode_double(inst[0].value, inst[1].value); vald2 = decode_double(inst[2].value, inst[3].value); - encode_double(pow(vald1, vald2), &val0hi, &val0lo); + encode_double(glulx_pow(vald1, vald2), &val0hi, &val0lo); store_operand(inst[4].desttype, inst[4].value, val0lo); store_operand(inst[5].desttype, inst[5].value, val0hi); break; diff --git a/inform6/Tests/Assistants/dumb-glulx/glulxe/gestalt.c b/inform6/Tests/Assistants/dumb-glulx/glulxe/gestalt.c index 2ad97626b..2b6f9da52 100755 --- a/inform6/Tests/Assistants/dumb-glulx/glulxe/gestalt.c +++ b/inform6/Tests/Assistants/dumb-glulx/glulxe/gestalt.c @@ -15,7 +15,7 @@ glui32 do_gestalt(glui32 val, glui32 val2) return 0x00030103; /* Glulx spec version 3.1.3 */ case gestulx_TerpVersion: - return 0x00000600; /* Glulxe version 0.6.0 */ + return 0x00000601; /* Glulxe version 0.6.1 */ case gestulx_ResizeMem: #ifdef FIXED_MEMSIZE @@ -25,7 +25,9 @@ glui32 do_gestalt(glui32 val, glui32 val2) #endif /* FIXED_MEMSIZE */ case gestulx_Undo: - return 1; /* We can handle saveundo and restoreundo. */ + if (max_undo_level > 0) + return 1; /* We can handle saveundo and restoreundo. */ + return 0; /* Got "--undo 0", so nope. */ case gestulx_IOSystem: switch (val2) { diff --git a/inform6/Tests/Assistants/dumb-glulx/glulxe/glkop.c b/inform6/Tests/Assistants/dumb-glulx/glulxe/glkop.c index 79d2bc9d6..9f7b8018e 100755 --- a/inform6/Tests/Assistants/dumb-glulx/glulxe/glkop.c +++ b/inform6/Tests/Assistants/dumb-glulx/glulxe/glkop.c @@ -80,6 +80,7 @@ #define ReleaseVMUstring(ptr) \ (free_temp_ustring(ptr)) +#include #include "glk.h" #include "glulxe.h" #include "gi_dispa.h" @@ -178,6 +179,7 @@ static char *get_game_id(void); int init_dispatch() { int ix; + int randish; /* What with one thing and another, this *could* be called more than once. We only need to allocate the tables once. */ @@ -196,9 +198,10 @@ int init_dispatch() * sizeof(classtable_t *)); if (!classes) return FALSE; - + + randish = time(NULL) % 101; for (ix=0; ix +#include + +/* Allocate a chunk of memory. */ +void *glulx_malloc(glui32 len) +{ + return malloc(len); +} + +/* Resize a chunk of memory. This must follow ANSI rules: if the + size-change fails, this must return NULL, but the original chunk + must remain unchanged. */ +void *glulx_realloc(void *ptr, glui32 len) +{ + return realloc(ptr, len); +} + +/* Deallocate a chunk of memory. */ +void glulx_free(void *ptr) +{ + free(ptr); +} + +/* Use our xoshiro128** as the native RNG, seeded from the clock. */ +#define RAND_SET_SEED() (xo_seed_random(time(NULL))) +#define RAND_GET() (xo_random()) + +#endif /* OS_STDC */ #ifdef OS_UNIX @@ -39,7 +79,7 @@ void *glulx_malloc(glui32 len) } /* Resize a chunk of memory. This must follow ANSI rules: if the - size-change fails, this must return NULL, but the original chunk + size-change fails, this must return NULL, but the original chunk must remain unchanged. */ void *glulx_realloc(void *ptr, glui32 len) { @@ -52,34 +92,45 @@ void glulx_free(void *ptr) free(ptr); } -/* Set the random-number seed; zero means use as random a source as - possible. */ -void glulx_setrandom(glui32 seed) +#ifdef UNIX_RAND_ARC4 + +/* Use arc4random() as the native RNG. It doesn't need to be seeded. */ +#define RAND_SET_SEED() (0) +#define RAND_GET() (arc4random()) + +#elif UNIX_RAND_GETRANDOM + +/* Use xoshiro128** as the native RNG, seeded from getrandom(). */ +#include + +static void rand_set_seed(void) { - if (seed == 0) - seed = time(NULL); -#ifdef COMPILE_RANDOM_CODE - lo_seed_random(seed); -#else - srandom(seed); -#endif + glui32 seeds[4]; + int res = getrandom(seeds, 4*sizeof(glui32), 0); + if (res < 0) { + /* Error; fall back to the clock. */ + xo_seed_random(time(NULL)); + } + else { + xo_seed_random_4(seeds[0], seeds[1], seeds[2], seeds[3]); + } } -/* Return a random number in the range 0 to 2^32-1. */ -glui32 glulx_random() -{ -#ifdef COMPILE_RANDOM_CODE - return (lo_random() << 16) ^ lo_random(); -#else - return (random() << 16) ^ random(); -#endif -} +#define RAND_SET_SEED() (rand_set_seed()) +#define RAND_GET() (xo_random()) + +#else /* UNIX_RAND_... */ + +/* Use our xoshiro128** as the native RNG, seeded from the clock. */ +#define RAND_SET_SEED() (xo_seed_random(time(NULL))) +#define RAND_GET() (xo_random()) + +#endif /* UNIX_RAND_... */ #endif /* OS_UNIX */ #ifdef OS_MAC -/* The Glk library uses malloc/free liberally, so we might as well also. */ #include /* Allocate a chunk of memory. */ @@ -89,7 +140,7 @@ void *glulx_malloc(glui32 len) } /* Resize a chunk of memory. This must follow ANSI rules: if the - size-change fails, this must return NULL, but the original chunk + size-change fails, this must return NULL, but the original chunk must remain unchanged. */ void *glulx_realloc(void *ptr, glui32 len) { @@ -102,24 +153,17 @@ void glulx_free(void *ptr) free(ptr); } -/* Return a random number in the range 0 to 2^32-1. */ -glui32 glulx_random() -{ - return (lo_random() << 16) ^ lo_random(); -} - -/* Set the random-number seed; zero means use as random a source as - possible. */ -void glulx_setrandom(glui32 seed) -{ - if (seed == 0) - seed = TickCount() ^ Random(); - lo_seed_random(seed); -} +/* Use arc4random() as the native RNG. It doesn't need to be seeded. */ +#define RAND_SET_SEED() (0) +#define RAND_GET() (arc4random()) #endif /* OS_MAC */ -#ifdef WIN32 +#ifdef OS_WINDOWS + +#ifdef _MSC_VER /* For Visual C++, get rand_s() */ +#define _CRT_RAND_S +#endif #include #include @@ -131,7 +175,7 @@ void *glulx_malloc(glui32 len) } /* Resize a chunk of memory. This must follow ANSI rules: if the - size-change fails, this must return NULL, but the original chunk + size-change fails, this must return NULL, but the original chunk must remain unchanged. */ void *glulx_realloc(void *ptr, glui32 len) { @@ -144,63 +188,128 @@ void glulx_free(void *ptr) free(ptr); } +#ifdef _MSC_VER /* Visual C++ */ + +/* Do nothing, as rand_s() has no seed. */ +static void msc_srandom() +{ +} + +/* Use the Visual C++ function rand_s() as the native RNG. + This calls the OS function RtlGetRandom(). */ +static glui32 msc_random() +{ + glui32 value; + rand_s(&value); + return value; +} + +#define RAND_SET_SEED() (msc_srandom()) +#define RAND_GET() (msc_random()) + +#else /* Other Windows compilers */ + +/* Use our xoshiro128** as the native RNG, seeded from the clock. */ +#define RAND_SET_SEED() (xo_seed_random(time(NULL))) +#define RAND_GET() (xo_random()) + +#endif + +#endif /* OS_WINDOWS */ + + +/* If no native RNG is defined above, use the xoshiro128** implementation. */ +#ifndef RAND_SET_SEED +#define RAND_SET_SEED() (xo_seed_random(time(NULL))) +#define RAND_GET() (xo_random()) +#endif /* RAND_SET_SEED */ + +static int rand_use_native = TRUE; + +/* Set the random-number seed, and also select which RNG to use. +*/ +void glulx_setrandom(glui32 seed) +{ + if (seed == 0) { + rand_use_native = TRUE; + RAND_SET_SEED(); + } + else { + rand_use_native = FALSE; + xo_seed_random(seed); + } +} + /* Return a random number in the range 0 to 2^32-1. */ glui32 glulx_random() { - return (lo_random() << 16) ^ lo_random(); + if (rand_use_native) { + return RAND_GET(); + } + else { + return xo_random(); + } } -__declspec(dllimport) unsigned long __stdcall GetTickCount(void); -/* Set the random-number seed; zero means use as random a source as -possible. */ -void glulx_setrandom(glui32 seed) +/* This is the "xoshiro128**" random-number generator and seed function. + Adapted from: https://prng.di.unimi.it/xoshiro128starstar.c + About this algorithm: https://prng.di.unimi.it/ +*/ +static uint32_t xo_table[4]; + +static void xo_seed_random_4(glui32 seed0, glui32 seed1, glui32 seed2, glui32 seed3) { - if (seed == 0) - seed = GetTickCount() ^ time(NULL); - lo_seed_random(seed); + /* Set up the 128-bit state from four integers. Use this if you can get + four high-quality random values. */ + xo_table[0] = seed0; + xo_table[1] = seed1; + xo_table[2] = seed2; + xo_table[3] = seed3; } -#endif /* WIN32 */ - -#ifdef COMPILE_RANDOM_CODE - -/* Here is a pretty standard random-number generator and seed function. */ -static glui32 lo_random(void); -static void lo_seed_random(glui32 seed); -static glui32 rand_table[55]; /* State for the RNG. */ -static int rand_index1, rand_index2; - -static glui32 lo_random() +static void xo_seed_random(glui32 seed) { - rand_index1 = (rand_index1 + 1) % 55; - rand_index2 = (rand_index2 + 1) % 55; - rand_table[rand_index1] = rand_table[rand_index1] - rand_table[rand_index2]; - return rand_table[rand_index1]; + int ix; + /* Set up the 128-bit state from a single 32-bit integer. We rely + on a different RNG, SplitMix32. This isn't high-quality, but we + just need to get a bunch of bits into xo_table. */ + for (ix=0; ix<4; ix++) { + seed += 0x9E3779B9; + glui32 s = seed; + s ^= s >> 15; + s *= 0x85EBCA6B; + s ^= s >> 13; + s *= 0xC2B2AE35; + s ^= s >> 16; + xo_table[ix] = s; + } } -static void lo_seed_random(glui32 seed) +static glui32 xo_random(void) { - glui32 k = 1; - int i, loop; + /* I've inlined the utility function: + rotl(x, k) => (x << k) | (x >> (32 - k)) + */ + + const uint32_t t1x5 = xo_table[1] * 5; + const uint32_t result = ((t1x5 << 7) | (t1x5 >> (32-7))) * 9; - rand_table[54] = seed; - rand_index1 = 0; - rand_index2 = 31; - - for (i = 0; i < 55; i++) { - int ii = (21 * i) % 55; - rand_table[ii] = k; - k = seed - k; - seed = rand_table[ii]; - } - for (loop = 0; loop < 4; loop++) { - for (i = 0; i < 55; i++) - rand_table[i] = rand_table[i] - rand_table[ (1 + i + 30) % 55]; - } + const uint32_t t1s9 = xo_table[1] << 9; + + xo_table[2] ^= xo_table[0]; + xo_table[3] ^= xo_table[1]; + xo_table[1] ^= xo_table[2]; + xo_table[0] ^= xo_table[3]; + + xo_table[2] ^= t1s9; + + const uint32_t t3 = xo_table[3]; + xo_table[3] = ((t3 << 11) | (t3 >> (32-11))); + + return result; } -#endif /* COMPILE_RANDOM_CODE */ #include @@ -216,8 +325,6 @@ void glulx_sort(void *addr, int count, int size, #ifdef FLOAT_SUPPORT #include -#ifdef FLOAT_COMPILE_SAFER_POWF - /* This wrapper handles all special cases, even if the underlying powf() function doesn't. */ gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2) @@ -231,14 +338,20 @@ gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2) return powf(val1, val2); } -#else /* FLOAT_COMPILE_SAFER_POWF */ +#endif /* FLOAT_SUPPORT */ -/* This is the standard powf() function, unaltered. */ -gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2) +#ifdef DOUBLE_SUPPORT + +/* Same for pow(). */ +extern gfloat64 glulx_pow(gfloat64 val1, gfloat64 val2) { - return powf(val1, val2); + if (val1 == 1.0) + return 1.0; + else if ((val2 == 0.0) || (val2 == -0.0)) + return 1.0; + else if ((val1 == -1.0) && isinf(val2)) + return 1.0; + return pow(val1, val2); } -#endif /* FLOAT_COMPILE_SAFER_POWF */ - -#endif /* FLOAT_SUPPORT */ +#endif /* DOUBLE_SUPPORT */ diff --git a/inform6/Tests/Assistants/dumb-glulx/glulxe/serial.c b/inform6/Tests/Assistants/dumb-glulx/glulxe/serial.c index 99b9f89fd..2ce4908e9 100755 --- a/inform6/Tests/Assistants/dumb-glulx/glulxe/serial.c +++ b/inform6/Tests/Assistants/dumb-glulx/glulxe/serial.c @@ -63,10 +63,14 @@ static int reposition_write(dest_t *dest, glui32 pos); int init_serial() { undo_chain_num = 0; - undo_chain_size = max_undo_level; - undo_chain = (unsigned char **)glulx_malloc(sizeof(unsigned char *) * undo_chain_size); - if (!undo_chain) - return FALSE; + undo_chain_size = 0; + undo_chain = NULL; + if (max_undo_level > 0) { + undo_chain_size = max_undo_level; + undo_chain = (unsigned char **)glulx_malloc(sizeof(unsigned char *) * undo_chain_size); + if (!undo_chain) + return FALSE; + } #ifdef SERIALIZE_CACHE_RAM { @@ -280,6 +284,9 @@ glui32 perform_restoreundo() dest.ptr = NULL; } + if (heapsumarr) + glulx_free(heapsumarr); + return res; } @@ -571,6 +578,9 @@ glui32 perform_restore(strid_t str, int fromshell) } } + if (heapsumarr) + glulx_free(heapsumarr); + if (res) return 1; diff --git a/inform6/inform6.mkscript b/inform6/inform6.mkscript index 5ba8a08e7..e9cb74481 100644 --- a/inform6/inform6.mkscript +++ b/inform6/inform6.mkscript @@ -113,7 +113,7 @@ $(INTERPRETERS)/dumb-glulx/glulxe/glulxe: \ cd $(INTERPRETERS)/dumb-glulx/cheapglk; make clean cd $(INTERPRETERS)/dumb-glulx/cheapglk; make cd $(INTERPRETERS)/dumb-glulx/glulxe; make clean - cd $(INTERPRETERS)/dumb-glulx/glulxe; make OPTIONS=-g\ -Wall\ -Wno-unused\ -DOS_UNIX\ -UWIN32\ -DCOMPILE_RANDOM_CODE + cd $(INTERPRETERS)/dumb-glulx/glulxe; make OPTIONS=-g\ -Wall\ -Wno-unused\ -DOS_STDC # Cleaning up: