From 25d6e80f0683d74882f3415e1c51008c93a830d6 Mon Sep 17 00:00:00 2001 From: Wouter van Oortmerssen Date: Mon, 4 Oct 2021 15:56:26 -0700 Subject: [PATCH 1/2] [Memory64] Core JS runtime changes for 64-bit --- src/library.js | 14 +++++--- src/library_formatString.js | 2 ++ src/parseTools.js | 72 +++++++++++++++++++++++++++++++------ src/postamble.js | 4 +-- src/runtime_strings.js | 1 + src/settings.js | 1 + 6 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/library.js b/src/library.js index 1d5b51e560776..ff0d83b235cae 100644 --- a/src/library.js +++ b/src/library.js @@ -91,6 +91,7 @@ LibraryManager.library = { utime__proxy: 'sync', utime__sig: 'iii', utime: function(path, times) { + {{{ from64('times') }}}; // int utime(const char *path, const struct utimbuf *times); // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/utime.h.html var time; @@ -458,6 +459,7 @@ LibraryManager.library = { time__sig: 'ii', time: function(ptr) { + {{{ from64('ptr') }}}; var ret = (Date.now()/1000)|0; if (ptr) { {{{ makeSetValue('ptr', 0, 'ret', 'i32') }}}; @@ -3102,6 +3104,7 @@ LibraryManager.library = { $readAsmConstArgsArray: '=[]', $readAsmConstArgs__deps: ['$readAsmConstArgsArray'], $readAsmConstArgs: function(sigPtr, buf) { + {{{ from64(['sigPtr', 'buf']) }}}; #if ASSERTIONS // Nobody should have mutated _readAsmConstArgsArray underneath us to be something else than an array. assert(Array.isArray(readAsmConstArgsArray)); @@ -3646,20 +3649,23 @@ LibraryManager.library = { // mode are created here and imported by the module. // Mark with `__import` so these are usable from native code. This is needed // because, by default, only functions can be be imported. - __stack_pointer: "new WebAssembly.Global({'value': 'i32', 'mutable': true}, {{{ STACK_BASE }}})", + __stack_pointer: "new WebAssembly.Global({'value': '{{{ SIZE_TYPE }}}', 'mutable': true}, {{{ to64(STACK_BASE) }}})", __stack_pointer__import: true, // tell the memory segments where to place themselves - __memory_base: '{{{ GLOBAL_BASE }}}', + __memory_base: "new WebAssembly.Global({'value': '{{{ SIZE_TYPE}}}', 'mutable': false}, {{{ to64(GLOBAL_BASE) }}})", __memory_base__import: true, // the wasm backend reserves slot 0 for the NULL function pointer - __table_base: 1, + __table_base: "new WebAssembly.Global({'value': '{{{ SIZE_TYPE }}}', 'mutable': false}, {{{ to64(1) }}})", __table_base__import: true, +#if MEMORY64 + __table_base32: 1, +#endif // To support such allocations during startup, track them on __heap_base and // then when the main module is loaded it reads that value and uses it to // initialize sbrk (the main module is relocatable itself, and so it does not // have __heap_base hardcoded into it - it receives it from JS as an extern // global, basically). - __heap_base: '{{{ HEAP_BASE }}}', + __heap_base: '{{{ to64(HEAP_BASE) }}}', __heap_base__import: true, #endif }; diff --git a/src/library_formatString.js b/src/library_formatString.js index 77bd3c40ce0f3..c9415c15dde7f 100644 --- a/src/library_formatString.js +++ b/src/library_formatString.js @@ -52,6 +52,7 @@ mergeInto(LibraryManager.library, { #endif ], $formatString: function(format, varargs) { + {{{ from64(['format', 'varargs']) }}}; #if ASSERTIONS assert((varargs & 3) === 0); #endif @@ -470,6 +471,7 @@ mergeInto(LibraryManager.library, { // printf/puts/strlen implementations for when musl is not pulled in - very // partial. useful for tests, and when bootstrapping structInfo strlen: function(ptr) { + {{{ from64('ptr') }}}; var end = ptr; while (HEAPU8[end]) ++end; return end - ptr; diff --git a/src/parseTools.js b/src/parseTools.js index 92490fe7106d4..037bbf1dd0919 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -197,10 +197,20 @@ function isStructPointerType(type) { return !Compiletime.isNumberType(type) && type[0] == '%'; } +const POINTER_SIZE = MEMORY64 ? 8 : 4; +const POINTER_BITS = POINTER_SIZE * 8; +const POINTER_TYPE = 'i' + POINTER_BITS; + +const SIZE_TYPE = POINTER_TYPE; + function isPointerType(type) { return type[type.length - 1] == '*'; } +function sizeT(x) { + return MEMORY64 ? `BigInt(${x})` : x; +} + function isArrayType(type) { return /^\[\d+\ x\ (.*)\]/.test(type); } @@ -235,7 +245,7 @@ function isIntImplemented(type) { // Note: works for iX types and structure types, not pointers (even though they are implemented as ints) function getBits(type, allowPointers) { - if (allowPointers && isPointerType(type)) return 32; + if (allowPointers && isPointerType(type)) return pointerBits(); if (!type) return 0; if (type[0] == 'i') { const left = type.substr(1); @@ -480,7 +490,7 @@ function checkSafeHeap() { } function getHeapOffset(offset, type) { - if (Runtime.getNativeFieldSize(type) > 4 && type == 'i64') { + if (!WASM_BIGINT && Runtime.getNativeFieldSize(type) > 4 && type == 'i64') { // we emulate 64-bit integer values as 32 in asmjs-unknown-emscripten, but not double type = 'i32'; } @@ -623,7 +633,16 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa return asmCoercion('SAFE_HEAP_LOAD' + ((type in Compiletime.FLOAT_TYPES) ? '_D' : '') + '(' + asmCoercion(offset, 'i32') + ', ' + Runtime.getNativeTypeSize(type) + ', ' + (!!unsigned + 0) + ')', type, unsigned ? 'u' : undefined); } } - return getHeapForType(type, unsigned) + '[' + getHeapOffset(offset, type) + ']'; + + const slab = getHeapForType(type, unsigned); + let ret = slab + '[' + getHeapOffset(offset, type) + ']'; + if (slab.substr(slab.length - 2) == '64') { + ret = `Number(${ret})`; + } + if (forceAsm) { + ret = asmCoercion(ret, type); + } + return ret; } /** @@ -665,7 +684,7 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, return '(' + makeSetTempDouble(0, 'double', value) + ',' + makeSetValue(ptr, pos, makeGetTempDouble(0, 'i32'), 'i32', noNeedFirst, ignore, align, noSafe, ',') + ',' + makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), makeGetTempDouble(1, 'i32'), 'i32', noNeedFirst, ignore, align, noSafe, ',') + ')'; - } else if (type == 'i64') { + } else if (!WASM_BIGINT && type == 'i64') { return '(tempI64 = [' + splitI64(value) + '],' + makeSetValue(ptr, pos, 'tempI64[0]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ',' + makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'tempI64[1]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ')'; @@ -705,7 +724,12 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, return 'SAFE_HEAP_STORE' + ((type in Compiletime.FLOAT_TYPES) ? '_D' : '') + '(' + asmCoercion(offset, 'i32') + ', ' + asmCoercion(value, type) + ', ' + Runtime.getNativeTypeSize(type) + ')'; } } - return getHeapForType(type) + '[' + getHeapOffset(offset, type) + '] = ' + value; + + const slab = getHeapForType(type); + if (slab == 'HEAPU64' || slab == 'HEAP64') { + value = `BigInt(${value})`; + } + return slab + '[' + getHeapOffset(offset, type) + '] = ' + value; } const UNROLL_LOOP_MAX = 8; @@ -862,13 +886,15 @@ function getFastValue(a, op, b, type) { function calcFastOffset(ptr, pos, noNeedFirst) { assert(!noNeedFirst); + if (typeof ptr == 'bigint') ptr = Number(ptr); + if (typeof pos == 'bigint') pos = Number(pos); return getFastValue(ptr, '+', pos, 'i32'); } function getHeapForType(type, unsigned) { assert(type); if (isPointerType(type)) { - type = 'i32'; // Hardcoded 32-bit + type = POINTER_TYPE; } switch (type) { case 'i1': @@ -876,9 +902,13 @@ function getHeapForType(type, unsigned) { return unsigned ? 'HEAPU8' : 'HEAP8'; case 'i16': return unsigned ? 'HEAPU16' : 'HEAP16'; + case 'i64': + if (WASM_BIGINT) { + return unsigned ? 'HEAPU64' : 'HEAP64'; + } + // fall through case '<4 x i32>': case 'i32': - case 'i64': return unsigned ? 'HEAPU32' : 'HEAP32'; case 'double': return 'HEAPF64'; @@ -908,10 +938,7 @@ function makeThrow(what) { } function makeSignOp(value, type, op, force, ignore) { - if (type == 'i64') { - return value; // these are always assumed to be two 32-bit unsigneds. - } - if (isPointerType(type)) type = 'i32'; // Pointers are treated as 32-bit ints + if (isPointerType(type)) type = POINTER_TYPE; if (!value) return value; let bits; let full; @@ -1292,6 +1319,29 @@ function sendI64Argument(low, high) { return low + ', ' + high; } +// Any function called from wasm64 may have bigint args, this function takes +// a list of variable names to convert to number. +function from64(x) { + if (!MEMORY64) { + return ''; + } + if (Array.isArray(x)) { + let ret = ''; + for (e of x) ret += from64(e); + return ret; + } else { + return `${x} = Number(${x});`; + } +} + +function to64(x) { + if (MEMORY64) { + return `BigInt(${x})`; + } else { + return `${x}`; + } +} + // Add assertions to catch common errors when using the Promise object we // create on Module.ready() and return from MODULARIZE Module() invocations. function addReadyPromiseAssertions(promise) { diff --git a/src/postamble.js b/src/postamble.js index 15f87d8883fca..718398d4125c1 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -149,7 +149,7 @@ function callMain(args) { args = args || []; var argc = args.length+1; - var argv = stackAlloc((argc + 1) * {{{ Runtime.POINTER_SIZE }}}); + var argv = stackAlloc((argc + 1) * {{{ `${Runtime.POINTER_SIZE}` }}}); HEAP32[argv >> 2] = allocateUTF8OnStack(thisProgram); for (var i = 1; i < argc; i++) { HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i - 1]); @@ -215,7 +215,7 @@ function stackCheckInit() { // here. // TODO(sbc): Move writeStackCookie to native to to avoid this. #if RELOCATABLE - _emscripten_stack_set_limits({{{ STACK_BASE }}}, {{{ STACK_MAX }}}); + _emscripten_stack_set_limits({{{ to64(STACK_BASE) }}}, {{{ to64(STACK_MAX) }}}); #else _emscripten_stack_init(); #endif diff --git a/src/runtime_strings.js b/src/runtime_strings.js index 63d599e1bd70c..396d731413186 100644 --- a/src/runtime_strings.js +++ b/src/runtime_strings.js @@ -128,6 +128,7 @@ function UTF8ArrayToString(heap, idx, maxBytesToRead) { * @return {string} */ function UTF8ToString(ptr, maxBytesToRead) { + {{{ from64('ptr') }}}; #if CAN_ADDRESS_2GB ptr >>>= 0; #endif diff --git a/src/settings.js b/src/settings.js index af7aee12731a2..172cb6f6bba3d 100644 --- a/src/settings.js +++ b/src/settings.js @@ -237,6 +237,7 @@ var MEMORY_GROWTH_LINEAR_STEP = -1; // the full end-to-end wasm64 mode, and 2 is wasm64 for clang/lld but lowered to // wasm32 in Binaryen (such that it can run on wasm32 engines, while internally // using i64 pointers). +// Assumes WASM_BIGINT. // [compile+link] var MEMORY64 = 0; From 8e87d8bd13a85148e9cac1ec945227891409816d Mon Sep 17 00:00:00 2001 From: Wouter van Oortmerssen Date: Mon, 4 Oct 2021 16:50:41 -0700 Subject: [PATCH 2/2] Code review changes --- src/library.js | 6 +++--- src/parseTools.js | 9 +++------ src/postamble.js | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/library.js b/src/library.js index ff0d83b235cae..68b173a63dece 100644 --- a/src/library.js +++ b/src/library.js @@ -3649,13 +3649,13 @@ LibraryManager.library = { // mode are created here and imported by the module. // Mark with `__import` so these are usable from native code. This is needed // because, by default, only functions can be be imported. - __stack_pointer: "new WebAssembly.Global({'value': '{{{ SIZE_TYPE }}}', 'mutable': true}, {{{ to64(STACK_BASE) }}})", + __stack_pointer: "new WebAssembly.Global({'value': '{{{ POINTER_TYPE }}}', 'mutable': true}, {{{ to64(STACK_BASE) }}})", __stack_pointer__import: true, // tell the memory segments where to place themselves - __memory_base: "new WebAssembly.Global({'value': '{{{ SIZE_TYPE}}}', 'mutable': false}, {{{ to64(GLOBAL_BASE) }}})", + __memory_base: "new WebAssembly.Global({'value': '{{{ POINTER_TYPE }}}', 'mutable': false}, {{{ to64(GLOBAL_BASE) }}})", __memory_base__import: true, // the wasm backend reserves slot 0 for the NULL function pointer - __table_base: "new WebAssembly.Global({'value': '{{{ SIZE_TYPE }}}', 'mutable': false}, {{{ to64(1) }}})", + __table_base: "new WebAssembly.Global({'value': '{{{ POINTER_TYPE }}}', 'mutable': false}, {{{ to64(1) }}})", __table_base__import: true, #if MEMORY64 __table_base32: 1, diff --git a/src/parseTools.js b/src/parseTools.js index 037bbf1dd0919..35e8ad0f3fc09 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -245,7 +245,7 @@ function isIntImplemented(type) { // Note: works for iX types and structure types, not pointers (even though they are implemented as ints) function getBits(type, allowPointers) { - if (allowPointers && isPointerType(type)) return pointerBits(); + if (allowPointers && isPointerType(type)) return POINTER_SIZE; if (!type) return 0; if (type[0] == 'i') { const left = type.substr(1); @@ -1335,11 +1335,8 @@ function from64(x) { } function to64(x) { - if (MEMORY64) { - return `BigInt(${x})`; - } else { - return `${x}`; - } + if (!MEMORY64) return x; + return `BigInt(${x})`; } // Add assertions to catch common errors when using the Promise object we diff --git a/src/postamble.js b/src/postamble.js index 718398d4125c1..0fdd070f49db6 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -149,7 +149,7 @@ function callMain(args) { args = args || []; var argc = args.length+1; - var argv = stackAlloc((argc + 1) * {{{ `${Runtime.POINTER_SIZE}` }}}); + var argv = stackAlloc((argc + 1) * {{{ Runtime.POINTER_SIZE }}}); HEAP32[argv >> 2] = allocateUTF8OnStack(thisProgram); for (var i = 1; i < argc; i++) { HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i - 1]);