/* * Copyright 2016 WebAssembly Community Group participants * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Support JavaScript code to run WebAssembly in a JavaScript shell. * * This is a giant hack which stands in for a real libc and runtime. It acts * both as a hobbling libc and a linker/loader, including dynamic linking. */ var heap_size_bytes = 16 * 1024 * 1024; var heap_size_pages = heap_size_bytes / (64 * 1024); var memory = new WebAssembly.Memory({initial: heap_size_pages, maximum: heap_size_pages}) var heap; var heap_uint8; var heap_uint32; if (typeof process === 'object' && typeof require === 'function') { // This is node.js // Emulate JS shell behavior used below const nodeFS = require('fs'); const nodePath = require('path'); var read = function(file_path) { filename = nodePath['normalize'](file_path); return nodeFS['readFileSync'](filename); } var print = console.log; arguments = process['argv'].slice(2); } function setHeap(h) { heap = h heap_uint8 = new Uint8Array(heap); heap_uint32 = new Uint32Array(heap); heap_size_bytes = heap.byteLength; } setHeap(memory.buffer) // Heap access helpers. function charFromHeap(ptr) { return String.fromCharCode(heap_uint8[ptr]); } function stringFromHeap(ptr, len = -1) { var str = ''; var end = heap_size_bytes; if (len != -1) end = ptr + len; for (var i = ptr; i < end && heap_uint8[i] != 0; ++i) str += charFromHeap(i); return str; } // Exceptions. function TerminateWasmException(value) { this.value = value; this.message = 'Terminating WebAssembly'; this.toString = function() { return this.message + ': ' + this.value; }; } function NotYetImplementedException(what) { this.message = 'Not yet implemented'; this.what = what; this.toString = function() { return this.message + ': ' + this.what; }; } function NYI(what) { return function() { throw new NotYetImplementedException(what); }; } var builtins = (function() { return { // TODO create a complete list from: // https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html // Constructing function calls. __builtin_apply_args: NYI('__builtin_apply_args'), __builtin_apply: NYI('__builtin_apply'), __builtin_return: NYI('__builtin_return'), __builtin_va_arg_pack: NYI('__builtin_va_arg_pack'), __builtin_va_arg_pack_len: NYI('__builtin_va_arg_pack_len'), // Others. __builtin_malloc: function(size) { return stdlib.malloc(size); } }; })(); var ctype = (function() { function between(c, lower, upper) { c &= 0xff; return lower.charCodeAt(0) <= c && c <= upper.charCodeAt(0); } return { // Character classification functions. isalnum: function(c) { return ctype.isalpha(c) || ctype.isnum(c); }, isalpha: function(c) { return between(ctype.tolower(c), 'a', 'z'); }, isblank: function(c) { c &= 0xff; return c == 0x09 || c == 0x20; }, iscntrl: function(c) { c &= 0xff; return c <= 0x1f || c == 0x7f; }, isdigit: function(c) { return between(c, '0', '9'); }, isgraph: function(c) { return ctype.isprint(c) && (c & 0xff) != 0x20; }, islower: function(c) { return between(c, 'a', 'z'); }, isprint: function(c) { return !ctype.iscntrl(c); }, ispunct: function(c) { return ctype.isgraph(c) && !ctype.isalnum(c); }, isspace: function(c) { var spaces = [0x20, 0x09, 0x0a, 0x0b, 0x0c, 0x0d]; for (var s in spaces) if ((c & 0xff) == spaces[s]) return 1; return 0; }, isupper: function(c) { return between(c, 'A', 'Z'); }, isxdigit: function(c) { return ctype.isdigit(c) || between(tolower(c), 'a', 'f'); }, // Character conversion functions. tolower: function(c) { return ctype.isupper(c) ? 0xff & c + 0x20 : c; }, toupper: function(c) { return ctype.islower(c) ? 0xff & c - 0x20 : c; } }; })(); var math = (function() { return { // Constants. math_errhandling: 2, // TODO: use MATH_ERRNO instead? INFINITY: Number.POSITIVE_INFINITY, NAN: Number.NAN, HUGE_VAL: Number.POSITIVE_INFINITY, HUGE_VALF: Number.POSITIVE_INFINITY, HUGE_VALL: Number.POSITIVE_INFINITY, MATH_ERRNO: 1, MATH_ERREXCEPT: 2, // No defined because we don't guarantee fast FMA. // FP_FAST_FMA // FP_FAST_FMAF // FP_FAST_FMAL FP_INFINITE: 1, FP_NAN: 0, FP_NORMAL: 4, FP_SUBNORMAL: 3, FP_ZERO: 2, FP_ILOGB0: -1 - 0x7fffffff, FP_ILOGBNAN: -1 - 0x7fffffff, // Trigonometric functions. cos: Math.cos, sin: Math.sin, tan: Math.tan, acos: Math.acos, asin: Math.asin, atan: Math.atan, atan2: Math.atan2, // Hyperbolic functions. cosh: Math.cosh, sinh: Math.sinh, tanh: Math.tanh, acosh: Math.acosh, asinh: Math.asinh, atanh: Math.atanh, // Exponential and logarithmic functions. exp: Math.exp, frexp: NYI('frexp'), ldexp: NYI('ldexp'), log: Math.log, log10: Math.log10, modf: NYI('fmod'), exp2: NYI('exp2'), expm1: Math.expm1, ilogb: NYI('ilogb'), log1p: Math.log1p, log2: Math.log2, logb: Math.logb, scalbn: NYI('scalbn'), scalbln: NYI('scalbln'), // Power functions. pow: Math.pow, sqrt: Math.sqrt, cbrt: Math.cbrt, hypot: Math.hypot, // Error and gamma functions. erf: NYI('erf'), erfc: NYI('erfc'), tgamma: NYI('tgamma'), lgamma: NYI('lgamma'), // Rounding and remainder functions. ceil: Math.ceil, floor: Math.floor, fmod: NYI('fmod'), trunc: Math.trunc, round: Math.round, lround: Math.round, llround: Math.round, rint: NYI('rint'), lrint: NYI('lrint'), llrint: NYI('llrint'), nearbyint: NYI('nearbyint'), remainder: NYI('remainder'), remquo: NYI('remquo'), // Floating-point manipulation functions. copysign: NYI('copysign'), nan: function() { return Number.NAN; }, nextafter: NYI('nextafter'), nexttoward: NYI('nexttoward'), // Minimum, maximum, difference functions. fdim: function(x, y) { return x > y ? x - y : 0; }, fmax: NYI('fmax'), fmin: NYI('fmin'), // Other functions. fabs: Math.abs, abs: Math.abs, // fma is not provided. // Classification. fpclassify: function(x) { if (math.isnormal(x)) return math.FP_NORMAL; if (math.isfinite(x)) return math.FP_INFINITE; if (math.isnan(x)) return math.FP_NAN; if (x == 0.0 || x == -0.0) return math.FP_ZERO; else return math.FP_SUBNORMAL; }, isfinite: Number.isFinite, isfinitef: Number.isFinite, isfinitel: Number.isFinite, isinf: function(x) { return Number.POSITIVE_INFINITY == x ? 1 : Number.NEGATIVE_INFINITY ? -1 : 0; }, isinff: function(x) { return math.isinf(x); }, isinfl: function(x) { return math.isinf(x); }, isnan: Number.isNaN, isnanf: Number.isNaN, isnanl: Number.isNaN, isnormal: NYI('isnormal'), signbit: NYI('signbit'), // Comparison. isgreater: function(x, y) { return x > y; }, isgreaterequal: function(x, y) { return x >= y; }, isless: function(x, y) { return x < y; }, islessequal: function(x, y) { return x <= y; }, islessgreater: function(x, y) { return x < y || x > y; }, isunordered: Number.isNaN, // Non-standard. finite: Number.isFinite, finitef: Number.isFinite, finitel: Number.isFinite, __builtin_finite: Number.isFinite, __builtin_finitef: Number.isFinite, __builtin_finitel: Number.isFinite, __builtin_isinf: function(x) { return math.isinf(x); }, __builtin_isinff: function(x) { return math.isinf(x); }, __builtin_isinfl: function(x) { return math.isinf(x); }, __builtin_isnan: Number.isNaN, __builtin_isnanf: Number.isNaN, __builtin_isnanl: Number.isNaN, __builtin_clrsb: NYI('__builtin_clrsb'), __builtin_clrsbl: NYI('__builtin_clrsbl'), __builtin_clrsbll: NYI('__builtin_clrsbll') }; })(); var runtime = (function() { return { // TODO create a complete list from: // https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html __addtf3: NYI('__addtf3'), __divtf3: NYI('__divtf3'), __eqtf2: NYI('__eqtf2'), __fixsfti: NYI('__fixsfti'), __fixtfdi: NYI('__fixtfdi'), __fixtfsi: NYI('__fixtfsi'), __fixunstfdi: NYI('__fixunstfdi'), __fixunstfsi: NYI('__fixunstfsi'), __floatditf: NYI('__floatditf'), __floatsitf: NYI('__floatsitf'), __floatunditf: NYI('__floatunditf'), __floatunsitf: NYI('__floatunsitf'), __getf2: NYI('__getf2'), __gttf2: NYI('__gttf2'), __letf2: NYI('__letf2'), __lttf2: NYI('__lttf2'), __multf3: NYI('__multf3'), __multi3: NYI('__multi3'), __muldc3: NYI('__muldc3'), __mulsc3: NYI('__mulsc3'), __multc3: NYI('__multc3'), __netf2: NYI('__netf2'), __subtf3: NYI('__subtf3'), __divsc3: NYI('__divsc3'), __unordtf2: NYI('__unordtf2') }; })(); var stdio = (function() { var stdout_buf = ''; return { // Internal. __write_stdout: function(str) { stdout_buf += str; }, __flush_stdout: function() { if (stdout_buf[-1] = '\n') stdout_buf = stdout_buf.slice(0, -1); print(stdout_buf); stdout_buf = ''; }, // Constants. BUFSIZ: 0xffffffff, // TODO EOF: 0xffffffff, FILENAME_MAX: 4096, FOPEN_MAX: 1000, L_tmpnam: 20, NULL: 0, TMP_MAX: 10000, // Operations on files. remove: NYI('remove'), rename: NYI('rename'), tmpfile: NYI('tmpfile'), tmpnam: NYI('tmpnam'), // File access. fclose: NYI('fclose'), fflush: NYI('fflush'), fopen: NYI('fopen'), freopen: NYI('freopen'), setbuf: NYI('setbuf'), setvbuf: NYI('setvbuf'), // Formatted input/output. fprintf: NYI('fprintf'), fscanf: NYI('fscanf'), printf: NYI('printf'), scanf: NYI('scanf'), snprintf: NYI('snprintf'), sprintf: NYI('sprintf'), sscanf: NYI('sscanf'), vfprintf: NYI('vfprintf'), vfscanf: NYI('vfscanf'), vprintf: NYI('vprintf'), vscanf: NYI('vscanf'), vsnprintf: NYI('vsnprintf'), vsprintf: NYI('vsprintf'), vsscanf: NYI('vsscanf'), // Character input/output. fgetc: NYI('fgetc'), fgets: NYI('fgets'), fputc: NYI('fputc'), fputs: NYI('fputs'), getc: NYI('getc'), getchar: NYI('getchar'), gets: NYI('gets'), putc: NYI('putc'), putchar: function(character) { character &= 0xff; stdout_buf += String.fromCharCode(character); return character; }, puts: function(str) { stdio.__write_stdout(stringFromHeap(str) + '\n'); stdio.__flush_stdout(); }, ungetc: NYI('ungetc'), // Direct input/output. fread: NYI('fread'), fwrite: NYI('fwrite'), // File positioning. fgetpos: NYI('fgetpos'), fseek: NYI('fseek'), fsetpos: NYI('fsetpos'), ftell: NYI('ftell'), rewind: NYI('rewind'), // Error-handling. clearerr: NYI('clearerr'), feof: NYI('feof'), ferror: NYI('ferror'), perror: NYI('perror') }; })(); var stdlib = (function() { var exit_code = 0; var allocated_bytes = 1 << 14; // Leave the heap bottom free. return { // Internals. __get_exit_code: function() { return exit_code; }, // Constants. EXIT_SUCCESS: 0, EXIT_FAILURE: 1, NULL: 0, MB_CUR_MAX: 1, RAND_MAX: 0xffffffff, // String conversion. atof: NYI('atof'), atoi: NYI('atoi'), atol: NYI('atol'), atoll: NYI('atoll'), strtod: NYI('strtod'), strtof: NYI('strtof'), strtol: NYI('strtol'), strtold: NYI('strtold'), strtoll: NYI('strtoll'), strtoul: NYI('strtoul'), strtoull: NYI('strtoull'), // Pseudo-random sequence generation. rand: NYI('rand'), srand: NYI('srand'), // Dynamic memory management. calloc: function(nmemb, size) { var bytes = nmemb * size; if (bytes == 0) return 0; var ptr = stdlib.malloc(bytes); return ptr ? string.memset(ptr, 0, bytes) : 0; }, free: NYI('free'), malloc: function(size) { if (size == 0) return 0; if (allocated_bytes > heap_size_bytes) return 0; allocated_bytes += size; return allocated_bytes - size; }, realloc: function(ptr, size) { if (ptr == 0) return stdlib.malloc(size); if (size == 0) stdlib.free(ptr); throw new NotYetImplementedException('realloc'); }, // Environment. abort: function() { exit_code = stdlib.EXIT_FAILURE; throw new TerminateWasmException('abort()'); }, atexit: NYI('atexit'), at_quick_exit: NYI('at_quick_exit'), exit: function(code) { // TODO invoke atexit functions. exit_code = code; throw new TerminateWasmException('exit(' + code + ')'); }, getenv: NYI('getenv'), quick_exit: function(code) { // TODO invoke at_quick_exit functions. _Exit(code); }, system: NYI('system'), _Exit: function(code) { exit_code = code; throw new TerminateWasmException('_Exit(' + code + ')'); }, // Searching and sorting. bsearch: NYI('bsearch'), qsort: NYI('qsort'), // Integer arithmetics. abs: NYI('abs'), div: NYI('div'), labs: NYI('labs'), ldiv: NYI('ldiv'), llabs: NYI('llabs'), lldiv: NYI('lldiv'), // Multibyte characters. mblen: NYI('mblen'), mbtowc: NYI('mbtowc'), wctomb: NYI('wctomb'), // Multibyte strings. mbstowcs: NYI('mbstowcs'), wcstombs: NYI('wcstombs') }; })(); var string = (function() { return { // Constants. NULL: 0, // Functions. memcpy: function(destination, source, num) { for (var i = 0; i != num; ++i) heap_uint8[destination + i] = heap_uint8[source + i]; return destination; }, mempcpy: function(destination, source, num) { // Non-standard. return num + string.memcpy(destination, source, num); }, memmove: NYI('memmove'), strcpy: function(destination, source) { var i = 0; for (; heap_uint8[source + i] != 0; ++i) heap_uint8[destination + i] = heap_uint8[source + i]; heap_uint8[destination + i] = 0; return destination; }, strncpy: function(destination, source, num) { var i = 0; for (; i != num && heap_uint8[source + i] != 0; ++i) heap_uint8[destination + i] = heap_uint8[source + i]; for (; i != num; ++i) heap_uint8[destination + i] = 0; return destination; }, // Concatenation. strcat: NYI('strcat'), strncat: NYI('strncat'), // Comparison. memcmp: function(ptr1, ptr2, num) { for (var i = 0; i != num; ++i) if (heap_uint8[ptr1 + i] != heap_uint8[ptr2 + i]) return heap_uint8[ptr1 + i] < heap_uint8[ptr2 + i]; return 0; }, strcmp: function(str1, str2) { for (var i1 = 0, i2 = 0; heap_uint8[str1 + i1] == heap_uint8[str2 + i2]; ++i1, ++i2) { if (heap_uint8[str1 + i1] == 0) return 0; } return heap_uint8[str1 + i1] < heap_uint8[str2 + i2] ? -1 : 1; }, strcoll: NYI('strcoll'), strncmp: function(str1, str2, num) { for (var i1 = 0, i2 = 0; num > 0; ++i1, ++i2, --num) { var s1 = heap_uint8[str1 + i1], s2 = heap_uint8[str2 + i2]; if (s1 != s2) return s1 < s2 ? -1 : 1; else if (s1 == 0) return 0; } return 0; }, strxfrm: NYI('strxfrm'), // Searching. memchr: NYI('memchr'), strchr: function(str, character) { character &= 0xff; var i = 0; for (; heap_uint8[str + i] != 0; ++i) { if (str + i >= heap_size_bytes) return 0; if (heap_uint8[str + i] == character) return str + i; } return character == 0 ? str + i : 0; }, strcspn: NYI('strcspn'), strpbrk: NYI('strpbrk'), strrchr: function(str, character) { character &= 0xff; if (character == 0) return str + string.strlen(str); var found = str; for (var i = 0; heap_uint8[str + i] != 0; ++i) if (heap_uint8[str + i] == character) found = str + i; return heap_uint8[found] == character ? found : 0; }, strspn: NYI('strspn'), strstr: NYI('strstr'), strtok: NYI('strtok'), // Other. memset: function(ptr, value, num) { for (var i = 0; i != num; ++i) heap_uint8[ptr + i] = value; return ptr; }, strerror: NYI('strerror'), strlen: function(str) { for (var i = 0;; ++i) if (heap_uint8[str + i] == 0) return i; } }; })(); var unix = (function() { var OPEN_MAX = 256; var open_files = new Uint8Array(OPEN_MAX); var dlfcn = {}; var dlfcn_handle_to_filename = {}; var dlfcn_max_handle = 0; return { // constants. RTLD_LAZY: 1, RTLD_NOW: 2, RTLD_NOLOAD: 4, RTLD_NODELETE: 4096, RTLD_GLOBAL: 256, RTLD_LOCAL: 0, RTLD_NEXT: 0xffffffff, RTLD_DEFAULT: 0, RTLD_DI_LINKMAP: 2, // dlclose: function(handle) { var filename = dlfcn_handle_to_filename[handle]; if (!filename) NYI('dlclose of invalid handle')(); dlfcn[filename].refcount -= 1; if (dlfcn[filename].refcount == 0) dlfcn[filename] = undefined; return 0; }, dlerror: function() { // TODO: implement error handling. return 0; }, dlopen: function(filename, flags) { if (!filename) NYI('dlopen(NULL, ...);')(); var fs = stringFromHeap(filename); if (dlfcn[fs]) { dlfcn[fs].refcount += 1; return dlfcn[fs].handle; } if (flags & unix.RTLD_LAZY) NYI('dlopen with flag RTLD_LAZY')(); if (~flags & unix.RTLD_NOW) NYI('dlopen without flag RTDL_NOW')(); if (flags & unix.RTLD_NOLOAD) NYI('dlopen with flag RTLD_NOLOAD')(); if (flags & unix.RTLD_NODELETE) NYI('dlopen with flag RTLD_NODELETE')(); if (flags & unix.RTLD_GLOBAL) NYI('dlopen with flag RTLD_GLOBAL')(); // TODO: other flags. var handle = ++dlfcn_max_handle; dlfcn[fs] = { refcount: 0, module: load_wasm(fs), handle: handle }; dlfcn_handle_to_filename[handle] = fs; return handle; }, dlsym: function(handle, symbol) { var filename = dlfcn_handle_to_filename[handle]; if (!filename) NYI('dlsym of invalid handle')(); if (!symbol) NYI('dlsym of NULL symbol')(); var ss = stringFromHeap(symbol); // TODO: error handling when module doesn't contain symbol. for (var m in dlfcn[filename].module) if (m == ss) return dlfcn[filename].module[m]; NYI('dlsym with symbol not found in handle')(); }, dladdr: NYI('dladdr'), dlinfo: NYI('dlinfo'), // constants. F_DUPFD: 0, F_GETFD: 1, F_SETFD: 2, F_GETFL: 3, F_SETFL: 4, F_GETLK: 5, F_SETLK: 6, F_SETLKW: 7, F_GETOWN: 9, F_SETOWN: 8, FD_CLOEXEC: 1, F_RDLCK: 0, F_UNLCK: 2, F_WRLCK: 1, SEEK_SET: 0, SEEK_CUR: 1, SEEK_END: 2, O_CREAT: 0100, O_EXCL: 0200, O_NOCTTY: 0400, O_TRUNC: 01000, O_APPEND: 02000, O_DSYNC: 010000, O_NONBLOCK: 04000, O_RSYNC: 04010000, O_SYNC: 04010000, O_ACCMODE: 010000030, O_RDONLY: 00, O_RDWR: 02, O_WRONLY: 01, // creat: function(pathname, mode) { var flags = unix.O_CREAT | unix.O_WRONLY | unix.O_TRUNC; return unix.open(pathname, flags, mode); }, fcntl: NYI('fcntl'), open: function(pathname, flags, mode) { for (var i = 0; i != OPEN_MAX; ++i) if (!open_files[i]) { open_files[i] = true; return i; } return -1; }, posix_fadvise: NYI('posix_fadvise'), posix_fallocate: NYI('posix_fallocate'), // longjmp: NYI('longjmp'), siglongjmp: NYI('siglongjmp'), _longjmp: NYI('_longjmp'), setjmp: NYI('setjmp'), sigsetjmp: NYI('sigsetjmp'), _setjmp: NYI('_setjmp'), // constants. // TODO other constants. SIG_ERR: 0xffffffff, // // TODO other functions. signal: function signal(signum, handler) { return unix.SIG_ERR; }, // constants. PROT_READ: 1, PROT_WRITE: 2, PROT_EXEC: 4, PROT_NONE: 0, MAP_SHARED: 0x01, MAP_PRIVATE: 0x02, MAP_FIXED: 0x10, MS_ASYNC: 1, MS_SYNC: 4, MS_INVALIDATE: 2, MCL_CURRENT: 1, MCL_FUTURE: 2, MAP_FAILED: 0xffffffff, // mlock: NYI('mlock'), mlockall: NYI('mlockall'), mmap: function(addr, length, prot, flags, fd, offset) { if (addr != 0) NYI('mmap addr ' + addr)(); if (prot != PROT_READ | PROT_WRITE) NYI('mmap prot ' + prot)(); if (fd != -1) NYI('mmap fd ' + fd)(); return stdlib.malloc(length); }, mprotect: NYI('mprotect'), msync: NYI('msync'), munlock: NYI('munlock'), munlockall: NYI('munlockall'), munmap: NYI('munmap'), shm_open: NYI('shm_open'), shm_unlink: NYI('shm_unlink'), // constants. NULL: 0, F_OK: 0, R_OK: 4, W_OK: 2, X_OK: 1, F_LOCK: 1, F_TEST: 3, F_TLOCK: 2, F_ULOCK: 0, STDIN_FILENO: 0, STDOUT_FILENO: 1, STDERR_FILENO: 2, // access: NYI('access'), alarm: NYI('alarm'), brk: NYI('brk'), chdir: NYI('chdir'), chroot: NYI('chroot'), chown: NYI('chown'), close: function(fd) { if (fd > OPEN_MAX || !open_files[fd]) return -1; open_files[fd] = false; }, confstr: NYI('confstr'), crypt: NYI('crypt'), ctermid: NYI('ctermid'), cuserid: NYI('cuserid'), dup: NYI('dup'), dup2: NYI('dup2'), encrypt: NYI('encrypt'), execl: NYI('execl'), execle: NYI('execle'), execlp: NYI('execlp'), execv: NYI('execv'), execve: NYI('execve'), execvp: NYI('execvp'), exit: stdlib.exit, fchown: NYI('fchown'), fchdir: NYI('fchdir'), fdatasync: NYI('fdatasync'), fork: NYI('fork'), fpathconf: NYI('fpathconf'), fsync: NYI('fsync'), ftruncate: NYI('ftruncate'), getcwd: NYI('getcwd'), getdtablesize: NYI('getdtablesize'), getegid: function() { return 0; }, geteuid: function() { return 0; }, getgid: function() { return 0; }, getgroups: NYI('getgroups'), gethostid: function() { return 0; }, getlogin: NYI('getlogin'), getlogin_r: NYI('getlogin_r'), getopt: NYI('getopt'), getpagesize: NYI('getpagesize'), getpass: NYI('getpass'), getpgid: function(pid) { return 0; }, getpgrp: function() { return 0; }, getpid: function() { return 0; }, getppid: function() { return 0; }, getsid: function(pid) { return 0; }, getuid: function() { return 0; }, getwd: function() { return 0; }, isatty: function(fd) { return 0; }, lchown: NYI('lchown'), link: NYI('link'), lockf: NYI('lockf'), lseek: NYI('lseek'), nice: NYI('nice'), pathconf: NYI('pathconf'), pause: NYI('pause'), pipe: NYI('pipe'), pread: NYI('pread'), pthread_atfork: NYI('pthread_atfork'), void: NYI(' void'), pwrite: NYI('pwrite'), read: NYI('read'), readlink: NYI('readlink'), rmdir: NYI('rmdir'), sbrk: NYI('sbrk'), setgid: NYI('setgid'), setpgid: NYI('setpgid'), setpgrp: NYI('setpgrp'), setregid: NYI('setregid'), setreuid: NYI('setreuid'), setsid: NYI('setsid'), setuid: NYI('setuid'), sleep: NYI('sleep'), swab: NYI('swab'), symlink: NYI('symlink'), sync: NYI('sync'), sysconf: NYI('sysconf'), tcgetpgrp: NYI('tcgetpgrp'), tcsetpgrp: NYI('tcsetpgrp'), truncate: NYI('truncate'), ttyname: NYI('ttyname'), ttyname_r: NYI('ttyname_r'), ualarm: NYI('ualarm'), unlink: NYI('unlink'), usleep: NYI('usleep'), vfork: NYI('vfork'), write: NYI('write') }; })(); // Temporary workarounds for functions my hacky musl build thinks are imports. // Generated with: // grep "(import " musl.wast | cut -d'$' -f2 | cut -d' ' -f1 | grep -v __syscall | sort | sed "s/\(.*\)/\1: NYI('\1'),/g" var musl_hack = (function() { return { abort: NYI('abort'), __addtf3: NYI('__addtf3'), __clone: NYI('__clone'), __divtf3: NYI('__divtf3'), __eqtf2: NYI('__eqtf2'), exit: NYI('exit'), _Exit: NYI('_Exit'), __extenddftf2: NYI('__extenddftf2'), __extendsftf2: NYI('__extendsftf2'), __fixtfdi: NYI('__fixtfdi'), __fixtfsi: NYI('__fixtfsi'), __fixunstfsi: NYI('__fixunstfsi'), __floatsitf: NYI('__floatsitf'), __floatunsitf: NYI('__floatunsitf'), __getf2: NYI('__getf2'), getrlimit: NYI('getrlimit'), __gttf2: NYI('__gttf2'), ioctl: NYI('ioctl'), __letf2: NYI('__letf2'), __lock: NYI('__lock'), longjmp: NYI('longjmp'), __lttf2: NYI('__lttf2'), __muldc3: NYI('__muldc3'), __mulsc3: NYI('__mulsc3'), __multc3: NYI('__multc3'), __multf3: NYI('__multf3'), __netf2: NYI('__netf2'), nftw: NYI('nftw'), pthread_attr_init: NYI('pthread_attr_init'), pthread_attr_setdetachstate: NYI('pthread_attr_setdetachstate'), pthread_attr_setguardsize: NYI('pthread_attr_setguardsize'), pthread_attr_setstacksize: NYI('pthread_attr_setstacksize'), pthread_barrier_destroy: NYI('pthread_barrier_destroy'), pthread_barrier_init: NYI('pthread_barrier_init'), pthread_barrier_wait: NYI('pthread_barrier_wait'), pthread_cancel: NYI('pthread_cancel'), _pthread_cleanup_pop: NYI('_pthread_cleanup_pop'), _pthread_cleanup_push: NYI('_pthread_cleanup_push'), pthread_cond_init: NYI('pthread_cond_init'), pthread_cond_wait: NYI('pthread_cond_wait'), pthread_create: NYI('pthread_create'), pthread_mutex_init: NYI('pthread_mutex_init'), pthread_mutex_lock: NYI('pthread_mutex_lock'), pthread_mutex_unlock: NYI('pthread_mutex_unlock'), pthread_once: NYI('pthread_once'), pthread_rwlock_rdlock: NYI('pthread_rwlock_rdlock'), pthread_rwlock_unlock: NYI('pthread_rwlock_unlock'), pthread_rwlock_wrlock: NYI('pthread_rwlock_wrlock'), pthread_setcancelstate: NYI('pthread_setcancelstate'), pthread_sigmask: NYI('pthread_sigmask'), pthread_testcancel: NYI('pthread_testcancel'), sem_init: NYI('sem_init'), sem_post: NYI('sem_post'), sem_wait: NYI('sem_wait'), setjmp: NYI('setjmp'), setrlimit: NYI('setrlimit'), __set_thread_area: NYI('__set_thread_area'), __subtf3: NYI('__subtf3'), __synccall: NYI('__synccall'), __timedwait_cp: NYI('__timedwait_cp'), __trunctfdf2: NYI('__trunctfdf2'), __trunctfsf2: NYI('__trunctfsf2'), uname: NYI('uname'), __unlock: NYI('__unlock'), __unordtf2: NYI('__unordtf2'), __wait: NYI('__wait'), }; })(); syscall_names = { // Generted using the following command: // grep "^#define SYS_" obj/include/bits/syscall.h | awk '{print $3 ": \"" $2 "\"," }' 0: "SYS_restart_syscall", 1: "SYS_exit", 2: "SYS_fork", 3: "SYS_read", 4: "SYS_write", 5: "SYS_open", 6: "SYS_close", 7: "SYS_waitpid", 8: "SYS_creat", 9: "SYS_link", 10: "SYS_unlink", 11: "SYS_execve", 12: "SYS_chdir", 13: "SYS_time", 14: "SYS_mknod", 15: "SYS_chmod", 16: "SYS_lchown", 17: "SYS_break", 18: "SYS_oldstat", 19: "SYS_lseek", 20: "SYS_getpid", 21: "SYS_mount", 22: "SYS_umount", 23: "SYS_setuid", 24: "SYS_getuid", 25: "SYS_stime", 26: "SYS_ptrace", 27: "SYS_alarm", 28: "SYS_oldfstat", 29: "SYS_pause", 30: "SYS_utime", 31: "SYS_stty", 32: "SYS_gtty", 33: "SYS_access", 34: "SYS_nice", 35: "SYS_ftime", 36: "SYS_sync", 37: "SYS_kill", 38: "SYS_rename", 39: "SYS_mkdir", 40: "SYS_rmdir", 41: "SYS_dup", 42: "SYS_pipe", 43: "SYS_times", 44: "SYS_prof", 45: "SYS_brk", 46: "SYS_setgid", 47: "SYS_getgid", 48: "SYS_signal", 49: "SYS_geteuid", 50: "SYS_getegid", 51: "SYS_acct", 52: "SYS_umount2", 53: "SYS_lock", 54: "SYS_ioctl", 55: "SYS_fcntl", 56: "SYS_mpx", 57: "SYS_setpgid", 58: "SYS_ulimit", 59: "SYS_oldolduname", 60: "SYS_umask", 61: "SYS_chroot", 62: "SYS_ustat", 63: "SYS_dup2", 64: "SYS_getppid", 65: "SYS_getpgrp", 66: "SYS_setsid", 67: "SYS_sigaction", 68: "SYS_sgetmask", 69: "SYS_ssetmask", 70: "SYS_setreuid", 71: "SYS_setregid", 72: "SYS_sigsuspend", 73: "SYS_sigpending", 74: "SYS_sethostname", 75: "SYS_setrlimit", 76: "SYS_getrlimit", 77: "SYS_getrusage", 78: "SYS_gettimeofday", 79: "SYS_settimeofday", 80: "SYS_getgroups", 81: "SYS_setgroups", 82: "SYS_select", 83: "SYS_symlink", 84: "SYS_oldlstat", 85: "SYS_readlink", 86: "SYS_uselib", 87: "SYS_swapon", 88: "SYS_reboot", 89: "SYS_readdir", 90: "SYS_mmap", 91: "SYS_munmap", 92: "SYS_truncate", 93: "SYS_ftruncate", 94: "SYS_fchmod", 95: "SYS_fchown", 96: "SYS_getpriority", 97: "SYS_setpriority", 98: "SYS_profil", 99: "SYS_statfs", 100: "SYS_fstatfs", 101: "SYS_ioperm", 102: "SYS_socketcall", 103: "SYS_syslog", 104: "SYS_setitimer", 105: "SYS_getitimer", 106: "SYS_stat", 107: "SYS_lstat", 108: "SYS_fstat", 109: "SYS_olduname", 110: "SYS_iopl", 111: "SYS_vhangup", 112: "SYS_idle", 113: "SYS_vm86old", 114: "SYS_wait4", 115: "SYS_swapoff", 116: "SYS_sysinfo", 117: "SYS_ipc", 118: "SYS_fsync", 119: "SYS_sigreturn", 120: "SYS_clone", 121: "SYS_setdomainname", 122: "SYS_uname", 123: "SYS_modify_ldt", 124: "SYS_adjtimex", 125: "SYS_mprotect", 126: "SYS_sigprocmask", 127: "SYS_create_module", 128: "SYS_init_module", 129: "SYS_delete_module", 130: "SYS_get_kernel_syms", 131: "SYS_quotactl", 132: "SYS_getpgid", 133: "SYS_fchdir", 134: "SYS_bdflush", 135: "SYS_sysfs", 136: "SYS_personality", 137: "SYS_afs_syscall", 138: "SYS_setfsuid", 139: "SYS_setfsgid", 140: "SYS__llseek", 141: "SYS_getdents", 142: "SYS__newselect", 143: "SYS_flock", 144: "SYS_msync", 145: "SYS_readv", 146: "SYS_writev", 147: "SYS_getsid", 148: "SYS_fdatasync", 149: "SYS__sysctl", 150: "SYS_mlock", 151: "SYS_munlock", 152: "SYS_mlockall", 153: "SYS_munlockall", 154: "SYS_sched_setparam", 155: "SYS_sched_getparam", 156: "SYS_sched_setscheduler", 157: "SYS_sched_getscheduler", 158: "SYS_sched_yield", 159: "SYS_sched_get_priority_max", 160: "SYS_sched_get_priority_min", 161: "SYS_sched_rr_get_interval", 162: "SYS_nanosleep", 163: "SYS_mremap", 164: "SYS_setresuid", 165: "SYS_getresuid", 166: "SYS_vm86", 167: "SYS_query_module", 168: "SYS_poll", 169: "SYS_nfsservctl", 170: "SYS_setresgid", 171: "SYS_getresgid", 172: "SYS_prctl", 173: "SYS_rt_sigreturn", 174: "SYS_rt_sigaction", 175: "SYS_rt_sigprocmask", 176: "SYS_rt_sigpending", 177: "SYS_rt_sigtimedwait", 178: "SYS_rt_sigqueueinfo", 179: "SYS_rt_sigsuspend", 180: "SYS_pread64", 181: "SYS_pwrite64", 182: "SYS_chown", 183: "SYS_getcwd", 184: "SYS_capget", 185: "SYS_capset", 186: "SYS_sigaltstack", 187: "SYS_sendfile", 188: "SYS_getpmsg", 189: "SYS_putpmsg", 190: "SYS_vfork", 191: "SYS_ugetrlimit", 192: "SYS_mmap2", 193: "SYS_truncate64", 194: "SYS_ftruncate64", 195: "SYS_stat64", 196: "SYS_lstat64", 197: "SYS_fstat64", 198: "SYS_lchown32", 199: "SYS_getuid32", 200: "SYS_getgid32", 201: "SYS_geteuid32", 202: "SYS_getegid32", 203: "SYS_setreuid32", 204: "SYS_setregid32", 205: "SYS_getgroups32", 206: "SYS_setgroups32", 207: "SYS_fchown32", 208: "SYS_setresuid32", 209: "SYS_getresuid32", 210: "SYS_setresgid32", 211: "SYS_getresgid32", 212: "SYS_chown32", 213: "SYS_setuid32", 214: "SYS_setgid32", 215: "SYS_setfsuid32", 216: "SYS_setfsgid32", 217: "SYS_pivot_root", 218: "SYS_mincore", 219: "SYS_madvise", 220: "SYS_getdents64", 221: "SYS_fcntl64", 224: "SYS_gettid", 225: "SYS_readahead", 226: "SYS_setxattr", 227: "SYS_lsetxattr", 228: "SYS_fsetxattr", 229: "SYS_getxattr", 230: "SYS_lgetxattr", 231: "SYS_fgetxattr", 232: "SYS_listxattr", 233: "SYS_llistxattr", 234: "SYS_flistxattr", 235: "SYS_removexattr", 236: "SYS_lremovexattr", 237: "SYS_fremovexattr", 238: "SYS_tkill", 239: "SYS_sendfile64", 240: "SYS_futex", 241: "SYS_sched_setaffinity", 242: "SYS_sched_getaffinity", 243: "SYS_set_thread_area", 244: "SYS_get_thread_area", 245: "SYS_io_setup", 246: "SYS_io_destroy", 247: "SYS_io_getevents", 248: "SYS_io_submit", 249: "SYS_io_cancel", 250: "SYS_fadvise64", 252: "SYS_exit_group", 253: "SYS_lookup_dcookie", 254: "SYS_epoll_create", 255: "SYS_epoll_ctl", 256: "SYS_epoll_wait", 257: "SYS_remap_file_pages", 258: "SYS_set_tid_address", 259: "SYS_timer_create", 260: "SYS_timer_settime", 261: "SYS_timer_gettime", 262: "SYS_timer_getoverrun", 263: "SYS_timer_delete", 264: "SYS_clock_settime", 265: "SYS_clock_gettime", 266: "SYS_clock_getres", 267: "SYS_clock_nanosleep", 268: "SYS_statfs64", 269: "SYS_fstatfs64", 270: "SYS_tgkill", 271: "SYS_utimes", 272: "SYS_fadvise64_64", 273: "SYS_vserver", 274: "SYS_mbind", 275: "SYS_get_mempolicy", 276: "SYS_set_mempolicy", 277: "SYS_mq_open", 278: "SYS_mq_unlink", 279: "SYS_mq_timedsend", 280: "SYS_mq_timedreceive", 281: "SYS_mq_notify", 282: "SYS_mq_getsetattr", 283: "SYS_kexec_load", 284: "SYS_waitid", 286: "SYS_add_key", 287: "SYS_request_key", 288: "SYS_keyctl", 289: "SYS_ioprio_set", 290: "SYS_ioprio_get", 291: "SYS_inotify_init", 292: "SYS_inotify_add_watch", 293: "SYS_inotify_rm_watch", 294: "SYS_migrate_pages", 295: "SYS_openat", 296: "SYS_mkdirat", 297: "SYS_mknodat", 298: "SYS_fchownat", 299: "SYS_futimesat", 300: "SYS_fstatat64", 301: "SYS_unlinkat", 302: "SYS_renameat", 303: "SYS_linkat", 304: "SYS_symlinkat", 305: "SYS_readlinkat", 306: "SYS_fchmodat", 307: "SYS_faccessat", 308: "SYS_pselect6", 309: "SYS_ppoll", 310: "SYS_unshare", 311: "SYS_set_robust_list", 312: "SYS_get_robust_list", 313: "SYS_splice", 314: "SYS_sync_file_range", 315: "SYS_tee", 316: "SYS_vmsplice", 317: "SYS_move_pages", 318: "SYS_getcpu", 319: "SYS_epoll_pwait", 320: "SYS_utimensat", 321: "SYS_signalfd", 322: "SYS_timerfd_create", 323: "SYS_eventfd", 324: "SYS_fallocate", 325: "SYS_timerfd_settime", 326: "SYS_timerfd_gettime", 327: "SYS_signalfd4", 328: "SYS_eventfd2", 329: "SYS_epoll_create1", 330: "SYS_dup3", 331: "SYS_pipe2", 332: "SYS_inotify_init1", 333: "SYS_preadv", 334: "SYS_pwritev", 335: "SYS_rt_tgsigqueueinfo", 336: "SYS_perf_event_open", 337: "SYS_recvmmsg", 338: "SYS_fanotify_init", 339: "SYS_fanotify_mark", 340: "SYS_prlimit64", 341: "SYS_name_to_handle_at", 342: "SYS_open_by_handle_at", 343: "SYS_clock_adjtime", 344: "SYS_syncfs", 345: "SYS_sendmmsg", 346: "SYS_setns", 347: "SYS_process_vm_readv", 348: "SYS_process_vm_writev", 349: "SYS_kcmp", 350: "SYS_finit_module", 351: "SYS_sched_setattr", 352: "SYS_sched_getattr", 353: "SYS_renameat2", 354: "SYS_seccomp", 355: "SYS_getrandom", 356: "SYS_memfd_create", 357: "SYS_bpf", 358: "SYS_execveat", 359: "SYS_socket", 360: "SYS_socketpair", 361: "SYS_bind", 362: "SYS_connect", 363: "SYS_listen", 364: "SYS_accept4", 365: "SYS_getsockopt", 366: "SYS_setsockopt", 367: "SYS_getsockname", 368: "SYS_getpeername", 369: "SYS_sendto", 370: "SYS_sendmsg", 371: "SYS_recvfrom", 372: "SYS_recvmsg", 373: "SYS_shutdown", 374: "SYS_userfaultfd", 375: "SYS_membarrier", 376: "SYS_mlock2", 377: "SYS_copy_file_range", 378: "SYS_preadv2", 379: "SYS_pwritev2", 380: "SYS_pkey_mprotect", 381: "SYS_pkey_alloc", 382: "SYS_pkey_free", 383: "SYS_statx", 384: "SYS_arch_prctl", } syscall_numbers = (function() { mapping = {}; for (var num in syscall_names) { mapping[syscall_names[num]] = num; } return mapping; })(); function syscall_impl(n) { // Special case for writev(stdout), essentially allowing printf to work if (n == syscall_numbers["SYS_writev"]) { if (arguments[1] == 1) { var iov = arguments[2] / 4; var iovcnt = arguments[3]; var rtn = 0; for (var i = 0; i < iovcnt; i++) { var ptr = heap_uint32[iov]; iov++; var len = heap_uint32[iov]; iov++; if (len > 0) { stdio.__write_stdout(stringFromHeap(ptr, len)); rtn += len; } } return rtn; } } // For other syscalls we trace that args and then either return 0 (ignore // the syscall) or -1, delare that we failed. arg_count = arguments.length - 1; var msg = 'syscall' + arg_count + '(' + syscall_names[n]; for (var arg_num = 0; arg_num < arg_count; arg_num++) { msg += ', ' + arguments[arg_num+1] } msg += ')'; print(msg); var ignore_syscalls = [ syscall_numbers["SYS_set_thread_area"], syscall_numbers["SYS_set_tid_address"] ]; if (ignore_syscalls.indexOf(n) != -1) return 0; return -1; } // Syscall API with C libraries. In theory this is the only JavaScript // implementation we need. var syscall = (function() { return { __syscall: syscall_impl, __syscall0: syscall_impl, __syscall1: syscall_impl, __syscall2: syscall_impl, __syscall3: syscall_impl, __syscall4: syscall_impl, __syscall5: syscall_impl, __syscall6: syscall_impl, __syscall_cp: syscall_impl }; })(); // Start with the stub implementations. Further module loads may shadow them. var ffi = (function() { var functions = {env:{memory: memory}}; var libraries = [ musl_hack, // Keep first, overriden later. builtins, ctype, math, runtime, stdio, stdlib, string, unix, syscall // Keep last. ]; for (var l in libraries) for (var f in libraries[l]) if (libraries[l].hasOwnProperty(f) && libraries[l][f] instanceof Function) functions["env"][f] = libraries[l][f]; return functions; })(); if (arguments.length < 1) throw new Error('Expected at least one wasm module to load.'); function load_wasm(file_path) { // TODO this should be split up in load, check dependencies, and then resolve // dependencies. That would make it easier to do lazy loading. We could do // this by catching load exceptions + adding to ffi and trying again, but // we're talking silly there: modules should just tell us what they want. const buf = (typeof readbuffer === 'function') ? new Uint8Array(readbuffer(file_path)) : read(file_path, 'binary'); instance = new WebAssembly.Instance(new WebAssembly.Module(buf), ffi) // For the application exports its memory, we use that rather than // the one we created. In this way this code works with modules that // import or export their memory. if (instance.exports.memory) setHeap(instance.exports.memory.buffer); return instance; } // Load modules in reverse, adding their exports to the ffi object. // This is analogous to how the linker resolves symbols: the later modules // export symbols used by the earlier modules, and allow shadowing. // Note that all modules, as well as the main module, share a heap. var modules = {}; for (var i = arguments.length - 1; i > 0; --i) { var path = arguments[i]; modules[i] = load_wasm(path); for (var f in modules[i].exports) { // TODO wasm modules don't have hasOwnProperty. They probably should. // The code should look like: // if (modules[i].hasOwnProperty(f) && // modules[i][f] instanceof Function) if (modules[i].exports[f] instanceof Function) ffi['env'][f] = modules[i].exports[f]; } } // Load the main module once the ffi object has been fully populated. var main_module = arguments[0]; modules[0] = load_wasm(main_module); // TODO check that `main` exists in modules[0].exports and error out if not. try { var ret = modules[0].exports.main(); stdio.__flush_stdout(); print(main_module + '::main() returned ' + ret); if (ret != stdlib.EXIT_SUCCESS) throw new Error('main reported failure'); } catch (e) { stdio.__flush_stdout(); if (e instanceof TerminateWasmException) { print('Program terminated with: ' + e); if (stdlib.__get_exit_code() != stdlib.EXIT_SUCCESS) { throw stdlib.__get_exit_code(); } } else if (e instanceof NotYetImplementedException) { print(e); throw e; } else { function is_runtime_trap(e) { if ('string' != typeof e) return false; var traps = ['unreachable', 'memory access out of bounds', 'divide by zero', 'divide result unrepresentable', 'remainder by zero', 'integer result unrepresentable', 'invalid function', 'function signature mismatch']; for (var msg in traps) if (e == traps[msg]) return true; return false; } print(is_runtime_trap(e) ? ('Runtime trap: ' + e) : ('Unknown exception of type `' + typeof(e) + '`: ' + e)); throw e; } }