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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
|
/* Linux port: stdin/stdout a byte at a time, the syscalls, diagnostics
on stderr. same behaviour as io_linux.c plus core.c's hostcall.
1..6 are files, getenv and unix time. 7 exits the process, 8 is a
counter, 9 reports the machine size. */
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#ifdef _WIN32
#include <io.h>
#include <process.h>
#ifndef O_BINARY
#define O_BINARY _O_BINARY
#endif
#if defined(_MSC_VER)
#define open _open
#define close _close
#define read _read
#define write _write
#endif
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <poll.h>
#include <signal.h>
#ifndef O_BINARY
#define O_BINARY 0
#endif
#endif
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "micro_port.h"
#include "bops.h"
#ifdef ZY_THREADS
#define VM_TLS _Thread_local
#else
#define VM_TLS
#endif
/* the machine's memory: a block that grows by realloc, zero in the new part. the
base can move on a grow, which micro.c takes into account (it re-reads m). */
static VM_TLS Cell *lmem;
static VM_TLS UCell lcap;
Cell *micro_mem_alloc(UCell words)
{
Cell *n;
if (words <= lcap && lmem) return lmem;
if (words > ((UCell)-1) / sizeof(Cell)) return NULL;
n = realloc(lmem, words * sizeof(Cell));
if (!n) return NULL;
memset(n + lcap, 0, (words - lcap) * sizeof(Cell));
lmem = n;
lcap = words;
return n;
}
#if defined(ZY_THREADS)
static VM_TLS unsigned char input_bytes[65536];
static VM_TLS int input_position, input_count, input_ended;
static int input_fill(int wait) {
if (input_position < input_count) return 1;
if (input_ended) return 0;
if (!wait) { struct pollfd descriptor = {0, POLLIN, 0}; if (poll(&descriptor, 1, 0) <= 0) return 0; }
else fflush(stdout);
int n = (int)read(0, input_bytes, sizeof input_bytes);
if (n <= 0) { input_ended = 1; return 0; }
input_position = 0; input_count = n; return 1;
}
int micro_getc(void) { return input_fill(1) ? input_bytes[input_position++] : -1; }
int micro_eof(void) { input_fill(0); return input_ended; }
#else
int micro_getc(void) { int c = getchar(); return c == EOF ? -1 : c; }
int micro_eof(void) { int c = getchar(); if (c == EOF) return 1; ungetc(c, stdin); return 0; }
#endif
void micro_putc(int c) { putchar(c & 255); }
void micro_msg(const char *s) { fputs(s, stderr); }
static int zcstr(Cell *mem, Cell addr, char *dst, int max)
{
int i = 0, c;
if (max < 1) return -1;
if ((UCell)addr >= (UCell)micro_mem_words()) { dst[0] = 0; return -1; }
while (i < max - 1 && (UCell)(addr + i) < (UCell)micro_mem_words()) {
c = (int)mem[addr + i];
if (!c) break;
dst[i++] = (char)c;
}
dst[i] = 0;
return i;
}
static int inmem(Cell a) { return (UCell)a < (UCell)micro_mem_words(); }
/* the command line and the environment, kept for ops 21 to 24: the same
contract as src/core/core.c. a program read from stdin has no file name, so
argv[0] is <eval>. */
#ifndef _WIN32
extern char **environ;
#endif
static VM_TLS int zy_argc;
static VM_TLS char **zy_argv;
static VM_TLS int zy_envc;
static VM_TLS char **zy_envp;
static void zy_args(int argc, char **argv)
{
char **e;
int n = 0;
#ifdef _WIN32
e = _environ;
#else
e = environ;
#endif
while (e && e[n]) n++;
zy_envp = e; zy_envc = n;
zy_argv = argv;
zy_argc = argc > 0 ? argc : 1;
}
/* copy entry a of a char ** into vm words, one character per word, terminated
like SYS_GETENV. a zero buffer asks for the length only. lead, when given,
replaces entry zero. */
static Cell vecstr(Cell *mem, char **v, int count, Cell a, Cell b, Cell c, const char *lead)
{
char *e;
int i = 0;
if (a < 0 || a >= (Cell)count) return -1;
e = (a == 0 && lead) ? (char *)lead : v[a];
if (!e) return -1;
if (!b) { while (e[i]) i++; return i; }
if (c < 1 || !inmem(b)) return -1;
while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
if (inmem(b + i)) mem[b + i] = 0;
return i;
}
static Cell host_rand(void)
{
static VM_TLS uint64_t s;
if (!s) s = (uint64_t)time(0) ^ 0x9E3779B97F4A7C15ull;
if (!s) s = 0x9E3779B97F4A7C15ull;
s ^= s << 13; s ^= s >> 7; s ^= s << 17;
if ((Cell)s == -1) s ^= 0x100;
return (Cell)s;
}
static int memory_words(const char *name, UCell fallback, UCell *words)
{
const char *s = getenv(name);
char *end;
unsigned long long value;
if (!s) { *words = fallback; return 1; }
errno = 0;
value = strtoull(s, &end, 10);
if (errno || end == s || *end || !value || value > 0x7fffffffULL) return 0;
*words = (UCell)value;
return 1;
}
Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
{
char path[1024], tmp[512];
int n, i, got;
switch (op) {
case 43: { /* clone an isolated execution context with a bidirectional channel */
#if !defined(_WIN32) && !defined(__wasm__)
int fd[2]; pid_t child;
if (!inmem(a) || socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1;
fflush(NULL); child = fork();
if (child < 0) { close(fd[0]); close(fd[1]); return -1; }
if (!child) { close(fd[0]); mem[a] = fd[1]; return 0; }
close(fd[1]); mem[a] = fd[0]; return child;
#else
return -1;
#endif
}
case 44: case 45: { /* write/read complete word buffers on a context channel */
#if !defined(_WIN32) && !defined(__wasm__)
size_t done = 0, length; ssize_t got; unsigned char *buffer;
if (b < 0 || c < 0 || c > (Cell)micro_mem_words() - b) return -1;
buffer = (unsigned char *)(mem + b); length = (size_t)c * sizeof(Cell);
while (done < length) {
if (op == 44) {
#ifdef __APPLE__
int enabled = 1; setsockopt((int)a, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof enabled);
#ifdef MSG_NOSIGNAL
got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
#else
got = send((int)a, buffer + done, length - done, 0);
#endif
#else
got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
#endif
} else got = read((int)a, buffer + done, length - done);
if (got < 0 && errno == EINTR) continue;
if (got <= 0) return -1;
done += (size_t)got;
}
return c;
#else
return -1;
#endif
}
case 46: { /* channel readiness, with a millisecond timeout */
#if !defined(_WIN32) && !defined(__wasm__)
struct pollfd fd; int ready; fd.fd = (int)a; fd.events = POLLIN; fd.revents = 0;
do { ready = poll(&fd, 1, (int)b); } while (ready < 0 && errno == EINTR);
return ready > 0 ? !!(fd.revents & (POLLIN | POLLHUP)) : ready;
#else
return -1;
#endif
}
case 47: case 49: { /* join a child context, optionally terminating it first */
#if !defined(_WIN32) && !defined(__wasm__)
int status; pid_t joined;
if (a <= 0) return -1;
if (op == 49) kill((pid_t)a, SIGTERM);
do { joined = waitpid((pid_t)a, &status, 0); } while (joined < 0 && errno == EINTR);
return joined < 0 ? -1 : WIFEXITED(status) ? WEXITSTATUS(status) : -1;
#else
return -1;
#endif
}
case 50: { /* monotonic milliseconds for elapsed-time measurements */
#if !defined(_WIN32) && !defined(__wasm__)
struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return -1;
return (Cell)now.tv_sec * 1000 + now.tv_nsec / 1000000;
#else
return -1;
#endif
}
case 48: { /* sleep for a bounded millisecond duration */
#if !defined(_WIN32) && !defined(__wasm__)
struct timespec duration;
if (a < 0 || a > 86400000) return -1;
duration.tv_sec = (time_t)(a / 1000); duration.tv_nsec = (long)(a % 1000) * 1000000;
while (nanosleep(&duration, &duration) < 0) if (errno != EINTR) return -1;
return 0;
#else
return -1;
#endif
}
case 1:
if (zcstr(mem, a, path, 1024) < 0) return -1;
if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
return open(path, O_RDONLY | O_BINARY);
case 2:
return close((int)a);
case 3:
if (c < 0 || !inmem(b)) return -1;
n = 0;
while (n < c) {
i = (int)(c - n);
if (i > 512) i = 512;
if (!inmem(b + n)) break;
if (!inmem(b + n + i - 1)) i = (int)(micro_mem_words() - (b + n));
if (i <= 0) break;
got = (int)read((int)a, tmp, (size_t)i);
if (got <= 0) break;
for (i = 0; i < got; i++) mem[b + n + i] = (unsigned char)tmp[i];
n += got;
}
return n;
case 4:
if (c < 0 || !inmem(b)) return -1;
n = 0;
while (n < c) {
i = (int)(c - n);
if (i > 512) i = 512;
if (!inmem(b + n)) break;
if (!inmem(b + n + i - 1)) i = (int)(micro_mem_words() - (b + n));
if (i <= 0) break;
for (got = 0; got < i; got++) tmp[got] = (char)mem[b + n + got];
got = (int)write((int)a, tmp, (size_t)i);
if (got <= 0) break;
n += got;
}
return n;
case 5: {
char *e;
if (zcstr(mem, a, path, 1024) < 0) return -1;
e = getenv(path);
if (!e) return -1;
if (c < 1 || !inmem(b)) return -1;
i = 0;
while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
if (inmem(b + i)) mem[b + i] = 0;
return i;
}
case 6:
return (Cell)time(0);
case 7:
exit((int)a);
case 8:
return host_rand();
case 9:
if (a == 0) return micro_mem_words();
if (a == 1) return HEAP_LO;
if (a == 2) return HEAP_HI;
if (a == 3) return (Cell)micro_mem_words() * 8;
return -1;
case 10:
if (bop_cstr(mem, a, path, 1024, micro_mem_words()) < 0) return -1;
if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
return open(path, O_RDONLY | O_BINARY);
case 11:
if (c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
n = 0;
while (n < c) {
i = (int)(c - n);
if (i > 512) i = 512;
i = bop_span(b + n, i, micro_mem_words());
if (i <= 0) break;
got = (int)read((int)a, tmp, (size_t)i);
if (got <= 0) break;
for (i = 0; i < got; i++) bop_put1(mem, b + n + i, (unsigned char)tmp[i]);
n += got;
}
return n;
case 12:
if (c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
n = 0;
while (n < c) {
i = (int)(c - n);
if (i > 512) i = 512;
i = bop_span(b + n, i, micro_mem_words());
if (i <= 0) break;
for (got = 0; got < i; got++) tmp[got] = (char)bop_load(mem, b + n + got, 1);
got = (int)write((int)a, tmp, (size_t)i);
if (got <= 0) break;
n += got;
}
return n;
case 13: {
char *e;
if (bop_cstr(mem, a, path, 1024, micro_mem_words()) < 0) return -1;
e = getenv(path);
if (!e) return -1;
if (c < 1 || !bop_ok(b, 1, micro_mem_words())) return -1;
i = 0;
while (e[i] && i < c - 1 && bop_ok(b + i, 1, micro_mem_words())) { bop_put1(mem, b + i, (unsigned char)e[i]); i++; }
if (bop_ok(b + i, 1, micro_mem_words())) bop_put1(mem, b + i, 0);
return i;
}
case 21:
return (Cell)zy_argc;
case 22:
return vecstr(mem, zy_argv, zy_argc, a, b, c, "<eval>");
case 23:
return (Cell)zy_envc;
case 24:
return vecstr(mem, zy_envp, zy_envc, a, b, c, 0);
default:
return -1;
}
}
int main(int argc, char **argv) {
UCell initial, limit;
if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
fputs("usage: zym [program arguments] < input\n"
"runs the embedded image with the portable vm; compiler images read source from stdin.\n"
"input after a backtick is program input; run-only images read program input directly.\n"
"program arguments are available through std.argv; stdin has the name <eval>.\n"
"options:\n --help, -h show this help and exit\n"
"environment:\n"
" TINYVM_MEMORY_WORDS initial vm memory in words\n"
" TINYVM_MAX_MEMORY_WORDS maximum vm memory in words\n", stdout);
return 0;
}
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-' && strcmp(argv[1], "--explain-checks")) { /* the compiler reads that one */
fprintf(stderr, "micro: unknown option %s; use --help for usage\n", argv[1]);
return 2;
}
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
zy_args(argc, argv);
if (!memory_words("TINYVM_MEMORY_WORDS", MICRO_MW_INIT, &initial) ||
!memory_words("TINYVM_MAX_MEMORY_WORDS", MICRO_MW_LIMIT, &limit) ||
initial > limit || !micro_memory_config(initial, limit)) {
fputs("micro: invalid memory configuration\n", stderr);
return 1;
}
return micro_run();
}
#ifdef ZY_THREADS
struct PortSnapshot {
unsigned char bytes[65536];
int position, count, ended, argc, envc;
char **argv, **envp;
};
size_t micro_context_port_size(void) { return sizeof(struct PortSnapshot); }
void micro_context_port_save(void *destination) {
struct PortSnapshot *snapshot = destination;
memcpy(snapshot->bytes, input_bytes, sizeof input_bytes);
snapshot->position = input_position; snapshot->count = input_count; snapshot->ended = input_ended;
snapshot->argc = zy_argc; snapshot->argv = zy_argv; snapshot->envc = zy_envc; snapshot->envp = zy_envp;
}
void micro_context_port_restore(const void *source) {
const struct PortSnapshot *snapshot = source;
memcpy(input_bytes, snapshot->bytes, sizeof input_bytes);
input_position = snapshot->position; input_count = snapshot->count; input_ended = snapshot->ended;
zy_argc = snapshot->argc; zy_argv = snapshot->argv; zy_envc = snapshot->envc; zy_envp = snapshot->envp;
}
void micro_context_port_free(void) { free(lmem); lmem = 0; lcap = 0; }
#endif
|