Skip to content

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

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

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 @@ -550,4 +550,5 @@ a license to everyone to use it as detailed in LICENSE.)
* Camil Staps <[email protected]>
* Michael Kircher <[email protected]>
* Sharad Saxena <[email protected]> (copyright owned by Autodesk, Inc.)
* Vasili Skurydzin <[email protected]>
* Jakub Nowakowski <[email protected]>
19 changes: 19 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
51 changes: 51 additions & 0 deletions src/library_little_endian_heap.js
Original file line number Diff line number Diff line change
@@ -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);
4 changes: 4 additions & 0 deletions src/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions src/preamble_minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions src/runtime_assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions tests/optimizer/test-LittleEndianHeap-output.js
Original file line number Diff line number Diff line change
@@ -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];
18 changes: 18 additions & 0 deletions tests/optimizer/test-LittleEndianHeap.js
Original file line number Diff line number Diff line change
@@ -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];
3 changes: 3 additions & 0 deletions tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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)

Expand Down
112 changes: 112 additions & 0 deletions tools/acorn-optimizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work overall!

Instead of doing the multiplies here, I would recommend placing the multiplies inside the LE_HEAP_LOAD_ and LE_HEAP_STORE_ functions for a nice code size win. (only one multiply per function versus once for each call) - unless there is some reason they need to be here?

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 @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down