summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2020-04-24 15:24:22 -0700
committerGitHub <noreply@github.com>2020-04-24 15:24:22 -0700
commit204cc7b7172d665ee731171d20995edb5897ef7d (patch)
treeca30d6ae5b527c7286ecbcb0e390e7424f0b68fa
parent99708992211d82dd7f157b2dda6dd81e59671378 (diff)
downloadbinaryen-204cc7b7172d665ee731171d20995edb5897ef7d.tar.gz
binaryen-204cc7b7172d665ee731171d20995edb5897ef7d.tar.bz2
binaryen-204cc7b7172d665ee731171d20995edb5897ef7d.zip
Wasm2c2Wasm Fuzzer: wasm2c + emcc (#2791)
This adds a variant on wasm2c that uses emcc instead of a native compiler. This helps us fuzz emcc. To make that practical, rewrite the setjmp glue to only use one setjmp. The wasm backend ends up doing linear work per setjmp, so it's quadratic with many setjmps. Instead, do a big switch-loop construct around a single setjmp.
-rwxr-xr-xscripts/fuzz_opt.py38
-rw-r--r--src/tools/wasm2c-wrapper.h45
2 files changed, 67 insertions, 16 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 16a09eeb6..bc3b9fd5d 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -329,6 +329,9 @@ class CompareVMs(TestCaseHandler):
wabt_bin = shared.which('wasm2c')
wabt_root = os.path.dirname(os.path.dirname(wabt_bin))
self.wasm2c_dir = os.path.join(wabt_root, 'wasm2c')
+ if not os.path.isdir(self.wasm2c_dir):
+ print('wabt found, but not wasm2c support dir')
+ self.wasm2c_dir = None
except Exception as e:
print('warning: no wabt found:', e)
self.wasm2c_dir = None
@@ -358,12 +361,45 @@ class CompareVMs(TestCaseHandler):
# C won't trap on OOB, and NaNs can differ from wasm VMs
return not OOB and not NANS
+ class Wasm2C2Wasm(Wasm2C):
+ name = 'wasm2c2wasm'
+
+ def __init__(self):
+ super(Wasm2C2Wasm, self).__init__()
+
+ self.has_emcc = shared.which('emcc') is not None
+
+ def run(self, wasm):
+ run([in_bin('wasm-opt'), wasm, '--emit-wasm2c-wrapper=main.c'] + FEATURE_OPTS)
+ run(['wasm2c', wasm, '-o', 'wasm.c'])
+ compile_cmd = ['emcc', 'main.c', 'wasm.c', os.path.join(self.wasm2c_dir, 'wasm-rt-impl.c'), '-I' + self.wasm2c_dir, '-lm']
+ if random.random() < 0.5:
+ compile_cmd += ['-O' + str(random.randint(1, 3))]
+ elif random.random() < 0.5:
+ if random.random() < 0.5:
+ compile_cmd += ['-Os']
+ else:
+ compile_cmd += ['-Oz']
+ run(compile_cmd)
+ return run_vm(['d8', 'a.out.js'])
+
+ def can_run(self):
+ return super(Wasm2C2Wasm, self).can_run() and self.has_emcc
+
+ def can_compare_to_self(self):
+ return True
+
+ def can_compare_to_others(self):
+ # NaNs can differ from wasm VMs
+ return not NANS
+
self.vms = [
VM('binaryen interpreter', byn_run, can_run=yes, can_compare_to_self=yes, can_compare_to_others=yes),
# with nans, VM differences can confuse us, so only very simple VMs can compare to themselves after opts in that case.
# if not legalized, the JS will fail immediately, so no point to compare to others
VM('d8', v8_run, can_run=yes, can_compare_to_self=if_no_nans, can_compare_to_others=if_legal_and_no_nans),
- Wasm2C()
+ Wasm2C(),
+ Wasm2C2Wasm(),
]
def handle_pair(self, input, before_wasm, after_wasm, opts):
diff --git a/src/tools/wasm2c-wrapper.h b/src/tools/wasm2c-wrapper.h
index 684bd52f7..aa36dd782 100644
--- a/src/tools/wasm2c-wrapper.h
+++ b/src/tools/wasm2c-wrapper.h
@@ -29,6 +29,7 @@ static std::string generateWasm2CWrapper(Module& wasm) {
// First, emit implementations of the wasm's imports so that the wasm2c code
// can call them. The names use wasm2c's name mangling.
std::string ret = R"(
+#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
@@ -41,7 +42,7 @@ void _Z_fuzzingZ2DsupportZ_logZ2Di32Z_vi(u32 x) {
void (*Z_fuzzingZ2DsupportZ_logZ2Di32Z_vi)(u32) = _Z_fuzzingZ2DsupportZ_logZ2Di32Z_vi;
void _Z_fuzzingZ2DsupportZ_logZ2Di64Z_vj(u64 x) {
- printf("[LoggingExternalInterface logging %ld]\n", (long)x);
+ printf("[LoggingExternalInterface logging %" PRId64 "]\n", (int64_t)x);
}
void (*Z_fuzzingZ2DsupportZ_logZ2Di64Z_vj)(u64) = _Z_fuzzingZ2DsupportZ_logZ2Di64Z_vj;
@@ -79,28 +80,41 @@ u32 (*Z_envZ_getTempRet0Z_iv)(void) = _Z_envZ_getTempRet0Z_iv;
int main(int argc, char** argv) {
init();
+ // We go through each export and call it, in turn. Note that we use a loop
+ // so we can do all this with a single setjmp. A setjmp is needed to handle
+ // wasm traps, and emitting a single one helps compilation speed into wasm as
+ // compile times are O(size * num_setjmps).
+ for (size_t curr = 0;; curr++) {
+ // Always call the hang limit initializer before each export.
+ (*Z_hangLimitInitializerZ_vv)();
+
+ // Prepare to call the export, so we can catch traps.
+ if (setjmp(g_jmp_buf) != 0) {
+ puts("exception!");
+ } else {
+ // Call the proper export.
+ switch(curr) {
)";
// For each export in the wasm, emit code to call it and log its result,
// similar to the other wrappers.
+ size_t exportIndex = 0;
+
for (auto& exp : wasm.exports) {
if (exp->kind != ExternalKind::Function) {
continue;
}
- // Always call the hang limit initializer before each export.
- ret += " (*Z_hangLimitInitializerZ_vv)();\n";
+ ret += " case " + std::to_string(exportIndex++) + ":\n";
auto* func = wasm.getFunction(exp->value);
- // Emit a setjmp so that we can handle traps from the compiled wasm.
- ret +=
- std::string(" puts(\"[fuzz-exec] calling ") + exp->name.str + "\");\n";
- ret += " if (setjmp(g_jmp_buf) == 0) {\n";
+ ret += std::string(" puts(\"[fuzz-exec] calling ") +
+ exp->name.str + "\");\n";
auto result = func->sig.results;
// Emit the call itself.
- ret += " ";
+ ret += " ";
if (result != Type::none) {
ret += std::string("printf(\"[fuzz-exec] note result: ") + exp->name.str +
" => ";
@@ -109,7 +123,7 @@ int main(int argc, char** argv) {
ret += "%d\\n\", ";
break;
case Type::i64:
- ret += "%ld\\n\", (long)";
+ ret += "%\" PRId64 \"\\n\", (int64_t)";
break;
case Type::f32:
ret += "%.17e\\n\", ";
@@ -168,14 +182,15 @@ int main(int argc, char** argv) {
}
ret += ");\n";
- // Handle a longjmp which indicates a trap happened.
- ret += " } else {\n";
- ret += " puts(\"exception!\");\n";
- ret += " }\n";
+ // Break from the big switch.
+ ret += " break;\n";
}
- ret += R"(
-
+ ret += R"( default:
+ return 0; // All done.
+ }
+ }
+ }
return 0;
}
)";