diff --git a/AUTHORS b/AUTHORS index f9c637a2359c5..06f26fab91a5e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -550,4 +550,5 @@ a license to everyone to use it as detailed in LICENSE.) * Camil Staps * Michael Kircher * Sharad Saxena (copyright owned by Autodesk, Inc.) +* Vasili Skurydzin * Jakub Nowakowski diff --git a/emcc.py b/emcc.py index b6b1f717b48e5..5e3040448bc35 100755 --- a/emcc.py +++ b/emcc.py @@ -1462,6 +1462,22 @@ def default_setting(name, new_default): diagnostics.warning('emcc', 'linking a library with `-shared` will emit a static object file. This is a form of emulation to support existing build systems. If you want to build a runtime shared library use the SIDE_MODULE setting.') link_to_object = True + if shared.Settings.SUPPORT_BIG_ENDIAN: + shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ + '$LE_HEAP_STORE_U16', + '$LE_HEAP_STORE_I16', + '$LE_HEAP_STORE_U32', + '$LE_HEAP_STORE_I32', + '$LE_HEAP_STORE_F32', + '$LE_HEAP_STORE_F64', + '$LE_HEAP_LOAD_U16', + '$LE_HEAP_LOAD_I16', + '$LE_HEAP_LOAD_U32', + '$LE_HEAP_LOAD_I32', + '$LE_HEAP_LOAD_F32', + '$LE_HEAP_LOAD_F64' + ] + if shared.Settings.STACK_OVERFLOW_CHECK: shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_end', '_emscripten_stack_get_free'] if shared.Settings.RELOCATABLE: @@ -2821,6 +2837,9 @@ def do_binaryen(target, options, wasm_target): webassembly.add_emscripten_metadata(wasm_target) if final_js: + if shared.Settings.SUPPORT_BIG_ENDIAN: + final_js = building.little_endian_heap(final_js) + # >=2GB heap support requires pointers in JS to be unsigned. rather than # require all pointers to be unsigned by default, which increases code size # a little, keep them signed, and just unsign them here if we need that. diff --git a/src/library_little_endian_heap.js b/src/library_little_endian_heap.js new file mode 100644 index 0000000000000..d4b3f92bfc3eb --- /dev/null +++ b/src/library_little_endian_heap.js @@ -0,0 +1,51 @@ +var LibraryLittleEndianHeap = { + $LE_HEAP_STORE_U16: function(byteOffset, value) { + HEAP_DATA_VIEW.setUint16(byteOffset, value, true); + }, + + $LE_HEAP_STORE_I16: function(byteOffset, value) { + HEAP_DATA_VIEW.setInt16(byteOffset, value, true); + }, + + $LE_HEAP_STORE_U32: function(byteOffset, value) { + HEAP_DATA_VIEW.setUint32(byteOffset, value, true); + }, + + $LE_HEAP_STORE_I32: function(byteOffset, value) { + HEAP_DATA_VIEW.setInt32(byteOffset, value, true); + }, + + $LE_HEAP_STORE_F32: function(byteOffset, value) { + HEAP_DATA_VIEW.setFloat32(byteOffset, value, true); + }, + + $LE_HEAP_STORE_F64: function(byteOffset, value) { + HEAP_DATA_VIEW.setFloat64(byteOffset, value, true); + }, + + $LE_HEAP_LOAD_U16: function(byteOffset) { + return HEAP_DATA_VIEW.getUint16(byteOffset, true); + }, + + $LE_HEAP_LOAD_I16: function(byteOffset) { + return HEAP_DATA_VIEW.getInt16(byteOffset, true); + }, + + $LE_HEAP_LOAD_U32: function(byteOffset) { + return HEAP_DATA_VIEW.getUint32(byteOffset, true); + }, + + $LE_HEAP_LOAD_I32: function(byteOffset) { + return HEAP_DATA_VIEW.getInt32(byteOffset, true); + }, + + $LE_HEAP_LOAD_F32: function(byteOffset) { + return HEAP_DATA_VIEW.getFloat32(byteOffset, true); + }, + + $LE_HEAP_LOAD_F64: function(byteOffset) { + return HEAP_DATA_VIEW.getFloat64(byteOffset, true); + } +} + +mergeInto(LibraryManager.library, LibraryLittleEndianHeap); diff --git a/src/modules.js b/src/modules.js index 0a94d52ea7bcf..dc9c56690a536 100644 --- a/src/modules.js +++ b/src/modules.js @@ -173,6 +173,10 @@ var LibraryManager = { ]; } + if (SUPPORT_BIG_ENDIAN) { + libraries.push('library_little_endian_heap.js'); + } + // Deduplicate libraries to avoid processing any library file multiple times libraries = libraries.filter(function(item, pos) { return libraries.indexOf(item) == pos; diff --git a/src/preamble.js b/src/preamble.js index a8a8a1c61617f..517bcfa561083 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -261,12 +261,19 @@ var HEAP, /** @type {Float64Array} */ HEAPF64; +#if SUPPORT_BIG_ENDIAN +var HEAP_DATA_VIEW; +#endif + #if WASM_BIGINT var HEAP64; #endif function updateGlobalBufferAndViews(buf) { buffer = buf; +#if SUPPORT_BIG_ENDIAN + Module['HEAP_DATA_VIEW'] = HEAP_DATA_VIEW = new DataView(buf); +#endif Module['HEAP8'] = HEAP8 = new Int8Array(buf); Module['HEAP16'] = HEAP16 = new Int16Array(buf); Module['HEAP32'] = HEAP32 = new Int32Array(buf); diff --git a/src/preamble_minimal.js b/src/preamble_minimal.js index e19b774076092..474c791ed81d6 100644 --- a/src/preamble_minimal.js +++ b/src/preamble_minimal.js @@ -64,11 +64,18 @@ Module['wasm'] = base64Decode('<<< WASM_BINARY_DATA >>>'); var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64; var wasmMemory, buffer, wasmTable; +#if SUPPORT_BIG_ENDIAN +var HEAP_DATA_VIEW; +#endif + function updateGlobalBufferAndViews(b) { #if ASSERTIONS && USE_PTHREADS assert(b instanceof SharedArrayBuffer, 'requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag'); #endif buffer = b; +#if SUPPORT_BIG_ENDIAN + HEAP_DATA_VIEW = new DataView(b); +#endif HEAP8 = new Int8Array(b); HEAP16 = new Int16Array(b); HEAP32 = new Int32Array(b); diff --git a/src/runtime_assertions.js b/src/runtime_assertions.js index 510b0b727df37..81e51e9cb5e3f 100644 --- a/src/runtime_assertions.js +++ b/src/runtime_assertions.js @@ -5,13 +5,16 @@ */ #if ASSERTIONS -// Endianness check (note: assumes compiler arch was little-endian) + +// Endianness check +#if !SUPPORT_BIG_ENDIAN (function() { var h16 = new Int16Array(1); var h8 = new Int8Array(h16.buffer); h16[0] = 0x6373; - if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian!'; + if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -s SUPPORT_BIG_ENDIAN=1 to bypass)'; })(); +#endif function abortFnPtrError(ptr, sig) { #if ASSERTIONS >= 2 diff --git a/src/settings.js b/src/settings.js index eb43818c9c4c5..a1f2589b00c9f 100644 --- a/src/settings.js +++ b/src/settings.js @@ -297,6 +297,12 @@ var DECLARE_ASM_MODULE_EXPORTS = 1; // [compile+link] var INLINING_LIMIT = 0; +// If set to 1, perform acorn pass that converts each HEAP access into a +// function call that uses DataView to enforce LE byte order for HEAP buffer; +// This makes generated JavaScript run on BE as well as LE machines. (If 0, only +// LE systems are supported). Does not affect generated wasm. +var SUPPORT_BIG_ENDIAN = 0; + // Check each write to the heap, for example, this will give a clear // error on what would be segfaults in a native build (like dereferencing // 0). See runtime_safe_heap.js for the actual checks performed. diff --git a/tests/optimizer/test-LittleEndianHeap-output.js b/tests/optimizer/test-LittleEndianHeap-output.js new file mode 100644 index 0000000000000..321177386fee3 --- /dev/null +++ b/tests/optimizer/test-LittleEndianHeap-output.js @@ -0,0 +1,18 @@ +a = HEAP8[x]; +HEAP8[x] = a; +a = HEAPU8[x]; +HEAPU8[x] = a; +a = LE_HEAP_LOAD_I16(x * 2); +LE_HEAP_STORE_I16(x * 2, a); +a = LE_HEAP_LOAD_U16(x * 2); +LE_HEAP_STORE_U16(x * 2, a); +a = LE_HEAP_LOAD_I32(x * 4); +LE_HEAP_STORE_I32(x * 4, a); +a = LE_HEAP_LOAD_U32(x * 4); +LE_HEAP_STORE_U32(x * 4, a); +a = LE_HEAP_LOAD_F32(x * 4); +LE_HEAP_STORE_F32(x * 4, a); +a = LE_HEAP_LOAD_F64(x * 8); +LE_HEAP_STORE_F64(x * 8, a); +HEAP[x]; +HeAp[x]; diff --git a/tests/optimizer/test-LittleEndianHeap.js b/tests/optimizer/test-LittleEndianHeap.js new file mode 100644 index 0000000000000..8b12dea0a98af --- /dev/null +++ b/tests/optimizer/test-LittleEndianHeap.js @@ -0,0 +1,18 @@ +a = HEAP8[x]; // HEAP8 +HEAP8[x] = a; +a = HEAPU8[x]; // HEAPU8 +HEAPU8[x] = a; +a = HEAP16[x]; // HEAP16 +HEAP16[x] = a; +a = HEAPU16[x]; // HEAPU16 +HEAPU16[x] = a; +a = HEAP32[x]; // HEAPI32 +HEAP32[x] = a; +a = HEAPU32[x]; // HEAPU32 +HEAPU32[x] = a; +a = HEAPF32[x]; // HEAPF32 +HEAPF32[x] = a; +a = HEAPF64[x]; // HEAPF64 +HEAPF64[x] = a; +HEAP[x]; // should not be changed +HeAp[x]; diff --git a/tests/test_other.py b/tests/test_other.py index 09828dc7bdff0..46c7760f537d0 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -1864,6 +1864,7 @@ def test_js_optimizer(self): 'asanify', 'safeHeap', 'minifyLocals', + 'littleEndianHeap' ] for input, expected, passes in [ (path_from_root('tests', 'optimizer', 'test-js-optimizer-minifyGlobals.js'), open(path_from_root('tests', 'optimizer', 'test-js-optimizer-minifyGlobals-output.js')).read(), @@ -1912,6 +1913,8 @@ def test_js_optimizer(self): ['asanify']), (path_from_root('tests', 'optimizer', 'test-safeHeap.js'), open(path_from_root('tests', 'optimizer', 'test-safeHeap-output.js')).read(), ['safeHeap']), + (path_from_root('tests', 'optimizer', 'test-LittleEndianHeap.js'), open(path_from_root('tests', 'optimizer', 'test-LittleEndianHeap-output.js')).read(), + ['littleEndianHeap']), ]: print(input, passes) diff --git a/tools/acorn-optimizer.js b/tools/acorn-optimizer.js index b384013767f43..088d5936c44f4 100644 --- a/tools/acorn-optimizer.js +++ b/tools/acorn-optimizer.js @@ -974,6 +974,117 @@ function isEmscriptenHEAP(name) { } } +// Replaces each HEAP access with function call that uses DataView to enforce +// LE byte order for HEAP buffer +function littleEndianHeap(ast) { + recursiveWalk(ast, { + FunctionDeclaration: function(node, c) { + // do not recurse into LE_HEAP_STORE, LE_HEAP_LOAD functions + if (!(node.id.type === 'Identifier' && + node.id.name.startsWith('LE_HEAP'))) { + c(node.body); + } + }, + AssignmentExpression: function(node, c) { + var target = node.left; + var value = node.right; + c(value); + if (!isHEAPAccess(target)) { + // not accessing the HEAP + c(target); + } else { + // replace the heap access with LE_HEAP_STORE + var name = target.object.name; + var idx = target.property; + switch (target.object.name) { + case 'HEAP8': + case 'HEAPU8': { + // no action required - storing only 1 byte + break; + } + case 'HEAP16': { + // change "name[idx] = value" to "LE_HEAP_STORE_I16(idx*2, value)" + makeCallExpression(node, 'LE_HEAP_STORE_I16', [multiply(idx, 2), value]); + break; + } + case 'HEAPU16': { + // change "name[idx] = value" to "LE_HEAP_STORE_U16(idx*2, value)" + makeCallExpression(node, 'LE_HEAP_STORE_U16', [multiply(idx, 2), value]); + break; + } + case 'HEAP32': { + // change "name[idx] = value" to "LE_HEAP_STORE_I32(idx*4, value)" + makeCallExpression(node, 'LE_HEAP_STORE_I32', [multiply(idx, 4), value]); + break; + } + case 'HEAPU32': { + // change "name[idx] = value" to "LE_HEAP_STORE_U32(idx*4, value)" + makeCallExpression(node, 'LE_HEAP_STORE_U32', [multiply(idx, 4), value]); + break; + } + case 'HEAPF32': { + // change "name[idx] = value" to "LE_HEAP_STORE_F32(idx*4, value)" + makeCallExpression(node, 'LE_HEAP_STORE_F32', [multiply(idx, 4), value]); + break; + } + case 'HEAPF64': { + // change "name[idx] = value" to "LE_HEAP_STORE_F64(idx*8, value)" + makeCallExpression(node, 'LE_HEAP_STORE_F64', [multiply(idx, 8), value]); + break; + } + }; + } + }, + MemberExpression: function(node, c) { + c(node.property); + if (!isHEAPAccess(node)) { + // not accessing the HEAP + c(node.object); + } else { + // replace the heap access with LE_HEAP_LOAD + var idx = node.property; + switch (node.object.name) { + case 'HEAP8': + case 'HEAPU8': { + // no action required - loading only 1 byte + break; + } + case 'HEAP16': { + // change "name[idx]" to "LE_HEAP_LOAD_I16(idx*2)" + makeCallExpression(node, 'LE_HEAP_LOAD_I16', [multiply(idx, 2)]); + break; + } + case 'HEAPU16': { + // change "name[idx]" to "LE_HEAP_LOAD_U16(idx*2)" + makeCallExpression(node, 'LE_HEAP_LOAD_U16', [multiply(idx, 2)]); + break; + } + case 'HEAP32': { + // change "name[idx]" to "LE_HEAP_LOAD_I32(idx*4)" + makeCallExpression(node, 'LE_HEAP_LOAD_I32', [multiply(idx, 4)]); + break; + } + case 'HEAPU32': { + // change "name[idx]" to "LE_HEAP_LOAD_U32(idx*4)" + makeCallExpression(node, 'LE_HEAP_LOAD_U32', [multiply(idx, 4)]); + break; + } + case 'HEAPF32': { + // change "name[idx]" to "LE_HEAP_LOAD_F32(idx*4)" + makeCallExpression(node, 'LE_HEAP_LOAD_F32', [multiply(idx, 4)]); + break; + } + case 'HEAPF64': { + // change "name[idx]" to "LE_HEAP_LOAD_F64(idx*8)" + makeCallExpression(node, 'LE_HEAP_LOAD_F64', [multiply(idx, 8)]); + break; + } + }; + } + }, + }); +} + // Instrument heap accesses to call GROWABLE_HEAP_* helper functions instead, which allows // pthreads + memory growth to work (we check if the memory was grown on another thread // in each access), see #8365. @@ -1610,6 +1721,7 @@ var registry = { noPrint: function() { noPrint = true }, last: function() {}, // TODO: remove 'last' in the python driver code dump: function() { dump(ast) }, + littleEndianHeap: littleEndianHeap, growableHeap: growableHeap, unsignPointers: unsignPointers, minifyLocals: minifyLocals, diff --git a/tools/building.py b/tools/building.py index fab87eea240f7..cc03a0934a15d 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1379,6 +1379,11 @@ def emit_debug_on_side(wasm_file, wasm_file_with_dwarf): f.write(contents) +def little_endian_heap(js_file): + logger.debug('enforcing little endian heap byte order') + return acorn_optimizer(js_file, ['littleEndianHeap']) + + def apply_wasm_memory_growth(js_file): logger.debug('supporting wasm memory growth with pthreads') fixed = acorn_optimizer(js_file, ['growableHeap'])