diff --git a/interpreters/glulxe/LICENSE b/interpreters/glulxe/LICENSE index 3bda2fa..6741944 100644 --- a/interpreters/glulxe/LICENSE +++ b/interpreters/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/interpreters/glulxe/README.md b/interpreters/glulxe/README.md index 5645cdd..876c520 100644 --- a/interpreters/glulxe/README.md +++ b/interpreters/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,8 +120,19 @@ 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.) +- Added the double-precision opcodes. (Glulx spec 3.1.3.) - Added autosave support to the Unix startup code. (Previously the autosave support only existed in the iOS startup code, which was ObjC.) Autosave now works with the RemGlk library. @@ -274,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/interpreters/glulxe/exec.c b/interpreters/glulxe/exec.c index 065b440..4016614 100644 --- a/interpreters/glulxe/exec.c +++ b/interpreters/glulxe/exec.c @@ -124,9 +124,9 @@ void execute_loop() first. They have to be unsigned values, too, otherwise the 0x80000000 case goes wonky. */ if (vals0 < 0) { - val0 = (-vals0); + val0 = (-(glui32)vals0); if (vals1 < 0) { - val1 = (-vals1); + val1 = (-(glui32)vals1); value = val0 / val1; } else { @@ -137,7 +137,7 @@ void execute_loop() else { val0 = vals0; if (vals1 < 0) { - val1 = (-vals1); + val1 = (-(glui32)vals1); value = -(val0 / val1); } else { @@ -153,13 +153,13 @@ void execute_loop() if (vals1 == 0) fatal_error("Division by zero doing remainder."); if (vals1 < 0) { - val1 = -vals1; + val1 = -(glui32)vals1; } else { val1 = vals1; } if (vals0 < 0) { - val0 = (-vals0); + val0 = (-(glui32)vals0); value = -(val0 % val1); } else { @@ -170,7 +170,7 @@ void execute_loop() break; case op_neg: vals0 = inst[0].value; - value = (-vals0); + value = (-(glui32)vals0); store_operand(inst[1].desttype, inst[1].value, value); break; @@ -527,7 +527,7 @@ void execute_loop() vals1 = (vals0) - vals1; } else { - vals1 = (-vals1) % vals0; + vals1 = (-(glui32)vals1) % vals0; } if (vals1 == 0) break; @@ -663,7 +663,7 @@ void execute_loop() else if (vals0 >= 1) value = glulx_random() % (glui32)(vals0); else - value = -(glulx_random() % (glui32)(-vals0)); + value = -(glulx_random() % (glui32)(-(glui32)vals0)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_setrandom: @@ -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/interpreters/glulxe/gestalt.c b/interpreters/glulxe/gestalt.c index 2ad9762..2b6f9da 100644 --- a/interpreters/glulxe/gestalt.c +++ b/interpreters/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/interpreters/glulxe/glkop.c b/interpreters/glulxe/glkop.c index 79d2bc9..9f7b801 100644 --- a/interpreters/glulxe/glkop.c +++ b/interpreters/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 @@ -26,7 +48,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) { @@ -39,26 +61,76 @@ 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) +/* 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 + +#include +#include + +/* Allocate a chunk of memory. */ +void *glulx_malloc(glui32 len) { - if (seed == 0) - seed = time(NULL); - srandom(seed); + return malloc(len); } -/* Return a random number in the range 0 to 2^32-1. */ -glui32 glulx_random() +/* 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); +} + +#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) { - return (random() << 16) ^ random(); + 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]); + } } +#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. */ @@ -68,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) { @@ -81,28 +153,17 @@ void glulx_free(void *ptr) free(ptr); } -#define COMPILE_RANDOM_CODE -static glui32 lo_random(void); -static void lo_seed_random(glui32 seed); - -/* 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 @@ -114,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) { @@ -127,61 +188,128 @@ void glulx_free(void *ptr) free(ptr); } -/* Set the random-number seed; zero means use as random a source as - possible. */ +#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) - seed = time(NULL); - srand(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 (rand() << 24) ^ (rand() << 12) ^ rand(); + if (rand_use_native) { + return RAND_GET(); + } + else { + return xo_random(); + } } -#endif /* WIN32 */ -#ifdef COMPILE_RANDOM_CODE +/* 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]; -/* 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 void xo_seed_random_4(glui32 seed0, glui32 seed1, glui32 seed2, glui32 seed3) +{ + /* 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; +} -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; + + 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]; - 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]; - } + 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 @@ -197,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) @@ -212,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/interpreters/glulxe/profile-analyze.py b/interpreters/glulxe/profile-analyze.py index d879506..bae6d89 100644 --- a/interpreters/glulxe/profile-analyze.py +++ b/interpreters/glulxe/profile-analyze.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python from __future__ import print_function diff --git a/interpreters/glulxe/serial.c b/interpreters/glulxe/serial.c index 99b9f89..2ce4908 100644 --- a/interpreters/glulxe/serial.c +++ b/interpreters/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/interpreters/glulxe/string.c b/interpreters/glulxe/string.c index 37feca1..c0c02e7 100644 --- a/interpreters/glulxe/string.c +++ b/interpreters/glulxe/string.c @@ -142,7 +142,7 @@ void stream_num(glsi32 val, int inmiddle, int charnum) } else { if (val < 0) - ival = -val; + ival = -(glui32)val; else ival = val; diff --git a/interpreters/glulxe/unixstrt.c b/interpreters/glulxe/unixstrt.c index f7478d7..ed78992 100644 --- a/interpreters/glulxe/unixstrt.c +++ b/interpreters/glulxe/unixstrt.c @@ -34,6 +34,7 @@ static void glkunix_game_autorestore(void); glkunix_argumentlist_t glkunix_arguments[] = { { "--undo", glkunix_arg_ValueFollows, "Number of undo states to store." }, + { "--rngseed", glkunix_arg_ValueFollows, "Fix initial RNG if nonzero." }, #if GLKUNIX_AUTOSAVE_FEATURES { "--autosave", glkunix_arg_NoValue, "Autosave every turn." }, @@ -79,8 +80,8 @@ int glkunix_startup_code(glkunix_startup_t *data) #endif #ifdef GARGLK - garglk_set_program_name("Glulxe 0.6.0"); - garglk_set_program_info("Glulxe 0.6.0 by Andrew Plotkin"); + garglk_set_program_name("Glulxe 0.6.1"); + garglk_set_program_info("Glulxe 0.6.1 by Andrew Plotkin"); #endif /* Parse out the arguments. They've already been checked for validity, @@ -91,8 +92,9 @@ int glkunix_startup_code(glkunix_startup_t *data) if (!strcmp(data->argv[ix], "--undo")) { ix++; if (ixargc) { - int val = atoi(data->argv[ix]); - if (val <= 0) { + char *endptr = NULL; + int val = strtol(data->argv[ix], &endptr, 10); + if (*endptr) { init_err = "--undo must be a number."; return TRUE; } @@ -101,6 +103,20 @@ int glkunix_startup_code(glkunix_startup_t *data) continue; } + if (!strcmp(data->argv[ix], "--rngseed")) { + ix++; + if (ixargc) { + char *endptr = NULL; + int val = strtol(data->argv[ix], &endptr, 10); + if (*endptr) { + init_err = "--rngseed must be a number."; + return TRUE; + } + init_rng_seed = val; + } + continue; + } + #if GLKUNIX_AUTOSAVE_FEATURES if (!strcmp(data->argv[ix], "--autosave")) { pref_autosave = TRUE;