/* 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 #include #include #ifdef _WIN32 #include #include #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 #include #include #include #include #ifndef O_BINARY #define O_BINARY 0 #endif #endif #include #include #include #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 . */ #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, ""); 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 .\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] == '-') { 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