Add internal CAN_ADDRESS_2GB flag which is set for 2GB+ heap support (#10601)
To support that mode add a JS optimizer pass that fixes up pointers in JS libraries. Doing this as a pass is nice because our JS libraries can still be written with signed pointers, which is smaller and may be faster on some VMs (signed values fit in 31-bit smis). This will fix up user code as well, automatically. The pass looks for HEAP8 etc. symbols on which we do HEAP8[something] and ensures the pointer is unsigned. It also fixes up .set() and other calls that receive pointers. One compromise the pass makes is that it looks not just for the canonical HEAP8 etc. names but also HEAP and heap. Those are used in our JS library code a bunch, so fixing those up too prevents a lot more work in that code, and can help user code too. There is some risk to doing so, if a user has code with that name but actually doesn't want the unsigning; however, the risk is small (we just force their indexes on an array to be non-negative; likely they don't want that anyhow!), and I think the reverse risk (a user has code that should be changed, but forgets) is much more likely, so this seems safest. A later PR will add our JS library fixes, which will show that even with this extension there is still code that must be handled manually, so I think it's best to do as much automatically as we can. This also changes MAXIMUM_MEMORY to be 2GB. That is effectively no change from the previous -1 (no limit) since 2GB was the absolute limit anyhow. This has two benefits: first, people must opt-in explicitly to getting 2GB+ heap support with the code size downside, and also we expect only some browsers will support 2GB+ heaps for now, so requiring explicit opt-in from devs is safer to avoid web compat issues (i.e., only users that really need such heaps will set them, and then they may only work in some browsers, but at least that won't happen for users that could actually run in all browsers). Note that emmalloc is not designed to work with 2GB+ heaps, so show a useful error in that case. This is a minor breaking change for users in that building with emmalloc + memory growth will hit the error, forcing the user to set a maximum to the growth. This is part of a set of 3+ PRs for #6566, together with #10600
This commit is contained in:
Родитель
cce365f32e
Коммит
57cb2a4e3f
|
@ -17,6 +17,12 @@ See docs/process.md for how version tagging works.
|
|||
|
||||
Current Trunk
|
||||
-------------
|
||||
- Optionally support 2GB+ heap sizes. To do this we make the JS code have unsigned
|
||||
pointers (we need all 32 bits in them now), which can slightly increase code
|
||||
size (>>> instead of >>). This only happens when the heap size may be over
|
||||
2GB, which you must opt into explicity, by setting `MAXIMUM_MEMORY` to a
|
||||
higher value (i.e. by default you do not get support for 2GB+ heaps).
|
||||
See #10601
|
||||
- `--llvm-lto` flag is now ignored when using the upstream llvm backend.
|
||||
With the upstrema backend LTO is controlled via `-flto`.
|
||||
- Require format string for emscripten_log.
|
||||
|
|
25
emcc.py
25
emcc.py
|
@ -2002,6 +2002,23 @@ There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR P
|
|||
if not shared.Settings.LEGALIZE_JS_FFI:
|
||||
assert shared.Building.is_wasm_only(), 'LEGALIZE_JS_FFI incompatible with RUNNING_JS_OPTS.'
|
||||
|
||||
# check if we can address the 2GB mark and higher: either if we start at
|
||||
# 2GB, or if we allow growth to either any amount or to 2GB or more.
|
||||
if shared.Settings.WASM_BACKEND and \
|
||||
(shared.Settings.INITIAL_MEMORY > 2 * 1024 * 1024 * 1024 or
|
||||
(shared.Settings.ALLOW_MEMORY_GROWTH and
|
||||
(shared.Settings.MAXIMUM_MEMORY < 0 or
|
||||
shared.Settings.MAXIMUM_MEMORY > 2 * 1024 * 1024 * 1024))):
|
||||
shared.Settings.CAN_ADDRESS_2GB = 1
|
||||
if shared.Settings.MALLOC == 'emmalloc':
|
||||
if shared.Settings.INITIAL_MEMORY >= 2 * 1024 * 1024 * 1024:
|
||||
suggestion = 'decrease INITIAL_MEMORY'
|
||||
elif shared.Settings.MAXIMUM_MEMORY < 0:
|
||||
suggestion = 'set MAXIMUM_MEMORY'
|
||||
else:
|
||||
suggestion = 'decrease MAXIMUM_MEMORY'
|
||||
exit_with_error('emmalloc only works on <2GB of memory. Use the default allocator, or ' + suggestion)
|
||||
|
||||
shared.Settings.EMSCRIPTEN_VERSION = shared.EMSCRIPTEN_VERSION
|
||||
shared.Settings.PROFILING_FUNCS = options.profiling_funcs
|
||||
shared.Settings.SOURCE_MAP_BASE = options.source_map_base or ''
|
||||
|
@ -3179,6 +3196,8 @@ def do_binaryen(target, asm_target, options, memfile, wasm_binary_target,
|
|||
cmd += ['--table-max=-1']
|
||||
if shared.Settings.SIDE_MODULE:
|
||||
cmd += ['--mem-max=-1']
|
||||
elif not shared.Settings.ALLOW_MEMORY_GROWTH:
|
||||
cmd += ['--mem-max=' + str(shared.Settings.INITIAL_MEMORY)]
|
||||
elif shared.Settings.MAXIMUM_MEMORY >= 0:
|
||||
cmd += ['--mem-max=' + str(shared.Settings.MAXIMUM_MEMORY)]
|
||||
if shared.Settings.LEGALIZE_JS_FFI != 1:
|
||||
|
@ -3269,6 +3288,12 @@ def do_binaryen(target, asm_target, options, memfile, wasm_binary_target,
|
|||
if shared.Settings.USE_PTHREADS and shared.Settings.ALLOW_MEMORY_GROWTH:
|
||||
final = shared.Building.apply_wasm_memory_growth(final)
|
||||
|
||||
# >=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.
|
||||
if shared.Settings.CAN_ADDRESS_2GB:
|
||||
final = shared.Building.use_unsigned_pointers_in_js(final)
|
||||
|
||||
if shared.Settings.OPT_LEVEL >= 2 and shared.Settings.DEBUG_LEVEL <= 2:
|
||||
# minify the JS
|
||||
optimizer.do_minify() # calculate how to minify
|
||||
|
|
|
@ -300,16 +300,14 @@ LibraryManager.library = {
|
|||
switch(name) {
|
||||
case {{{ cDefine('_SC_PAGE_SIZE') }}}: return {{{ POSIX_PAGE_SIZE }}};
|
||||
case {{{ cDefine('_SC_PHYS_PAGES') }}}:
|
||||
#if WASM
|
||||
var maxHeapSize = 2*1024*1024*1024 - 65536;
|
||||
#if ALLOW_MEMORY_GROWTH
|
||||
#if MAXIMUM_MEMORY == -1 // no maximum set, assume the best
|
||||
var maxHeapSize = 4*1024*1024*1024;
|
||||
#else
|
||||
var maxHeapSize = 2*1024*1024*1024 - 16777216;
|
||||
var maxHeapSize = {{{ MAXIMUM_MEMORY }}};
|
||||
#endif
|
||||
#if MAXIMUM_MEMORY != -1
|
||||
maxHeapSize = {{{ MAXIMUM_MEMORY }}};
|
||||
#endif
|
||||
#if !ALLOW_MEMORY_GROWTH
|
||||
maxHeapSize = HEAPU8.length;
|
||||
#else // no growth
|
||||
var maxHeapSize = HEAPU8.length;
|
||||
#endif
|
||||
return maxHeapSize / {{{ POSIX_PAGE_SIZE }}};
|
||||
case {{{ cDefine('_SC_ADVISORY_INFO') }}}:
|
||||
|
|
|
@ -76,15 +76,15 @@ var GLOBAL_BASE = {{{ GLOBAL_BASE }}},
|
|||
#if WASM
|
||||
|
||||
#if ALLOW_MEMORY_GROWTH && MAXIMUM_MEMORY != -1
|
||||
var wasmMaximumMemory = {{{ MAXIMUM_MEMORY }}};
|
||||
var wasmMaximumMemory = {{{ MAXIMUM_MEMORY >>> 16 }}};
|
||||
#else
|
||||
var wasmMaximumMemory = {{{ INITIAL_MEMORY }}};
|
||||
var wasmMaximumMemory = {{{ INITIAL_MEMORY >>> 16}}};
|
||||
#endif
|
||||
|
||||
var wasmMemory = new WebAssembly.Memory({
|
||||
'initial': {{{ INITIAL_MEMORY }}} >> 16
|
||||
'initial': {{{ INITIAL_MEMORY >>> 16 }}}
|
||||
#if USE_PTHREADS || !ALLOW_MEMORY_GROWTH || MAXIMUM_MEMORY != -1
|
||||
, 'maximum': wasmMaximumMemory >> 16
|
||||
, 'maximum': wasmMaximumMemory
|
||||
#endif
|
||||
#if USE_PTHREADS
|
||||
, 'shared': true
|
||||
|
|
|
@ -141,16 +141,29 @@ var FAST_UNROLLED_MEMCPY_AND_MEMSET = 1;
|
|||
// (This option was formerly called TOTAL_MEMORY.)
|
||||
var INITIAL_MEMORY = 16777216;
|
||||
|
||||
// Set the maximum size of memory in the wasm module (in bytes). Without this,
|
||||
// INITIAL_MEMORY is used (as it is used for the initial value), or if memory
|
||||
// growth is enabled, the default value here (-1) is to have no limit, but you
|
||||
// can set this to set a maximum size that growth will stop at.
|
||||
// Set the maximum size of memory in the wasm module (in bytes). This is only
|
||||
// relevant when ALLOW_MEMORY_GROWTH is set, as without growth, the size of
|
||||
// INITIAL_MEMORY is the final size of memory anyhow.
|
||||
//
|
||||
// This setting only matters for wasm, as in asm.js there is no place to set
|
||||
// a maximum, and only when ALLOW_MEMORY_GROWTH is set.
|
||||
// If this value is -1, it means there is no specified limit.
|
||||
//
|
||||
// This setting only matters for wasm and wasm2js, as in asm.js with fastcomp
|
||||
// there is no place to set a maximum.
|
||||
//
|
||||
// Note that the default value here is 2GB, which means that by default if you
|
||||
// enable memory growth then we can grow up to 2GB but no higher. 2GB is a
|
||||
// natural limit for several reasons:
|
||||
//
|
||||
// * If the maximum heap size is over 2GB, then pointers must be unsigned in
|
||||
// JavaScript, which increases code size. We don't want memory growth builds
|
||||
// to be larger unless someone explicitly opts in to >2GB+ heaps.
|
||||
// * Historically no VM has supported more >2GB+, and only recently (Mar 2020)
|
||||
// has support started to appear. As support is limited, it's safer for
|
||||
// people to opt into >2GB+ heaps rather than get a build that may not
|
||||
// work on all VMs.
|
||||
//
|
||||
// (This option was formerly called WASM_MEM_MAX and BINARYEN_MEM_MAX.)
|
||||
var MAXIMUM_MEMORY = -1;
|
||||
var MAXIMUM_MEMORY = 2147483648;
|
||||
|
||||
// If false, we abort with an error if we try to allocate more memory than
|
||||
// we can (INITIAL_MEMORY). If true, we will grow the memory arrays at
|
||||
|
|
|
@ -173,6 +173,10 @@ var WASM_FUNCTIONS_THAT_ARE_NOT_NAME_MANGLED = ['setTempRet0', 'getTempRet0', 's
|
|||
// Internal: value of -flto argument (either full or thin)
|
||||
var LTO = 0;
|
||||
|
||||
// Whether we may be accessing the address 2GB or higher. If so then we need
|
||||
// to be using unsigned pointers in JS.
|
||||
var CAN_ADDRESS_2GB = 0;
|
||||
|
||||
// Whether to emit DWARF in a separate wasm file on the side (this is not called
|
||||
// "split" because there is already a DWARF concept by that name).
|
||||
// When DWARF is on the side, the main file has no DWARF info, while the side
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
HEAP32[x >>> 2];
|
||||
|
||||
HEAP8[x >>> 0];
|
||||
|
||||
HEAP8.length;
|
||||
|
||||
HEAP16.set(foo);
|
||||
|
||||
HEAP16.set(foo, x >>> 0);
|
||||
|
||||
HEAPU32.copyWithin(x >>> 0, y >>> 0);
|
||||
|
||||
HEAPU32.copyWithin(x >>> 0, y >>> 0, z >>> 0);
|
||||
|
||||
HEAPU8.subarray();
|
||||
|
||||
HEAPU8.subarray(x >>> 0);
|
||||
|
||||
HEAPU8.subarray(x >>> 0, y >>> 0);
|
||||
|
||||
process.versions.node;
|
||||
|
||||
insideCall(HEAP32[x >>> 2]);
|
||||
|
||||
heap[x >>> 0];
|
||||
|
||||
HEAP[x >>> 0];
|
||||
|
||||
HeAp[x];
|
|
@ -0,0 +1,29 @@
|
|||
HEAP32[x >> 2];
|
||||
|
||||
HEAP8[x];
|
||||
|
||||
HEAP8.length;
|
||||
|
||||
HEAP16.set(foo);
|
||||
|
||||
HEAP16.set(foo, x);
|
||||
|
||||
HEAPU32.copyWithin(x, y);
|
||||
|
||||
HEAPU32.copyWithin(x, y, z);
|
||||
|
||||
HEAPU8.subarray();
|
||||
|
||||
HEAPU8.subarray(x);
|
||||
|
||||
HEAPU8.subarray(x, y);
|
||||
|
||||
process.versions.node; // something completely different
|
||||
|
||||
insideCall(HEAP32[x >> 2]);
|
||||
|
||||
heap[x];
|
||||
|
||||
HEAP[x];
|
||||
|
||||
HeAp[x]; // but not this
|
|
@ -998,7 +998,7 @@ base align: 0, 0, 0, 0'''])
|
|||
@no_lsan('LSan does not support custom memory allocators')
|
||||
def test_emmalloc_trim(self, *args):
|
||||
self.set_setting('MALLOC', 'emmalloc')
|
||||
self.emcc_args += ['-s', 'INITIAL_MEMORY=128MB', '-s', 'ALLOW_MEMORY_GROWTH=1'] + list(args)
|
||||
self.emcc_args += ['-s', 'INITIAL_MEMORY=128MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'MAXIMUM_MEMORY=2147418112'] + list(args)
|
||||
|
||||
self.do_run_in_out_file_test('tests', 'core', 'test_emmalloc_trim')
|
||||
|
||||
|
@ -5490,7 +5490,7 @@ main( int argv, char ** argc ) {
|
|||
def test_unistd_sysconf_phys_pages(self):
|
||||
src = open(path_from_root('tests', 'unistd', 'sysconf_phys_pages.c')).read()
|
||||
if self.get_setting('ALLOW_MEMORY_GROWTH'):
|
||||
expected = (2 * 1024 * 1024 * 1024 - 16777216) // 16384
|
||||
expected = (2 * 1024 * 1024 * 1024) // 16384
|
||||
else:
|
||||
expected = 16 * 1024 * 1024 // 16384
|
||||
self.do_run(src, str(expected) + ', errno: 0')
|
||||
|
|
|
@ -1812,7 +1812,7 @@ int f() {
|
|||
def test_libjpeg(self):
|
||||
shutil.copyfile(path_from_root('tests', 'screenshot.jpg'), 'screenshot.jpg')
|
||||
Building.emcc(path_from_root('tests', 'jpeg_test.c'), ['--embed-file', 'screenshot.jpg', '-s', 'USE_LIBJPEG=1'], output_filename='a.out.js')
|
||||
self.assertContained('Image is 600 by 450 with 3 components', run_process(JS_ENGINES[0] + ['a.out.js', 'screenshot.jpg'], stdout=PIPE, stderr=PIPE).stdout)
|
||||
self.assertContained('Image is 600 by 450 with 3 components', run_js('a.out.js', args=['screenshot.jpg'], stdout=PIPE, stderr=PIPE))
|
||||
|
||||
def test_bullet(self):
|
||||
Building.emcc(path_from_root('tests', 'bullet_hello_world.cpp'), ['-s', 'USE_BULLET=1'], output_filename='a.out.js')
|
||||
|
@ -2085,7 +2085,7 @@ int f() {
|
|||
assert 'hello, world!' in run_js('two.js')
|
||||
|
||||
def test_js_optimizer(self):
|
||||
ACORN_PASSES = ['JSDCE', 'AJSDCE', 'applyImportAndExportNameChanges', 'emitDCEGraph', 'applyDCEGraphRemovals', 'growableHeap']
|
||||
ACORN_PASSES = ['JSDCE', 'AJSDCE', 'applyImportAndExportNameChanges', 'emitDCEGraph', 'applyDCEGraphRemovals', 'growableHeap', 'unsignPointers']
|
||||
for input, expected, passes in [
|
||||
(path_from_root('tests', 'optimizer', 'eliminateDeadGlobals.js'), open(path_from_root('tests', 'optimizer', 'eliminateDeadGlobals-output.js')).read(),
|
||||
['eliminateDeadGlobals']),
|
||||
|
@ -2187,6 +2187,8 @@ int f() {
|
|||
['asm']),
|
||||
(path_from_root('tests', 'optimizer', 'test-growableHeap.js'), open(path_from_root('tests', 'optimizer', 'test-growableHeap-output.js')).read(),
|
||||
['growableHeap']),
|
||||
(path_from_root('tests', 'optimizer', 'test-unsignPointers.js'), open(path_from_root('tests', 'optimizer', 'test-unsignPointers-output.js')).read(),
|
||||
['unsignPointers']),
|
||||
(path_from_root('tests', 'optimizer', 'test-js-optimizer-minifyGlobals.js'), open(path_from_root('tests', 'optimizer', 'test-js-optimizer-minifyGlobals-output.js')).read(),
|
||||
['minifyGlobals']),
|
||||
]:
|
||||
|
@ -7176,6 +7178,46 @@ Resolved: "/" => "/"
|
|||
run([])
|
||||
run(['-O2'])
|
||||
|
||||
@no_fastcomp("fastcomp doesn't support 2GB+")
|
||||
def test_emmalloc_2GB(self):
|
||||
def test(args, text=None):
|
||||
if text:
|
||||
stderr = self.expect_fail([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'MALLOC=emmalloc'] + args)
|
||||
self.assertContained(text, stderr)
|
||||
else:
|
||||
run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'MALLOC=emmalloc'] + args)
|
||||
|
||||
test(['-s', 'INITIAL_MEMORY=2GB'], 'INITIAL_MEMORY must be less than 2GB due to current spec limitations')
|
||||
# emmalloc allows growth by default (as the max size is fine), but not if
|
||||
# a too-high max is set
|
||||
test(['-s', 'ALLOW_MEMORY_GROWTH'])
|
||||
test(['-s', 'ALLOW_MEMORY_GROWTH', '-s', 'MAXIMUM_MEMORY=1GB'])
|
||||
test(['-s', 'ALLOW_MEMORY_GROWTH', '-s', 'MAXIMUM_MEMORY=3GB'], 'emmalloc only works on <2GB of memory. Use the default allocator, or decrease MAXIMUM_MEMORY')
|
||||
|
||||
@no_fastcomp("fastcomp doesn't support 2GB+")
|
||||
def test_2GB_plus(self):
|
||||
# when the heap size can be over 2GB, we rewrite pointers to be unsigned
|
||||
def test(page_diff):
|
||||
args = [PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-O2', '-s', 'ALLOW_MEMORY_GROWTH']
|
||||
if page_diff is not None:
|
||||
args += ['-s', 'MAXIMUM_MEMORY=%d' % (2**31 + page_diff * 64 * 1024)]
|
||||
print(args)
|
||||
run_process(args)
|
||||
return os.path.getsize('a.out.js')
|
||||
|
||||
less = test(-1)
|
||||
equal = test(0)
|
||||
more = test(1)
|
||||
none = test(None)
|
||||
|
||||
# exactly 2GB still doesn't require unsigned pointers, as we can't address
|
||||
# the 2GB location in memory
|
||||
self.assertEqual(less, equal)
|
||||
self.assertLess(equal, more)
|
||||
# not specifying maximum memory does not result in unsigned pointers, as the
|
||||
# default maximum memory is 2GB.
|
||||
self.assertEqual(less, none)
|
||||
|
||||
def test_sixtyfour_bit_return_value(self):
|
||||
# This test checks that the most significant 32 bits of a 64 bit long are correctly made available
|
||||
# to native JavaScript applications that wish to interact with compiled code returning 64 bit longs.
|
||||
|
@ -8460,11 +8502,8 @@ int main() {
|
|||
|
||||
run([], 1024)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB'], 2048)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1'], (2 * 1024 * 1024 * 1024 - 65536) // 16384)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'WASM=0'], (2 * 1024 * 1024 * 1024 - 16777216) // 16384)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB', '-s', 'BINARYEN=1'], 2048)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'BINARYEN=1'], (2 * 1024 * 1024 * 1024 - 65536) // 16384)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'BINARYEN=1', '-s', 'MAXIMUM_MEMORY=128MB'], 2048 * 4)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1'], (2 * 1024 * 1024 * 1024) // 16384)
|
||||
run(['-s', 'INITIAL_MEMORY=32MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'WASM=0'], (2 * 1024 * 1024 * 1024) // 16384)
|
||||
|
||||
def test_wasm_target_and_STANDALONE_WASM(self):
|
||||
# STANDALONE_WASM means we never minify imports and exports.
|
||||
|
@ -10555,7 +10594,7 @@ int main() {
|
|||
self.assertContained('wasm-ld: error:', stderr)
|
||||
self.assertContained('main_0.o: undefined symbol: foo', stderr)
|
||||
|
||||
@no_fastcomp('lld only')
|
||||
@no_fastcomp('wasm backend only')
|
||||
def test_4GB(self):
|
||||
stderr = self.expect_fail([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'INITIAL_MEMORY=2GB'])
|
||||
self.assertContained('INITIAL_MEMORY must be less than 2GB due to current spec limitations', stderr)
|
||||
|
|
|
@ -88,7 +88,6 @@ function fullWalk(node, c) {
|
|||
// Recursive post-order walk, calling properties on an object by node type,
|
||||
// if the type exists, and if so leaving recursion to that function.
|
||||
function recursiveWalk(node, cs) {
|
||||
//print('recw1 ' + JSON.stringify(node));
|
||||
(function c(node) {
|
||||
if (!(node.type in cs)) {
|
||||
visitChildren(node, function(child) {
|
||||
|
@ -911,6 +910,20 @@ function makeCallExpression(node, name, args) {
|
|||
});
|
||||
}
|
||||
|
||||
function isEmscriptenHEAP(name) {
|
||||
switch (name) {
|
||||
case 'HEAP8': case 'HEAPU8':
|
||||
case 'HEAP16': case 'HEAPU16':
|
||||
case 'HEAP32': case 'HEAPU32':
|
||||
case 'HEAPF32': case 'HEAPF64': {
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -918,19 +931,9 @@ function growableHeap(ast) {
|
|||
recursiveWalk(ast, {
|
||||
AssignmentExpression: function(node) {
|
||||
if (node.left.type === 'Identifier' &&
|
||||
node.left.name.startsWith('HEAP')
|
||||
) {
|
||||
isEmscriptenHEAP(node.left.name)) {
|
||||
// Don't transform initial setup of the arrays.
|
||||
switch (node.left.name) {
|
||||
case 'HEAP8': case 'HEAPU8':
|
||||
case 'HEAP16': case 'HEAPU16':
|
||||
case 'HEAP32': case 'HEAPU32':
|
||||
case 'HEAPF32': case 'HEAPF64': {
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
growableHeap(node.left);
|
||||
growableHeap(node.right);
|
||||
|
@ -988,6 +991,81 @@ function growableHeap(ast) {
|
|||
});
|
||||
}
|
||||
|
||||
// Make all JS pointers unsigned. We do this by modifying things like
|
||||
// HEAP32[X >> 2] to HEAP32[X >>> 2]. We also need to handle the case of
|
||||
// HEAP32[X] and make that HEAP32[X >>> 0], things like subarray(), etc.
|
||||
function unsignPointers(ast) {
|
||||
// Aside from the standard emscripten HEAP*s, also identify just "HEAP"/"heap"
|
||||
// as representing a heap. This can be used in JS library code in order
|
||||
// to get this pass to fix it up.
|
||||
function isHeap(name) {
|
||||
return isEmscriptenHEAP(name) || name === 'heap' || name === 'HEAP';
|
||||
}
|
||||
|
||||
function unsign(node) {
|
||||
// The pointer is often a >> shift, which we can just turn into >>>
|
||||
if (node.type === 'BinaryExpression') {
|
||||
if (node.operator === '>>') {
|
||||
node.operator = '>>>';
|
||||
return node;
|
||||
}
|
||||
}
|
||||
// If nothing else worked out, add a new shift.
|
||||
return {
|
||||
type: 'BinaryExpression',
|
||||
left: node,
|
||||
operator: ">>>",
|
||||
right: {
|
||||
type: "Literal",
|
||||
value: 0,
|
||||
raw: "0",
|
||||
start: 0,
|
||||
end: 0,
|
||||
},
|
||||
start: 0,
|
||||
end: 0,
|
||||
};
|
||||
}
|
||||
|
||||
fullWalk(ast, function(node) {
|
||||
if (node.type === 'MemberExpression') {
|
||||
// Check if this is HEAP*[?]
|
||||
if (node.object.type === 'Identifier' &&
|
||||
isHeap(node.object.name) &&
|
||||
node.computed) {
|
||||
node.property = unsign(node.property);
|
||||
}
|
||||
} else if (node.type === 'CallExpression') {
|
||||
if (node.callee.type === 'MemberExpression' &&
|
||||
node.callee.object.type === 'Identifier' &&
|
||||
isHeap(node.callee.object.name) &&
|
||||
node.callee.property.type === 'Identifier' &&
|
||||
!node.computed) {
|
||||
// This is a call on HEAP*.?. Specific things we need to fix up are
|
||||
// subarray, set, and copyWithin. TODO more?
|
||||
if (node.callee.property.name === 'set') {
|
||||
if (node.arguments.length >= 2) {
|
||||
node.arguments[1] = unsign(node.arguments[1]);
|
||||
}
|
||||
} else if (node.callee.property.name === 'subarray') {
|
||||
if (node.arguments.length >= 1) {
|
||||
node.arguments[0] = unsign(node.arguments[0]);
|
||||
if (node.arguments.length >= 2) {
|
||||
node.arguments[1] = unsign(node.arguments[1]);
|
||||
}
|
||||
}
|
||||
} else if (node.callee.property.name === 'copyWithin') {
|
||||
node.arguments[0] = unsign(node.arguments[0]);
|
||||
node.arguments[1] = unsign(node.arguments[1]);
|
||||
if (node.arguments.length >= 3) {
|
||||
node.arguments[2] = unsign(node.arguments[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function reattachComments(ast, comments) {
|
||||
var symbols = [];
|
||||
|
||||
|
@ -1079,6 +1157,7 @@ var registry = {
|
|||
noPrint: function() { noPrint = true },
|
||||
dump: function() { dump(ast) },
|
||||
growableHeap: growableHeap,
|
||||
unsignPointers: unsignPointers,
|
||||
};
|
||||
|
||||
passes.forEach(function(pass) {
|
||||
|
|
|
@ -1808,10 +1808,10 @@ class Building(object):
|
|||
use_start_function = Settings.STANDALONE_WASM
|
||||
if not use_start_function:
|
||||
cmd += ['--no-entry']
|
||||
if Settings.MAXIMUM_MEMORY != -1:
|
||||
cmd.append('--max-memory=%d' % Settings.MAXIMUM_MEMORY)
|
||||
elif not Settings.ALLOW_MEMORY_GROWTH:
|
||||
if not Settings.ALLOW_MEMORY_GROWTH:
|
||||
cmd.append('--max-memory=%d' % Settings.INITIAL_MEMORY)
|
||||
elif Settings.MAXIMUM_MEMORY != -1:
|
||||
cmd.append('--max-memory=%d' % Settings.MAXIMUM_MEMORY)
|
||||
if not Settings.RELOCATABLE:
|
||||
cmd.append('--global-base=%s' % Settings.GLOBAL_BASE)
|
||||
|
||||
|
@ -2734,6 +2734,11 @@ class Building(object):
|
|||
ret_f.write(support_code_f.read() + '\n' + fixed_f.read())
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def use_unsigned_pointers_in_js(js_file):
|
||||
logger.debug('using unsigned pointers in JS')
|
||||
return Building.acorn_optimizer(js_file, ['unsignPointers'])
|
||||
|
||||
@staticmethod
|
||||
def handle_final_wasm_symbols(wasm_file, symbols_file, debug_info):
|
||||
logger.debug('handle_final_wasm_symbols')
|
||||
|
|
Загрузка…
Ссылка в новой задаче