Skip to content

Commit

Permalink
AUTODEBUG support for wasm files (emscripten-core#8472)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kripken authored and belraquib committed Dec 23, 2020
1 parent 2b7b8c9 commit e203e4d
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 5 deletions.
9 changes: 9 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down
84 changes: 84 additions & 0 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tools/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit e203e4d

Please sign in to comment.