diff options
Diffstat (limited to 'scripts/fuzz_opt.py')
-rwxr-xr-x | scripts/fuzz_opt.py | 111 |
1 files changed, 109 insertions, 2 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 1e29a8422..838ed54cc 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -743,8 +743,8 @@ def run_d8_js(js, args=[], liftoff=True): FUZZ_SHELL_JS = in_binaryen('scripts', 'fuzz_shell.js') -def run_d8_wasm(wasm, liftoff=True): - return run_d8_js(FUZZ_SHELL_JS, [wasm], liftoff=liftoff) +def run_d8_wasm(wasm, liftoff=True, args=[]): + return run_d8_js(FUZZ_SHELL_JS, [wasm] + args, liftoff=liftoff) def all_disallowed(features): @@ -1391,6 +1391,111 @@ class Merge(TestCaseHandler): compare_between_vms(output, merged_output, 'Merge') +FUNC_NAMES_REGEX = re.compile(r'\n [(]func [$](\S+)') + + +# Tests wasm-split +class Split(TestCaseHandler): + frequency = 1 # TODO: adjust lower when we actually enable this + + def handle(self, wasm): + # get the list of function names, some of which we will decide to split + # out + wat = run([in_bin('wasm-dis'), wasm] + FEATURE_OPTS) + all_funcs = re.findall(FUNC_NAMES_REGEX, wat) + + # get the original output before splitting + output = run_d8_wasm(wasm) + output = fix_output(output) + + # find the names of the exports. we need this because when we split the + # module then new exports appear to connect the two halves of the + # original module. we do not want to call all the exports on the new + # primary module, but only the original ones. + exports = [] + for line in output.splitlines(): + if FUZZ_EXEC_CALL_PREFIX in line: + exports.append(get_export_from_call_line(line)) + + # pick which to split out, with a random rate of picking (biased towards + # 0.5). + rate = (random.random() + random.random()) / 2 + split_funcs = [] + for func in all_funcs: + if random.random() < rate: + split_funcs.append(func) + + if not split_funcs: + # nothing to split out + return + + # split the wasm into two + primary = wasm + '.primary.wasm' + secondary = wasm + '.secondary.wasm' + + # we require reference types, because that allows us to create our own + # table. without that we use the existing table, and that may interact + # with user code in odd ways (it really only works with the particular + # form of table+segments that LLVM emits, and not with random fuzzer + # content). + split_feature_opts = FEATURE_OPTS + ['--enable-reference-types'] + + run([in_bin('wasm-split'), wasm, '--split', + '--split-funcs', ','.join(split_funcs), + '--primary-output', primary, + '--secondary-output', secondary] + split_feature_opts) + + # sometimes also optimize the split modules + optimized = False + + def optimize(name): + # do not optimize if it would change the ABI + if CLOSED_WORLD: + return name + # TODO: use other optimizations here, but we'd need to be careful of + # anything that can alter the ABI, and also current + # limitations of open-world optimizations (see discussion in + # https://github.com/WebAssembly/binaryen/pull/6660) + opts = ['-O3'] + new_name = name + '.opt.wasm' + run([in_bin('wasm-opt'), name, '-o', new_name, '-all'] + opts + split_feature_opts) + nonlocal optimized + optimized = True + return new_name + + if random.random() < 0.5: + primary = optimize(primary) + if random.random() < 0.5: + secondary = optimize(secondary) + + # prepare the list of exports to call. the format is + # + # exports:A,B,C + # + exports_to_call = 'exports:' + ','.join(exports) + + # get the output from the split modules, linking them using JS + # TODO run liftoff/turboshaft/etc. + linked_output = run_d8_wasm(primary, args=[secondary, exports_to_call]) + linked_output = fix_output(linked_output) + + # see D8.can_compare_to_self: we cannot compare optimized outputs if + # NaNs are allowed, as the optimizer can modify NaNs differently than + # the JS engine. + if not (NANS and optimized): + compare_between_vms(output, linked_output, 'Split') + + def can_run_on_feature_opts(self, feature_opts): + # to run the split wasm we use JS, that is, JS links the exports of one + # to the imports of the other, etc. since we run in JS, the wasm must be + # valid for JS. + if not LEGALIZE: + return False + + # see D8.can_run + return all_disallowed(['shared-everything']) + + # Check that the text format round-trips without error. class RoundtripText(TestCaseHandler): frequency = 0.05 @@ -1413,6 +1518,8 @@ testcase_handlers = [ TrapsNeverHappen(), CtorEval(), Merge(), + # TODO: enable when stable enough, and adjust |frequency| (see above) + # Split(), RoundtripText() ] |