Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Big Endian Support (using extra acorn AST pass) #13413

Merged
merged 16 commits into from
Mar 9, 2021
Merged
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,4 @@ a license to everyone to use it as detailed in LICENSE.)
* Patric Stout <github@truebrain.nl>
* Jinoh Kang <jinoh.kang.kr@gmail.com>
* Jorge Prendes <jorge.prendes@gmail.com>
* Vasili Skurydzin <vasili.skurydzin@ibm.com>
3 changes: 3 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2726,6 +2726,9 @@ def do_binaryen(target, options, wasm_target):
webassembly.add_emscripten_metadata(wasm_target)

if final_js:
if shared.Settings.LE_HEAP or sys.byteorder == 'big':
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved
final_js = building.little_endian_heap(final_js)

# pthreads memory growth requires some additional JS fixups
if shared.Settings.USE_PTHREADS and shared.Settings.ALLOW_MEMORY_GROWTH:
final_js = building.apply_wasm_memory_growth(final_js)
Expand Down
4 changes: 4 additions & 0 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ if (typeof WebAssembly !== 'object') {
#endif

#include "runtime_safe_heap.js"
#include "runtime_little_endian_heap.js"

// Wasm globals

Expand Down Expand Up @@ -242,6 +243,8 @@ function alignUp(x, multiple) {
}

var HEAP,
/** @type {DataView} */
_HEAP_DATA_VIEW,
/** @type {ArrayBuffer} */
buffer,
/** @type {Int8Array} */
Expand All @@ -267,6 +270,7 @@ var HEAP64;

function updateGlobalBufferAndViews(buf) {
buffer = buf;
Module['_HEAP_DATA_VIEW'] = _HEAP_DATA_VIEW = new DataView(buf);
Module['HEAP8'] = HEAP8 = new Int8Array(buf);
Module['HEAP16'] = HEAP16 = new Int16Array(buf);
Module['HEAP32'] = HEAP32 = new Int32Array(buf);
Expand Down
4 changes: 3 additions & 1 deletion src/preamble_minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

#include "runtime_safe_heap.js"
#include "runtime_little_endian_heap.js"

#if ASSERTIONS
/** @type {function(*, string=)} */
Expand Down Expand Up @@ -61,14 +62,15 @@ Module['wasm'] = base64Decode('<<< WASM_BINARY_DATA >>>');
#include "runtime_functions.js"
#include "runtime_strings.js"

var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64;
var _HEAP_DATA_VIEW, HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64;
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved
var wasmMemory, buffer, wasmTable;

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;
_HEAP_DATA_VIEW = new DataView(b);
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved
HEAP8 = new Int8Array(b);
HEAP16 = new Int16Array(b);
HEAP32 = new Int32Array(b);
Expand Down
8 changes: 0 additions & 8 deletions src/runtime_assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@
*/

#if ASSERTIONS
// Endianness check (note: assumes compiler arch was little-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!';
})();

function abortFnPtrError(ptr, sig) {
#if ASSERTIONS >= 2
var possibleSig = '';
Expand Down
74 changes: 74 additions & 0 deletions src/runtime_little_endian_heap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* Stores value into the heap enforcing LE byte order. Uses DataView methods
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved
* as proxy.
* size is in bytes.
* type can be: 0=unsigned,1=signed,2=float.
*
* @param {number} byteOffset
* @param {ptr} value
* @param {number} size
* @param {number} type
*/
function LE_HEAP_STORE(byteOffset, value, size, type) {
var typeVals = [0, 1, 2];
if (!typeVals.includes(type)) {
throw 'error: type must be one of ' + typeVals;
}
var sizeVals = [2, 4, 8];
if (!sizeVals.includes(size)) {
throw 'error: size must be one of ' + sizeVals;
}
if (size === 2 && type === 0) {
_HEAP_DATA_VIEW.setUint16(byteOffset, value, true);
} else if (size === 2 && type === 1) {
_HEAP_DATA_VIEW.setInt16(byteOffset, value, true);
} else if (size === 4 && type === 0) {
_HEAP_DATA_VIEW.setUint32(byteOffset, value, true);
} else if (size === 4 && type === 1) {
_HEAP_DATA_VIEW.setInt32(byteOffset, value, true);
} else if (size === 4 && type === 2) {
_HEAP_DATA_VIEW.setFloat32(byteOffset, value, true);
} else if (size === 8 && type === 2) {
_HEAP_DATA_VIEW.setFloat64(byteOffset, value, true);
} else if (size === 8 && type === 1) {
_HEAP_DATA_VIEW.setBigInt64(byteOffset, value, true);
} else {
throw 'error: invalid size & type combination: size='+size +', type=' + type;
}
}

/* Loads value from the heap enforcing LE byte order. Uses DataView methods
* as proxy.
* size is in bytes.
* type can be: 0=unsigned,1=signed,2=float
*
* @param {number} byteOffset
* @param {number} size
* @param {number} type
*/
function LE_HEAP_LOAD(byteOffset, size, type) {
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved
var typeVals = [0, 1, 2];
if (!typeVals.includes(type)) {
throw 'error: type must be one of ' + typeVals;
}
var sizeVals = [2, 4, 8];
if (!sizeVals.includes(size)) {
throw 'error: size must be one of ' + sizeVals;
}
if (size === 2 && type === 0) {
return _HEAP_DATA_VIEW.getUint16(byteOffset, true);
} else if (size === 2 && type === 1) {
return _HEAP_DATA_VIEW.getInt16(byteOffset, true);
} else if (size === 4 && type === 0) {
return _HEAP_DATA_VIEW.getUint32(byteOffset, true);
} else if (size === 4 && type === 1) {
return _HEAP_DATA_VIEW.getInt32(byteOffset, true);
} else if (size === 4 && type === 2) {
return _HEAP_DATA_VIEW.getFloat32(byteOffset, true);
} else if (size === 8 && type === 2) {
return _HEAP_DATA_VIEW.getFloat64(byteOffset, true);
} else if (size === 8 && type === 1) {
return _HEAP_DATA_VIEW.getBigInt64(byteOffset, true);
} else {
throw 'error: invalid size & type combination: size='+size +', type=' + type;
}
}
6 changes: 6 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ var IGNORE_CLOSURE_COMPILER_ERRORS = 0;
// [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 LE_HEAP = 0;
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved

// 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.
Expand Down
1 change: 1 addition & 0 deletions tests/code_size/hello_world_wasm.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ WebAssembly.instantiate(d.wasm, {
k = a.d;
h = a.b;
var b = h.buffer;
new DataView(b);
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved
g = new Uint8Array(b);
a.c();
k();
Expand Down
8 changes: 4 additions & 4 deletions tests/code_size/hello_world_wasm.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.html": 665,
"a.html.gz": 427,
"a.js": 322,
"a.js.gz": 259,
"a.js": 338,
"a.js.gz": 269,
"a.wasm": 104,
"a.wasm.gz": 112,
"total": 1091,
"total_gz": 798
"total": 1107,
"total_gz": 808
V-for-Vasili marked this conversation as resolved.
Show resolved Hide resolved
}
127 changes: 127 additions & 0 deletions tools/acorn-optimizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,132 @@ function isEmscriptenHEAP(name) {
}
}

// Replaces each HEAP access with function call that uses DataView to enforce
// LE byte order for HEAP buffer
function littleEndianHeap(ast) {
function makeHeapStoreCallExpression(node, idx, value, bytes, type) {
var args = [
multiply(idx, bytes),
value,
createLiteral(bytes),
createLiteral(type)];
makeCallExpression(node, 'LE_HEAP_STORE', args);
}
function makeHeapLoadCallExpression(node, idx, bytes, type) {
var args = [
multiply(idx, bytes),
createLiteral(bytes),
createLiteral(type)];
makeCallExpression(node, 'LE_HEAP_LOAD', args);
}
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(idx*2, value, 2, 1)"
makeHeapStoreCallExpression(node, idx, value, 2, 1);
break;
}
case 'HEAPU16': {
// change "name[idx] = value" to "LE_HEAP_STORE(idx*2, value, 2, 0)"
makeHeapStoreCallExpression(node, idx, value, 2, 0);
break;
}
case 'HEAP32': {
// change "name[idx] = value" to "LE_HEAP_STORE(idx*4, value, 4, 1)"
makeHeapStoreCallExpression(node, idx, value, 4, 1);
break;
}
case 'HEAPU32': {
// change "name[idx] = value" to "LE_HEAP_STORE(idx*4, value, 4, 0)"
makeHeapStoreCallExpression(node, idx, value, 4, 0);
break;
}
case 'HEAPF32': {
// change "name[idx] = value" to "LE_HEAP_STORE(idx*4, value, 4, 2)"
makeHeapStoreCallExpression(node, idx, value, 4, 2);
break;
}
case 'HEAPF64': {
// change "name[idx] = value" to "LE_HEAP_STORE(idx*8, value, 8, 2)"
makeHeapStoreCallExpression(node, idx, value, 8, 2);
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(idx*2, 2, 1)"
makeHeapLoadCallExpression(node, idx, 2, 1);
break;
}
case 'HEAPU16': {
// change "name[idx]" to "LE_HEAP_LOAD(idx*2, 2, 0)"
makeHeapLoadCallExpression(node, idx, 2, 0);
break;
}
case 'HEAP32': {
// change "name[idx]" to "LE_HEAP_LOAD(idx*4, 4, 1)"
makeHeapLoadCallExpression(node, idx, 4, 1);
break;
}
case 'HEAPU32': {
// change "name[idx]" to "LE_HEAP_LOAD(idx*4, 4, 0)"
makeHeapLoadCallExpression(node, idx, 4, 0);
break;
}
case 'HEAPF32': {
// change "name[idx]" to "LE_HEAP_LOAD(idx*4, 4, 2)"
makeHeapLoadCallExpression(node, idx, 4, 2);
break;
}
case 'HEAPF64': {
// change "name[idx]" to "LE_HEAP_LOAD(idx*8, 8, 2)"
makeHeapLoadCallExpression(node, idx, 8, 2);
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.
Expand Down Expand Up @@ -1445,6 +1571,7 @@ var registry = {
minifyWhitespace: function() { minifyWhitespace = true },
noPrint: function() { noPrint = true },
dump: function() { dump(ast) },
littleEndianHeap: littleEndianHeap,
growableHeap: growableHeap,
unsignPointers: unsignPointers,
asanify: asanify,
Expand Down
5 changes: 5 additions & 0 deletions tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,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'])
Expand Down