Skip to content

Commit

Permalink
Wasm async compilation (#4867)
Browse files Browse the repository at this point in the history
* Compile wasm asynchronously, but only in wasm-only mode

* improve SWAPPABLE_ASM_MODULE docs
  • Loading branch information
kripken committed Jan 26, 2017
1 parent 7b402d2 commit 0ca77c7
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 9 deletions.
14 changes: 14 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,20 @@ def check(input_file):
if shared.Building.is_wasm_only() and shared.Settings.EVAL_CTORS:
logging.debug('disabling EVAL_CTORS, as in wasm-only mode it hurts more than it helps. TODO: a wasm version of it')
shared.Settings.EVAL_CTORS = 0
# enable async compilation if optimizing and not turned off manually
if opt_level > 0:
if 'BINARYEN_ASYNC_COMPILATION=0' not in settings_changes:
shared.Settings.BINARYEN_ASYNC_COMPILATION = 1
if shared.Settings.BINARYEN_ASYNC_COMPILATION == 1:
if shared.Building.is_wasm_only():
# async compilation requires a swappable module - we swap it in when it's ready
shared.Settings.SWAPPABLE_ASM_MODULE = 1
else:
# if not wasm-only, we can't do async compilation as the build can run in other
# modes than wasm (like asm.js) which may not support an async step
shared.Settings.BINARYEN_ASYNC_COMPILATION = 0
if 'BINARYEN_ASYNC_COMPILATION=1' in settings_changes:
logging.warning('BINARYEN_ASYNC_COMPILATION requested, but disabled since not in wasm-only mode')

# wasm outputs are only possible with a side wasm
if target.endswith(WASM_ENDINGS):
Expand Down
23 changes: 18 additions & 5 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -2200,6 +2200,23 @@ function integrateWasmJS(Module) {
};
info['global.Math'] = global.Math;
info['env'] = env;
// handle a generated wasm instance, receiving its exports and
// performing other necessary setup
function receiveInstance(instance) {
exports = instance.exports;
if (exports.memory) mergeMemory(exports.memory);
Module["usingWasm"] = true;
}
#if BINARYEN_ASYNC_COMPILATION
Module['printErr']('asynchronously preparing wasm');
addRunDependency('wasm-instantiate'); // we can't run yet
WebAssembly.instantiate(getBinary(), info).then(function(output) {
receiveInstance(output.instance);
Module['asm'] = exports; // swap in the exports so they can be called
removeRunDependency('wasm-instantiate');
});
return {}; // no exports yet; we'll fill them in later

This comment has been minimized.

Copy link
@cdwang1988

cdwang1988 Feb 23, 2017

Hi kripken,
Returning empty exports value will make the global variable 'asm' empty, then some statement like this will go wrong:

Runtime.stackAlloc = asm['stackAlloc'];
Runtime.stackSave = asm['stackSave'];
Runtime.stackRestore = asm['stackRestore'];
Runtime.establishStackSpace = asm['establishStackSpace'];
I made some changes in the local and my app runs correctly:
     function receiveInstance(instance) {
        exports = instance.exports;
        if (exports.memory) mergeMemory(exports.memory);
        asm = Module['asm'] = exports;
        Module["usingWasm"] = true;

        Runtime.stackAlloc = asm['stackAlloc'];
        Runtime.stackSave = asm['stackSave'];
        Runtime.stackRestore = asm['stackRestore'];
        Runtime.establishStackSpace = asm['establishStackSpace'];
      }

This comment has been minimized.

Copy link
@kripken

kripken Feb 23, 2017

Author Member

Thanks, yeah, this is a bug. The PR to fix it is #4966

#endif
var instance;
try {
instance = new WebAssembly.Instance(new WebAssembly.Module(getBinary()), info)
Expand All @@ -2210,11 +2227,7 @@ function integrateWasmJS(Module) {
}
return false;
}
exports = instance.exports;
if (exports.memory) mergeMemory(exports.memory);
Module["usingWasm"] = true;
receiveInstance(instance);
return exports;
}

Expand Down
12 changes: 8 additions & 4 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -582,10 +582,11 @@ var FINALIZE_ASM_JS = 1; // If 1, will finalize the final emitted code, includin
// that prevent later js optimizer passes from running, like
// converting +5 into 5.0 (the js optimizer sees 5.0 as just 5).

var SWAPPABLE_ASM_MODULE = 0; // If 1, then all exports from the asm.js module will be accessed
// indirectly, which allow the asm module to be swapped later.
// Note: It is very important to build the two modules that
// are to be swapped with the same optimizations and so forth,
var SWAPPABLE_ASM_MODULE = 0; // If 1, then all exports from the asm/wasm module will be accessed
// indirectly, which allow the module to be swapped later,
// simply by replacing Module['asm'].
// Note: It is very important that the replacement module be
// built with the same optimizations and so forth,
// as we depend on them being a drop-in replacement for each
// other (same globals on the heap at the same locations, etc.)

Expand Down Expand Up @@ -687,6 +688,9 @@ var BINARYEN_PASSES = ""; // A comma-separated list of passes to run in the bina
var BINARYEN_MEM_MAX = -1; // Set the maximum size of memory in the wasm module (in bytes).
// Without this, TOTAL_MEMORY is used (as it is used for the initial value),
// or if memory growth is enabled, no limit is set. This overrides both of those.
var BINARYEN_ASYNC_COMPILATION = 0; // Whether to compile the wasm asynchronously, which is more
// efficient. This is off by default in unoptimized builds and
// on by default in optimized ones.
var BINARYEN_ROOT = ""; // Directory where we can find Binaryen. Will be automatically set for you,
// but you can set it to override if you are a Binaryen developer.

Expand Down
13 changes: 13 additions & 0 deletions tests/binaryen_async.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <stdio.h>

#include <emscripten.h>

int main() {
printf("hello, world!\n");
int result = EM_ASM_INT_V({
return Module.sawAsyncCompilation | 0;
});
REPORT_RESULT();
return 0;
}

38 changes: 38 additions & 0 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3281,6 +3281,44 @@ def test_binaryen(self):
self.btest('browser_test_hello_world.c', expected='0', args=['-s', 'BINARYEN=1', '-s', 'BINARYEN_METHOD="interpret-binary"'])
self.btest('browser_test_hello_world.c', expected='0', args=['-s', 'BINARYEN=1', '-s', 'BINARYEN_METHOD="interpret-binary"', '-O2'])

def test_binaryen_native(self):
for opts in [[], ['-O1'], ['-O2'], ['-O3']]:
print opts
self.btest('browser_test_hello_world.c', expected='0', args=['-s', 'BINARYEN=1'] + opts)

def test_binaryen_async(self):
# notice when we use async compilation
open('shell.html', 'w').write(open(path_from_root('src', 'shell.html')).read().replace(
'''{{{ SCRIPT }}}''',
'''
<script>
// note if we do async compilation
var real_wasm_instantiate = WebAssembly.instantiate;
WebAssembly.instantiate = function(a, b) {
Module.sawAsyncCompilation = true;
return real_wasm_instantiate(a, b);
};
// show stderr for the viewer's fun
Module.printErr = function(x) {
Module.print('<<< ' + x + ' >>>');
console.log(x);
};
</script>
{{{ SCRIPT }}}
''',
))
for opts, expect in [
([], 0),
(['-O1'], 1),
(['-O2'], 1),
(['-O3'], 1),
(['-s', 'BINARYEN_ASYNC_COMPILATION=1'], 1), # force it on
(['-O1', '-s', 'BINARYEN_ASYNC_COMPILATION=0'], 0), # force it off
(['-s', 'BINARYEN_ASYNC_COMPILATION=1', '-s', 'BINARYEN_METHOD="native-wasm,asmjs"'], 0), # try to force it on, but have it disabled
]:
print opts, expect
self.btest('binaryen_async.c', expected=str(expect), args=['-s', 'BINARYEN=1', '--shell-file', 'shell.html'] + opts)

def test_utf8_textdecoder(self):
self.btest('benchmark_utf8.cpp', expected='0', args=['--embed-file', path_from_root('tests/utf8_corpus.txt') + '@/utf8_corpus.txt'])

Expand Down

0 comments on commit 0ca77c7

Please sign in to comment.