summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py111
-rw-r--r--scripts/fuzz_shell.js115
2 files changed, 189 insertions, 37 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()
]
diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js
index 1e4068dc8..3d29b197c 100644
--- a/scripts/fuzz_shell.js
+++ b/scripts/fuzz_shell.js
@@ -1,25 +1,47 @@
-// Shell integration.
-if (typeof console === 'undefined') {
- console = { log: print };
-}
-var tempRet0;
-var binary;
-if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) {
- var args = process.argv.slice(2);
- binary = require('fs').readFileSync(args[0]);
- if (!binary.buffer) binary = new Uint8Array(binary);
+// Shell integration: find argv and set up readBinary().
+var argv;
+var readBinary;
+if (typeof process === 'object' && typeof require === 'function') {
+ // Node.js.
+ argv = process.argv.slice(2);
+ readBinary = function(name) {
+ var data = require('fs').readFileSync(name);
+ if (!data.buffer) data = new Uint8Array(data);
+ return data;
+ };
} else {
- var args;
+ // A shell like D8.
if (typeof scriptArgs != 'undefined') {
- args = scriptArgs;
+ argv = scriptArgs;
} else if (typeof arguments != 'undefined') {
- args = arguments;
- }
- if (typeof readbuffer === 'function') {
- binary = new Uint8Array(readbuffer(args[0]));
- } else {
- binary = read(args[0], 'binary');
+ argv = arguments;
}
+ readBinary = function(name) {
+ if (typeof readbuffer === 'function') {
+ return new Uint8Array(readbuffer(name));
+ } else {
+ return read(name, 'binary');
+ }
+ };
+}
+
+// We are given the binary to run as a parameter.
+var binary = readBinary(argv[0]);
+
+// Normally we call all the exports of the given wasm file. But, if we are
+// passed a final parameter in the form of "exports:X,Y,Z" then we call
+// specifically the exports X, Y, and Z.
+var exportsToCall;
+if (argv[argv.length - 1].startsWith('exports:')) {
+ exportsToCall = argv[argv.length - 1].substr('exports:'.length).split(',');
+ argv.pop();
+}
+
+// If a second parameter is given, it is a second binary that we will link in
+// with it.
+var secondBinary;
+if (argv[1]) {
+ secondBinary = readBinary(argv[1]);
}
// Utilities.
@@ -27,17 +49,6 @@ function assert(x, y) {
if (!x) throw (y || 'assertion failed');// + new Error().stack;
}
-// Deterministic randomness.
-var detrand = (function() {
- var hash = 5381; // TODO DET_RAND_SEED;
- var x = 0;
- return function() {
- hash = (((hash << 5) + hash) ^ (x & 0xff)) >>> 0;
- x = (x + 1) % 256;
- return (hash % 256) / 256;
- };
-})();
-
// Print out a value in a way that works well for fuzzing.
function printed(x, y) {
if (typeof y !== 'undefined') {
@@ -124,6 +135,7 @@ function logValue(x, y) {
}
// Set up the imports.
+var tempRet0;
var imports = {
'fuzzing-support': {
'log-i32': logValue,
@@ -151,6 +163,24 @@ if (typeof WebAssembly.Tag !== 'undefined') {
};
}
+// If a second binary will be linked in then set up the imports for
+// placeholders. Any import like (import "placeholder" "0" (func .. will be
+// provided by the secondary module, and must be called using an indirection.
+if (secondBinary) {
+ imports['placeholder'] = new Proxy({}, {
+ get(target, prop, receiver) {
+ // Return a function that throws. We could do an indirect call using the
+ // exported table, but as we immediately link in the secondary module,
+ // these stubs will not be called (they are written to the table, and the
+ // secondary module overwrites them). We do need to return something so
+ // the primary module links without erroring, though.
+ return () => {
+ throw 'proxy stub should not be called';
+ }
+ }
+ });
+}
+
// Create the wasm.
var module = new WebAssembly.Module(binary);
@@ -165,17 +195,32 @@ try {
// Handle the exports.
var exports = instance.exports;
-var view;
+// Link in a second module, if one was provided.
+if (secondBinary) {
+ var secondModule = new WebAssembly.Module(secondBinary);
-// Recreate the view. This is important both initially and after a growth.
-function refreshView() {
- if (exports.memory) {
- view = new Int32Array(exports.memory.buffer);
+ // The secondary module just needs to import the primary one: all original
+ // imports it might have needed were exported from there.
+ var secondImports = {'primary': exports};
+ var secondInstance;
+ try {
+ secondInstance = new WebAssembly.Instance(secondModule, secondImports);
+ } catch (e) {
+ console.log('exception thrown: failed to instantiate second module');
+ quit();
}
}
// Run the wasm.
-for (var e in exports) {
+if (!exportsToCall) {
+ // We were not told specific exports, so call them all.
+ exportsToCall = [];
+ for (var e in exports) {
+ exportsToCall.push(e);
+ }
+}
+
+for (var e of exportsToCall) {
if (typeof exports[e] !== 'function') {
continue;
}