From e203e4dfeff66650577c6075070008f8811005da Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 25 Apr 2019 12:28:18 -0700 Subject: [PATCH] AUTODEBUG support for wasm files (#8472) The AUTODEBUG option previously only worked on bitcode files. It parsed them as text, adding instrumentation to log out operations for debug purposes, during the link step. But this doesn't work with wasm object files. This PR adds such support, in a different way - it runs the binaryen instrumentation passes. This has been incredibly useful in getting wasm2js working. --- emcc.py | 9 +++++ src/preamble.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++ src/settings.js | 4 +++ tests/test_core.py | 20 ++++++++--- tools/shared.py | 2 +- 5 files changed, 114 insertions(+), 5 deletions(-) diff --git a/emcc.py b/emcc.py index 55b3ee2b1f7f..c241f396a335 100755 --- a/emcc.py +++ b/emcc.py @@ -1050,6 +1050,9 @@ def check(input_file): # in strict mode. Code should use the define __EMSCRIPTEN__ instead. shared.COMPILER_OPTS += ['-DEMSCRIPTEN'] + if AUTODEBUG: + shared.Settings.AUTODEBUG = 1 + # Use settings if options.debug_level > 1 and options.use_closure_compiler: @@ -1442,6 +1445,12 @@ def check(input_file): passes += ['--strip-debug'] if not shared.Settings.EMIT_PRODUCERS_SECTION: passes += ['--strip-producers'] + if shared.Settings.AUTODEBUG and shared.Settings.WASM_OBJECT_FILES: + # adding '--flatten' here may make these even more effective + passes += ['--instrument-locals'] + passes += ['--log-execution'] + passes += ['--instrument-memory'] + passes += ['--legalize-js-interface'] if passes: shared.Settings.BINARYEN_PASSES = ','.join(passes) diff --git a/src/preamble.js b/src/preamble.js index a03f60c9abc2..b3deb13d2126 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -891,6 +891,90 @@ function getBinaryPromise() { // Create the wasm instance. // Receives the wasm imports, returns the exports. function createWasm(env) { +#if AUTODEBUG + env['setTempRet0'] = setTempRet0; + env['getTempRet0'] = getTempRet0; + env['log_execution'] = function(loc) { + console.log('log_execution ' + loc); + }; + env['get_i32'] = function(loc, index, value) { + console.log('get_i32 ' + [loc, index, value]); + return value; + }; + env['get_i64'] = function(loc, index, low, high) { + console.log('get_i64 ' + [loc, index, low, high]); + env['setTempRet0'](high); + return low; + }; + env['get_f32'] = function(loc, index, value) { + console.log('get_f32 ' + [loc, index, value]); + return value; + }; + env['get_f64'] = function(loc, index, value) { + console.log('get_f64 ' + [loc, index, value]); + return value; + }; + env['set_i32'] = function(loc, index, value) { + console.log('set_i32 ' + [loc, index, value]); + return value; + }; + env['set_i64'] = function(loc, index, low, high) { + console.log('set_i64 ' + [loc, index, low, high]); + env['setTempRet0'](high); + return low; + }; + env['set_f32'] = function(loc, index, value) { + console.log('set_f32 ' + [loc, index, value]); + return value; + }; + env['set_f64'] = function(loc, index, value) { + console.log('set_f64 ' + [loc, index, value]); + return value; + }; + env['load_ptr'] = function(loc, bytes, offset, ptr) { + console.log('load_ptr ' + [loc, bytes, offset, ptr]); + return ptr; + }; + env['load_val_i32'] = function(loc, value) { + console.log('load_val_i32 ' + [loc, value]); + return value; + }; + env['load_val_i64'] = function(loc, low, high) { + console.log('load_val_i64 ' + [loc, low, high]); + env['setTempRet0'](high); + return low; + }; + env['load_val_f32'] = function(loc, value) { + console.log('loaload_val_i32d_ptr ' + [loc, value]); + return value; + }; + env['load_val_f64'] = function(loc, value) { + console.log('load_val_f64 ' + [loc, value]); + return value; + }; + env['store_ptr'] = function(loc, bytes, offset, ptr) { + console.log('store_ptr ' + [loc, bytes, offset, ptr]); + return ptr; + }; + env['store_val_i32'] = function(loc, value) { + console.log('store_val_i32 ' + [loc, value]); + return value; + }; + env['store_val_i64'] = function(loc, low, high) { + console.log('store_val_i64 ' + [loc, low, high]); + env['setTempRet0'](high); + return low; + }; + env['store_val_f32'] = function(loc, value) { + console.log('loastore_val_i32d_ptr ' + [loc, value]); + return value; + }; + env['store_val_f64'] = function(loc, value) { + console.log('store_val_f64 ' + [loc, value]); + return value; + }; +#endif + // prepare imports var info = { 'env': env diff --git a/src/settings.js b/src/settings.js index 2b11cdd9a1d4..646fcfc8ca18 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1395,6 +1395,10 @@ var SYSCALLS_REQUIRE_FILESYSTEM = 1; // is received from wasm-emscripten-finalize, which reads it from the features section. var BINARYEN_FEATURES = []; +// Whether EMCC_AUTODEBUG is on, which automatically instruments code for runtime +// logging that can help in debugging. +var AUTODEBUG = 0; + // Legacy settings that have been removed, and the values they are now fixed to. // These can no longer be changed: // [OPTION_NAME, POSSIBLE_VALUES, ERROR_EXPLANATION], where POSSIBLE_VALUES is diff --git a/tests/test_core.py b/tests/test_core.py index 32e557249797..4fd3c87a07b4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5990,10 +5990,10 @@ def run_all(x): run_all('lto') - def test_autodebug(self): - if self.is_wasm_backend(): - # autodebugging only works with bitcode objects - self.set_setting('WASM_OBJECT_FILES', 0) + def test_autodebug_bitcode(self): + if self.is_wasm_backend() and self.get_setting('WASM_OBJECT_FILES') == 1: + return self.skipTest('must use bitcode object files for bitcode autodebug') + Building.COMPILER_TEST_OPTS += ['--llvm-opts', '0'] # Autodebug the code @@ -6036,6 +6036,18 @@ def do_autodebug(filename): ''' self.do_run(src, 'AD:-1,1', build_ll_hook=do_autodebug) + @no_fastcomp('autodebugging wasm is only supported in the wasm backend') + @with_env_modify({'EMCC_AUTODEBUG': '1'}) + def test_autodebug_wasm(self): + # test that the program both works and also emits some of the logging + # (but without the specific numbers, which may change over time) + def check(out, err): + for msg in ['log_execution', 'get_i32', 'set_i32', 'load_ptr', 'load_val', 'store_ptr', 'store_val']: + self.assertIn(msg, out) + return out + err + self.do_run(open(path_from_root('tests', 'core', 'test_hello_world.c')).read(), + 'hello, world!', output_nicerizer=check) + ### Integration tests @sync diff --git a/tools/shared.py b/tools/shared.py index d92c8cb4ba96..b5e6ecdd59de 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -2514,7 +2514,7 @@ def minify_wasm_js(js_file, wasm_file, expensive_optimizations, minify_whitespac # If we are building with DECLARE_ASM_MODULE_EXPORTS=0, we must *not* minify the exports from the wasm module, since in DECLARE_ASM_MODULE_EXPORTS=0 mode, the code that # reads out the exports is compacted by design that it does not have a chance to unminify the functions. If we are building with DECLARE_ASM_MODULE_EXPORTS=1, we might # as well minify wasm exports to regain some of the code size loss that setting DECLARE_ASM_MODULE_EXPORTS=1 caused. - if Settings.EMITTING_JS: + if Settings.EMITTING_JS and not Settings.AUTODEBUG: js_file = Building.minify_wasm_imports_and_exports(js_file, wasm_file, minify_whitespace=minify_whitespace, minify_exports=Settings.DECLARE_ASM_MODULE_EXPORTS, debug_info=debug_info) # finally, optionally use closure compiler to finish cleaning up the JS if use_closure_compiler: