-
Notifications
You must be signed in to change notification settings - Fork 22
/
osdepend.c
374 lines (296 loc) · 8.88 KB
/
osdepend.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/* osdepend.c: Glulxe platform-dependent code.
Designed by Andrew Plotkin <erkyrath@eblong.com>
http://eblong.com/zarf/glulx/index.html
*/
#include "glk.h"
#include "glulxe.h"
/* This file contains definitions for platform-dependent code. Since
Glk takes care of I/O, this is a short list -- memory allocation
and random numbers.
The Makefile (or whatever) should define OS_UNIX, or some other
symbol. Code contributions welcome.
*/
/* We have a slightly baroque random-number scheme. If the Glulx
@setrandom opcode is given seed 0, we use "true" randomness, from a
platform native RNG if possible. If @setrandom is given a nonzero
seed, we use a simple xoshiro128** RNG (provided below). The
use of a known algorithm aids cross-platform testing and debugging.
(Those being the cases where you'd set a nonzero seed.)
To define a native RNG, define the macros RAND_SET_SEED() (seed the
RNG with the clock or some other truly random source) and RAND_GET()
(grab a number). Note that RAND_SET_SEED() does not take an argument;
it is only called when seed=0. If RAND_GET() calls a non-seeded RNG
API (such as arc4random()), then RAND_SET_SEED() should be a no-op.
If RAND_SET_SEED/RAND_GET are not provided, we call back to the same
xoshiro128** RNG as before, but seeded from the system clock.
*/
static glui32 xo_random(void);
static void xo_seed_random(glui32 seed);
static void xo_seed_random_4(glui32 seed0, glui32 seed1, glui32 seed2, glui32 seed3);
#ifdef OS_STDC
#include <time.h>
#include <stdlib.h>
/* 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
#include <time.h>
#include <stdlib.h>
/* 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);
}
#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 <sys/random.h>
static void rand_set_seed(void)
{
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
#include <stdlib.h>
/* 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 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 OS_WINDOWS
#ifdef _MSC_VER /* For Visual C++, get rand_s() */
#define _CRT_RAND_S
#endif
#include <time.h>
#include <stdlib.h>
/* 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);
}
#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()
{
if (rand_use_native) {
return RAND_GET();
}
else {
return xo_random();
}
}
/* 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] = { 0, 0, 0, 0 };
/* The get_detstate() and set_detstate() routines save and restore the
entire RNG state. These are used only by autorestore. */
void glulx_random_get_detstate(int *usenative, glui32 **arr, int *count)
{
*usenative = rand_use_native;
*arr = xo_table;
*count = 4;
}
void glulx_random_set_detstate(int usenative, glui32 *arr, int count)
{
rand_use_native = usenative;
if (count == 4) {
xo_seed_random_4(arr[0], arr[1], arr[2], arr[3]);
}
}
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 void xo_seed_random(glui32 seed)
{
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 glui32 xo_random(void)
{
/* 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];
xo_table[2] ^= t1s9;
const uint32_t t3 = xo_table[3];
xo_table[3] = ((t3 << 11) | (t3 >> (32-11)));
return result;
}
#include <stdlib.h>
/* I'm putting a wrapper for qsort() here, in case I ever have to
worry about a platform without it. But I am not worrying at
present. */
void glulx_sort(void *addr, int count, int size,
int (*comparefunc)(void *p1, void *p2))
{
qsort(addr, count, size, (int (*)(const void *, const void *))comparefunc);
}
#ifdef FLOAT_SUPPORT
#include <math.h>
/* This wrapper handles all special cases, even if the underlying
powf() function doesn't. */
gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2)
{
if (val1 == 1.0f)
return 1.0f;
else if ((val2 == 0.0f) || (val2 == -0.0f))
return 1.0f;
else if ((val1 == -1.0f) && isinf(val2))
return 1.0f;
return powf(val1, val2);
}
#endif /* FLOAT_SUPPORT */
#ifdef DOUBLE_SUPPORT
/* Same for pow(). */
extern gfloat64 glulx_pow(gfloat64 val1, gfloat64 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 /* DOUBLE_SUPPORT */