summaryrefslogtreecommitdiff
path: root/scripts/fuzz_opt.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/fuzz_opt.py')
-rwxr-xr-xscripts/fuzz_opt.py90
1 files changed, 76 insertions, 14 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 9cf65d541..d9477a146 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -192,9 +192,9 @@ def numbers_are_close_enough(x, y):
pass
# otherwise, try a full eval which can handle i64s too
try:
- x = eval(x)
- y = eval(y)
- return x == y or float(x) == float(y)
+ ex = eval(x)
+ ey = eval(y)
+ return ex == ey or float(ex) == float(ey)
except Exception as e:
print('failed to check if numbers are close enough:', e)
return False
@@ -521,9 +521,34 @@ class Wasm2JS(TestCaseHandler):
frequency = 0.6
def handle_pair(self, input, before_wasm, after_wasm, opts):
+ before_wasm_temp = before_wasm + '.temp.wasm'
+ after_wasm_temp = after_wasm + '.temp.wasm'
+ # legalize the before wasm, so that comparisons to the interpreter
+ # later make sense (if we don't do this, the wasm may have i64 exports).
+ # after applying other necessary fixes, we'll recreate the after wasm
+ # from scratch.
+ run([in_bin('wasm-opt'), before_wasm, '--legalize-js-interface', '-o', before_wasm_temp] + FEATURE_OPTS)
+ compare_before_to_after = random.random() < 0.5
+ compare_to_interpreter = compare_before_to_after and random.random() < 0.5
+ if compare_before_to_after:
+ # to compare the wasm before and after optimizations, we must
+ # remove operations that wasm2js does not support with full
+ # precision, such as i64-to-f32, as the optimizer can give different
+ # results.
+ simplification_passes = ['--stub-unsupported-js']
+ if compare_to_interpreter:
+ # unexpectedly-unaligned loads/stores work fine in wasm in general but
+ # not in wasm2js, since typed arrays silently round down, effectively.
+ # if we want to compare to the interpreter, remove unaligned
+ # operations (by forcing alignment 1, then lowering those into aligned
+ # components, which means all loads and stores are of a single byte).
+ simplification_passes += ['--dealign', '--alignment-lowering']
+ run([in_bin('wasm-opt'), before_wasm_temp, '-o', before_wasm_temp] + simplification_passes + FEATURE_OPTS)
+ # now that the before wasm is fixed up, generate a proper after wasm
+ run([in_bin('wasm-opt'), before_wasm_temp, '-o', after_wasm_temp] + opts + FEATURE_OPTS)
# always check for compiler crashes
- before = self.run(before_wasm)
- after = self.run(after_wasm)
+ before = self.run(before_wasm_temp)
+ after = self.run(after_wasm_temp)
if NANS:
# with NaNs we can't compare the output, as a reinterpret through
# memory might end up different in JS than wasm
@@ -532,18 +557,55 @@ class Wasm2JS(TestCaseHandler):
# trap on many things wasm would, and in those cases it can do weird
# undefined things. in such a case, at least compare up until before
# the trap, which lets us compare at least some results in some cases.
- interpreter_output = run([in_bin('wasm-opt'), before_wasm, '--fuzz-exec-before'])
- if TRAP_PREFIX in interpreter_output:
- trap_index = interpreter_output.index(TRAP_PREFIX)
+ # (this is why wasm2js is not in CompareVMs, which does full
+ # comparisons - we need to limit the comparison in a special way here)
+ interpreter = run([in_bin('wasm-opt'), before_wasm_temp, '--fuzz-exec-before'])
+ if TRAP_PREFIX in interpreter:
+ trap_index = interpreter.index(TRAP_PREFIX)
# we can't test this function, which the trap is in the middle of.
# erase everything from this function's output and onward, so we
# only compare the previous trap-free code
- call_start = interpreter_output.rindex(FUZZ_EXEC_CALL_PREFIX, 0, trap_index)
- call_end = interpreter_output.index('\n', call_start)
- call_line = interpreter_output[call_start:call_end]
+ call_start = interpreter.rindex(FUZZ_EXEC_CALL_PREFIX, 0, trap_index)
+ call_end = interpreter.index('\n', call_start)
+ call_line = interpreter[call_start:call_end]
before = before[:before.index(call_line)]
after = after[:after.index(call_line)]
- compare(before, after, 'Wasm2JS')
+ interpreter = interpreter[:interpreter.index(call_line)]
+
+ def fix_output_for_js(x):
+ # start with the normal output fixes that all VMs need
+ x = fix_output(x)
+
+ # check if a number is 0 or a subnormal, which is basically zero
+ def is_basically_zero(x):
+ # to check if something is a subnormal, compare it to the largest one
+ return x >= 0 and x <= 2.22507385850720088902e-308
+
+ def fix_number(x):
+ x = x.group(1)
+ try:
+ x = float(x)
+ # There appear to be some cases where JS VMs will print
+ # subnormals in full detail while other VMs do not, and vice
+ # versa. Ignore such really tiny numbers.
+ if is_basically_zero(x):
+ x = 0
+ except ValueError:
+ # not a floating-point number, nothing to do
+ pass
+ return ' => ' + str(x)
+
+ # logging notation is "function_name => result", look for that with
+ # a floating-point result that may need to be fixed up
+ return re.sub(r' => (-?[\d+-.e\-+]+)', fix_number, x)
+
+ before = fix_output_for_js(before)
+ after = fix_output_for_js(after)
+ if compare_before_to_after:
+ compare_between_vms(before, after, 'Wasm2JS (before/after)')
+ if compare_to_interpreter:
+ interpreter = fix_output_for_js(interpreter)
+ compare_between_vms(before, interpreter, 'Wasm2JS (vs interpreter)')
def run(self, wasm):
wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'] + FEATURE_OPTS)
@@ -568,7 +630,7 @@ class Wasm2JS(TestCaseHandler):
f.write(glue)
f.write(main)
f.write(wrapper)
- return fix_output(run_vm([shared.NODEJS, js_file, 'a.wasm']))
+ return run_vm([shared.NODEJS, js_file, 'a.wasm'])
def can_run_on_feature_opts(self, feature_opts):
return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-threads', '--disable-bulk-memory', '--disable-nontrapping-float-to-int', '--disable-tail-call', '--disable-sign-ext', '--disable-reference-types', '--disable-multivalue']])
@@ -650,7 +712,7 @@ def test_one(random_input, opts, given_wasm):
# apply properties like not having any NaNs, which the original fuzz
# wasm had applied. that is, we need to preserve properties like not
# having nans through reduction.
- run([in_bin('wasm-opt'), given_wasm, '-o', 'a.wasm'] + FUZZ_OPTS)
+ run([in_bin('wasm-opt'), given_wasm, '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS)
else:
# emit the target features section so that reduction can work later,
# without needing to specify the features