diff options
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/clean_c_api_trace.py | 23 | ||||
-rwxr-xr-x | scripts/embedwast.py | 6 | ||||
-rw-r--r-- | scripts/fuzz_opt.py | 788 | ||||
-rwxr-xr-x | scripts/fuzz_passes.py | 156 | ||||
-rwxr-xr-x | scripts/fuzz_passes_wast.py | 148 | ||||
-rwxr-xr-x | scripts/fuzz_relooper.py | 272 | ||||
-rwxr-xr-x | scripts/gen-s-parser.py | 212 | ||||
-rwxr-xr-x | scripts/process_optimize_instructions.py | 2 | ||||
-rwxr-xr-x | scripts/storage.py | 48 | ||||
-rw-r--r-- | scripts/strip_local_names.py | 13 | ||||
-rwxr-xr-x | scripts/test/asm2wasm.py | 254 | ||||
-rwxr-xr-x | scripts/test/binaryenjs.py | 88 | ||||
-rwxr-xr-x | scripts/test/generate_lld_tests.py | 106 | ||||
-rwxr-xr-x | scripts/test/lld.py | 112 | ||||
-rw-r--r-- | scripts/test/shared.py | 553 | ||||
-rw-r--r-- | scripts/test/support.py | 280 | ||||
-rwxr-xr-x | scripts/test/wasm2js.py | 220 |
17 files changed, 1640 insertions, 1641 deletions
diff --git a/scripts/clean_c_api_trace.py b/scripts/clean_c_api_trace.py index e2bfa30ab..006a0ce4f 100755 --- a/scripts/clean_c_api_trace.py +++ b/scripts/clean_c_api_trace.py @@ -14,9 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -''' -Cleans up output from the C api, makes a runnable C file -''' +"""Cleans up output from the C api, makes a runnable C file +""" import sys @@ -24,14 +23,14 @@ trace = open(sys.argv[1]).read() start = trace.find('// beginning a Binaryen API trace') if start >= 0: - trace = trace[start:] + trace = trace[start:] - while 1: - start = trace.find('\n(') - if start < 0: - break - end = trace.find('\n)', start + 1) - assert end > 0 - trace = trace[:start] + trace[end + 2:] + while 1: + start = trace.find('\n(') + if start < 0: + break + end = trace.find('\n)', start + 1) + assert end > 0 + trace = trace[:start] + trace[end + 2:] - print(trace) + print(trace) diff --git a/scripts/embedwast.py b/scripts/embedwast.py index 6d97b21e9..804be4211 100755 --- a/scripts/embedwast.py +++ b/scripts/embedwast.py @@ -20,7 +20,7 @@ input_file = sys.argv[1] output_file = sys.argv[2] with open(input_file) as f: - wast = f.read() + wast = f.read() output = """\ // Automatically generated by embedwast.py @@ -31,7 +31,7 @@ static const char theModule[%d] = { """ % (len(wast) + 1) for c in wast: - output += str(ord(c)) + ', ' + output += str(ord(c)) + ', ' output += '''0 }; @@ -42,4 +42,4 @@ const char* IntrinsicsModuleWast = theModule; ''' with open(output_file, 'w') as f: - f.write(output) + f.write(output) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 9af65b09d..4591b4150 100644 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -47,49 +47,49 @@ LOG_LIMIT = 125 def in_binaryen(*args): - return os.path.join(options.binaryen_root, *args) + return os.path.join(options.binaryen_root, *args) def in_bin(tool): - return os.path.join(options.binaryen_root, 'bin', tool) + return os.path.join(options.binaryen_root, 'bin', tool) def random_size(): - return random.randint(1, INPUT_SIZE_LIMIT) + return random.randint(1, INPUT_SIZE_LIMIT) def run(cmd): - print(' '.join(cmd)[:LOG_LIMIT]) - return subprocess.check_output(cmd) + print(' '.join(cmd)[:LOG_LIMIT]) + return subprocess.check_output(cmd) def run_unchecked(cmd): - print(' '.join(cmd)[:LOG_LIMIT]) - return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] + print(' '.join(cmd)[:LOG_LIMIT]) + return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] def randomize_pass_debug(): - if random.random() < 0.125: - print('[pass-debug]') - os.environ['BINARYEN_PASS_DEBUG'] = '1' - else: - os.environ['BINARYEN_PASS_DEBUG'] = '0' - del os.environ['BINARYEN_PASS_DEBUG'] + if random.random() < 0.125: + print('[pass-debug]') + os.environ['BINARYEN_PASS_DEBUG'] = '1' + else: + os.environ['BINARYEN_PASS_DEBUG'] = '0' + del os.environ['BINARYEN_PASS_DEBUG'] def randomize_feature_opts(): - global FEATURE_OPTS - FEATURE_OPTS = CONSTANT_FEATURE_OPTS[:] - # half the time apply all the possible opts. this lets all test runners work at max - # capacity at least half the time, as otherwise if they need almost all the opts, the - # chance of getting them is exponentially small. - if random.random() < 0.5: - FEATURE_OPTS += POSSIBLE_FEATURE_OPTS - else: - for possible in POSSIBLE_FEATURE_OPTS: - if random.random() < 0.5: - FEATURE_OPTS.append(possible) - print('feature opts:', ' '.join(FEATURE_OPTS)) + global FEATURE_OPTS + FEATURE_OPTS = CONSTANT_FEATURE_OPTS[:] + # half the time apply all the possible opts. this lets all test runners work at max + # capacity at least half the time, as otherwise if they need almost all the opts, the + # chance of getting them is exponentially small. + if random.random() < 0.5: + FEATURE_OPTS += POSSIBLE_FEATURE_OPTS + else: + for possible in POSSIBLE_FEATURE_OPTS: + if random.random() < 0.5: + FEATURE_OPTS.append(possible) + print('feature opts:', ' '.join(FEATURE_OPTS)) # Test outputs we want to ignore are marked this way. @@ -97,59 +97,59 @@ IGNORE = '[binaryen-fuzzer-ignore]' def compare(x, y, context): - if x != y and x != IGNORE and y != IGNORE: - message = ''.join([a + '\n' for a in difflib.unified_diff(x.splitlines(), y.splitlines(), fromfile='expected', tofile='actual')]) - raise Exception(context + " comparison error, expected to have '%s' == '%s', diff:\n\n%s" % ( - x, y, - message - )) + if x != y and x != IGNORE and y != IGNORE: + message = ''.join([a + '\n' for a in difflib.unified_diff(x.splitlines(), y.splitlines(), fromfile='expected', tofile='actual')]) + raise Exception(context + " comparison error, expected to have '%s' == '%s', diff:\n\n%s" % ( + x, y, + message + )) def fix_output(out): - # large doubles may print slightly different on different VMs - def fix_double(x): - x = x.group(1) - if 'nan' in x or 'NaN' in x: - x = 'nan' - else: - x = x.replace('Infinity', 'inf') - x = str(float(x)) - return 'f64.const ' + x - out = re.sub(r'f64\.const (-?[nanN:abcdefxIity\d+-.]+)', fix_double, out) + # large doubles may print slightly different on different VMs + def fix_double(x): + x = x.group(1) + if 'nan' in x or 'NaN' in x: + x = 'nan' + else: + x = x.replace('Infinity', 'inf') + x = str(float(x)) + return 'f64.const ' + x + out = re.sub(r'f64\.const (-?[nanN:abcdefxIity\d+-.]+)', fix_double, out) - # mark traps from wasm-opt as exceptions, even though they didn't run in a vm - out = out.replace('[trap ', 'exception: [trap ') + # mark traps from wasm-opt as exceptions, even though they didn't run in a vm + out = out.replace('[trap ', 'exception: [trap ') - # exceptions may differ when optimizing, but an exception should occur. so ignore their types - # also js engines print them out slightly differently - return '\n'.join(map(lambda x: ' *exception*' if 'exception' in x else x, out.splitlines())) + # exceptions may differ when optimizing, but an exception should occur. so ignore their types + # also js engines print them out slightly differently + return '\n'.join(map(lambda x: ' *exception*' if 'exception' in x else x, out.splitlines())) def fix_spec_output(out): - out = fix_output(out) - # spec shows a pointer when it traps, remove that - out = '\n'.join(map(lambda x: x if 'runtime trap' not in x else x[x.find('runtime trap'):], out.splitlines())) - # https://github.com/WebAssembly/spec/issues/543 , float consts are messed up - out = '\n'.join(map(lambda x: x if 'f32' not in x and 'f64' not in x else '', out.splitlines())) - return out + out = fix_output(out) + # spec shows a pointer when it traps, remove that + out = '\n'.join(map(lambda x: x if 'runtime trap' not in x else x[x.find('runtime trap'):], out.splitlines())) + # https://github.com/WebAssembly/spec/issues/543 , float consts are messed up + out = '\n'.join(map(lambda x: x if 'f32' not in x and 'f64' not in x else '', out.splitlines())) + return out def run_vm(cmd): - # ignore some vm assertions, if bugs have already been filed - known_issues = [ - 'local count too large', # ignore this; can be caused by flatten, ssa, etc. passes - 'liftoff-assembler.cc, line 239\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8631 - 'liftoff-assembler.cc, line 245\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8631 - 'liftoff-register.h, line 86\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8632 - ] - try: - return run(cmd) - except subprocess.CalledProcessError: - output = run_unchecked(cmd) - for issue in known_issues: - if issue in output: - return IGNORE - raise + # ignore some vm assertions, if bugs have already been filed + known_issues = [ + 'local count too large', # ignore this; can be caused by flatten, ssa, etc. passes + 'liftoff-assembler.cc, line 239\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8631 + 'liftoff-assembler.cc, line 245\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8631 + 'liftoff-register.h, line 86\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8632 + ] + try: + return run(cmd) + except subprocess.CalledProcessError: + output = run_unchecked(cmd) + for issue in known_issues: + if issue in output: + return IGNORE + raise MAX_INTERPRETER_ENV_VAR = 'BINARYEN_MAX_INTERPRETER_DEPTH' @@ -157,101 +157,101 @@ MAX_INTERPRETER_DEPTH = 1000 def run_bynterp(wasm, args): - # increase the interpreter stack depth, to test more things - os.environ[MAX_INTERPRETER_ENV_VAR] = str(MAX_INTERPRETER_DEPTH) - try: - return run_vm([in_bin('wasm-opt'), wasm] + FEATURE_OPTS + args) - finally: - del os.environ['BINARYEN_MAX_INTERPRETER_DEPTH'] + # increase the interpreter stack depth, to test more things + os.environ[MAX_INTERPRETER_ENV_VAR] = str(MAX_INTERPRETER_DEPTH) + try: + return run_vm([in_bin('wasm-opt'), wasm] + FEATURE_OPTS + args) + finally: + del os.environ['BINARYEN_MAX_INTERPRETER_DEPTH'] def run_d8(wasm): - return run_vm(['d8'] + V8_OPTS + [in_binaryen('scripts', 'fuzz_shell.js'), '--', wasm]) + return run_vm(['d8'] + V8_OPTS + [in_binaryen('scripts', 'fuzz_shell.js'), '--', wasm]) # There are two types of test case handlers: -# * get_commands() users: these return a list of commands to run (for example, "run this wasm-opt -# command, then that one"). The calling code gets and runs those commands on the test wasm -# file, and has enough information and control to be able to perform auto-reduction of any -# bugs found. -# * Totally generic: These receive the input pattern, a wasm generated from it, and a wasm -# optimized from that, and can then do anything it wants with those. +# * get_commands() users: these return a list of commands to run (for example, "run this wasm-opt +# command, then that one"). The calling code gets and runs those commands on the test wasm +# file, and has enough information and control to be able to perform auto-reduction of any +# bugs found. +# * Totally generic: These receive the input pattern, a wasm generated from it, and a wasm +# optimized from that, and can then do anything it wants with those. class TestCaseHandler: - # If the core handle_pair() method is not overridden, it calls handle_single() - # on each of the pair. That is useful if you just want the two wasms, and don't - # care about their relationship - def handle_pair(self, input, before_wasm, after_wasm, opts): - self.handle(before_wasm) - self.handle(after_wasm) + # If the core handle_pair() method is not overridden, it calls handle_single() + # on each of the pair. That is useful if you just want the two wasms, and don't + # care about their relationship + def handle_pair(self, input, before_wasm, after_wasm, opts): + self.handle(before_wasm) + self.handle(after_wasm) - def can_run_on_feature_opts(self, feature_opts): - return True + def can_run_on_feature_opts(self, feature_opts): + return True # Run VMs and compare results class CompareVMs(TestCaseHandler): - def handle_pair(self, input, before_wasm, after_wasm, opts): - run([in_bin('wasm-opt'), before_wasm, '--emit-js-wrapper=a.js', '--emit-spec-wrapper=a.wat'] + FEATURE_OPTS) - run([in_bin('wasm-opt'), after_wasm, '--emit-js-wrapper=b.js', '--emit-spec-wrapper=b.wat'] + FEATURE_OPTS) - before = self.run_vms('a.js', before_wasm) - after = self.run_vms('b.js', after_wasm) - self.compare_vs(before, after) - - def run_vms(self, js, wasm): - results = [] - results.append(fix_output(run_bynterp(wasm, ['--fuzz-exec-before']))) - results.append(fix_output(run_vm(['d8', js] + V8_OPTS + ['--', wasm]))) - - # append to add results from VMs - # results += [fix_output(run_vm(['d8', js] + V8_OPTS + ['--', wasm]))] - # results += [fix_output(run_vm([os.path.expanduser('~/.jsvu/jsc'), js, '--', wasm]))] - # spec has no mechanism to not halt on a trap. so we just check until the first trap, basically - # run(['../spec/interpreter/wasm', wasm]) - # results += [fix_spec_output(run_unchecked(['../spec/interpreter/wasm', wasm, '-e', open(prefix + 'wat').read()]))] - - if len(results) == 0: - results = [0] - - # NaNs are a source of nondeterminism between VMs; don't compare them - if not NANS: - first = results[0] - for i in range(len(results)): - compare(first, results[i], 'CompareVMs at ' + str(i)) - - return results - - def compare_vs(self, before, after): - for i in range(len(before)): - compare(before[i], after[i], 'CompareVMs at ' + str(i)) - # with nans, we can only compare the binaryen interpreter to itself - if NANS: - break - - def can_run_on_feature_opts(self, feature_opts): - return all([x in feature_opts for x in ['--disable-simd']]) + def handle_pair(self, input, before_wasm, after_wasm, opts): + run([in_bin('wasm-opt'), before_wasm, '--emit-js-wrapper=a.js', '--emit-spec-wrapper=a.wat'] + FEATURE_OPTS) + run([in_bin('wasm-opt'), after_wasm, '--emit-js-wrapper=b.js', '--emit-spec-wrapper=b.wat'] + FEATURE_OPTS) + before = self.run_vms('a.js', before_wasm) + after = self.run_vms('b.js', after_wasm) + self.compare_vs(before, after) + + def run_vms(self, js, wasm): + results = [] + results.append(fix_output(run_bynterp(wasm, ['--fuzz-exec-before']))) + results.append(fix_output(run_vm(['d8', js] + V8_OPTS + ['--', wasm]))) + + # append to add results from VMs + # results += [fix_output(run_vm(['d8', js] + V8_OPTS + ['--', wasm]))] + # results += [fix_output(run_vm([os.path.expanduser('~/.jsvu/jsc'), js, '--', wasm]))] + # spec has no mechanism to not halt on a trap. so we just check until the first trap, basically + # run(['../spec/interpreter/wasm', wasm]) + # results += [fix_spec_output(run_unchecked(['../spec/interpreter/wasm', wasm, '-e', open(prefix + 'wat').read()]))] + + if len(results) == 0: + results = [0] + + # NaNs are a source of nondeterminism between VMs; don't compare them + if not NANS: + first = results[0] + for i in range(len(results)): + compare(first, results[i], 'CompareVMs at ' + str(i)) + + return results + + def compare_vs(self, before, after): + for i in range(len(before)): + compare(before[i], after[i], 'CompareVMs at ' + str(i)) + # with nans, we can only compare the binaryen interpreter to itself + if NANS: + break + + def can_run_on_feature_opts(self, feature_opts): + return all([x in feature_opts for x in ['--disable-simd']]) # Fuzz the interpreter with --fuzz-exec. This tests everything in a single command (no # two separate binaries) so it's easy to reproduce. class FuzzExec(TestCaseHandler): - def get_commands(self, wasm, opts, random_seed): - return [ - '%(MAX_INTERPRETER_ENV_VAR)s=%(MAX_INTERPRETER_DEPTH)d %(wasm_opt)s --fuzz-exec --fuzz-binary %(opts)s %(wasm)s' % { - 'MAX_INTERPRETER_ENV_VAR': MAX_INTERPRETER_ENV_VAR, - 'MAX_INTERPRETER_DEPTH': MAX_INTERPRETER_DEPTH, - 'wasm_opt': in_bin('wasm-opt'), - 'opts': ' '.join(opts), - 'wasm': wasm - } - ] + def get_commands(self, wasm, opts, random_seed): + return [ + '%(MAX_INTERPRETER_ENV_VAR)s=%(MAX_INTERPRETER_DEPTH)d %(wasm_opt)s --fuzz-exec --fuzz-binary %(opts)s %(wasm)s' % { + 'MAX_INTERPRETER_ENV_VAR': MAX_INTERPRETER_ENV_VAR, + 'MAX_INTERPRETER_DEPTH': MAX_INTERPRETER_DEPTH, + 'wasm_opt': in_bin('wasm-opt'), + 'opts': ' '.join(opts), + 'wasm': wasm + } + ] # As FuzzExec, but without a separate invocation. This can find internal bugs with generating # the IR (which might be worked around by writing it and then reading it). class FuzzExecImmediately(TestCaseHandler): - def handle_pair(self, input, before_wasm, after_wasm, opts): - # fuzz binaryen interpreter itself. separate invocation so result is easily reduceable - run_bynterp(before_wasm, ['--fuzz-exec', '--fuzz-binary'] + opts) + def handle_pair(self, input, before_wasm, after_wasm, opts): + # fuzz binaryen interpreter itself. separate invocation so result is easily reduceable + run_bynterp(before_wasm, ['--fuzz-exec', '--fuzz-binary'] + opts) # Check for determinism - the same command must have the same output. @@ -259,274 +259,274 @@ class FuzzExecImmediately(TestCaseHandler): # for something that autoreduction won't help with anyhow (nondeterminism is very # hard to reduce). class CheckDeterminism(TestCaseHandler): - def handle_pair(self, input, before_wasm, after_wasm, opts): - # check for determinism - run([in_bin('wasm-opt'), before_wasm, '-o', 'b1.wasm'] + opts) - run([in_bin('wasm-opt'), before_wasm, '-o', 'b2.wasm'] + opts) - assert open('b1.wasm').read() == open('b2.wasm').read(), 'output must be deterministic' + def handle_pair(self, input, before_wasm, after_wasm, opts): + # check for determinism + run([in_bin('wasm-opt'), before_wasm, '-o', 'b1.wasm'] + opts) + run([in_bin('wasm-opt'), before_wasm, '-o', 'b2.wasm'] + opts) + assert open('b1.wasm').read() == open('b2.wasm').read(), 'output must be deterministic' class Wasm2JS(TestCaseHandler): - def handle_pair(self, input, before_wasm, after_wasm, opts): - compare(self.run(before_wasm), self.run(after_wasm), 'Wasm2JS') - - def run(self, wasm): - # TODO: wasm2js does not handle nans precisely, and does not - # handle oob loads etc. with traps, should we use - # FUZZ_OPTS += ['--no-fuzz-nans'] - # FUZZ_OPTS += ['--no-fuzz-oob'] - # ? - wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'] + FEATURE_OPTS) - cmd = [in_bin('wasm2js'), wasm, '--emscripten'] - if random.random() < 0.5: - cmd += ['-O'] - main = run(cmd + FEATURE_OPTS) - with open(os.path.join(options.binaryen_root, 'scripts', 'wasm2js.js')) as f: - glue = f.read() - with open('js.js', 'w') as f: - f.write(glue) - f.write(main) - f.write(wrapper) - out = fix_output(run_vm([NODEJS, 'js.js', 'a.wasm'])) - if 'exception' in out: - # exception, so ignoring - wasm2js does not have normal wasm trapping, so opts can eliminate a trap - out = IGNORE - return out - - 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']]) + def handle_pair(self, input, before_wasm, after_wasm, opts): + compare(self.run(before_wasm), self.run(after_wasm), 'Wasm2JS') + + def run(self, wasm): + # TODO: wasm2js does not handle nans precisely, and does not + # handle oob loads etc. with traps, should we use + # FUZZ_OPTS += ['--no-fuzz-nans'] + # FUZZ_OPTS += ['--no-fuzz-oob'] + # ? + wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'] + FEATURE_OPTS) + cmd = [in_bin('wasm2js'), wasm, '--emscripten'] + if random.random() < 0.5: + cmd += ['-O'] + main = run(cmd + FEATURE_OPTS) + with open(os.path.join(options.binaryen_root, 'scripts', 'wasm2js.js')) as f: + glue = f.read() + with open('js.js', 'w') as f: + f.write(glue) + f.write(main) + f.write(wrapper) + out = fix_output(run_vm([NODEJS, 'js.js', 'a.wasm'])) + if 'exception' in out: + # exception, so ignoring - wasm2js does not have normal wasm trapping, so opts can eliminate a trap + out = IGNORE + return out + + 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']]) class Asyncify(TestCaseHandler): - def handle_pair(self, input, before_wasm, after_wasm, opts): - # we must legalize in order to run in JS - run([in_bin('wasm-opt'), before_wasm, '--legalize-js-interface', '-o', before_wasm] + FEATURE_OPTS) - run([in_bin('wasm-opt'), after_wasm, '--legalize-js-interface', '-o', after_wasm] + FEATURE_OPTS) - before = fix_output(run_d8(before_wasm)) - after = fix_output(run_d8(after_wasm)) - - # TODO: also something that actually does async sleeps in the code, say - # on the logging commands? - # --remove-unused-module-elements removes the asyncify intrinsics, which are not valid to call - - def do_asyncify(wasm): - cmd = [in_bin('wasm-opt'), wasm, '--asyncify', '-o', 't.wasm'] - if random.random() < 0.5: - cmd += ['--optimize-level=%d' % random.randint(1, 3)] - if random.random() < 0.5: - cmd += ['--shrink-level=%d' % random.randint(1, 2)] - cmd += FEATURE_OPTS - run(cmd) - out = run_d8('t.wasm') - # emit some status logging from asyncify - print(out.splitlines()[-1]) - # ignore the output from the new asyncify API calls - the ones with asserts will trap, too - for ignore in ['[fuzz-exec] calling $asyncify_start_unwind\nexception!\n', - '[fuzz-exec] calling $asyncify_start_unwind\n', - '[fuzz-exec] calling $asyncify_start_rewind\nexception!\n', - '[fuzz-exec] calling $asyncify_start_rewind\n', - '[fuzz-exec] calling $asyncify_stop_rewind\n', - '[fuzz-exec] calling $asyncify_stop_unwind\n']: - out = out.replace(ignore, '') - out = '\n'.join([l for l in out.splitlines() if 'asyncify: ' not in l]) - return fix_output(out) - - before_asyncify = do_asyncify(before_wasm) - after_asyncify = do_asyncify(after_wasm) - - compare(before, after, 'Asyncify (before/after)') - compare(before, before_asyncify, 'Asyncify (before/before_asyncify)') - compare(before, after_asyncify, 'Asyncify (before/after_asyncify)') - - def can_run_on_feature_opts(self, feature_opts): - return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-tail-call']]) + def handle_pair(self, input, before_wasm, after_wasm, opts): + # we must legalize in order to run in JS + run([in_bin('wasm-opt'), before_wasm, '--legalize-js-interface', '-o', before_wasm] + FEATURE_OPTS) + run([in_bin('wasm-opt'), after_wasm, '--legalize-js-interface', '-o', after_wasm] + FEATURE_OPTS) + before = fix_output(run_d8(before_wasm)) + after = fix_output(run_d8(after_wasm)) + + # TODO: also something that actually does async sleeps in the code, say + # on the logging commands? + # --remove-unused-module-elements removes the asyncify intrinsics, which are not valid to call + + def do_asyncify(wasm): + cmd = [in_bin('wasm-opt'), wasm, '--asyncify', '-o', 't.wasm'] + if random.random() < 0.5: + cmd += ['--optimize-level=%d' % random.randint(1, 3)] + if random.random() < 0.5: + cmd += ['--shrink-level=%d' % random.randint(1, 2)] + cmd += FEATURE_OPTS + run(cmd) + out = run_d8('t.wasm') + # emit some status logging from asyncify + print(out.splitlines()[-1]) + # ignore the output from the new asyncify API calls - the ones with asserts will trap, too + for ignore in ['[fuzz-exec] calling $asyncify_start_unwind\nexception!\n', + '[fuzz-exec] calling $asyncify_start_unwind\n', + '[fuzz-exec] calling $asyncify_start_rewind\nexception!\n', + '[fuzz-exec] calling $asyncify_start_rewind\n', + '[fuzz-exec] calling $asyncify_stop_rewind\n', + '[fuzz-exec] calling $asyncify_stop_unwind\n']: + out = out.replace(ignore, '') + out = '\n'.join([l for l in out.splitlines() if 'asyncify: ' not in l]) + return fix_output(out) + + before_asyncify = do_asyncify(before_wasm) + after_asyncify = do_asyncify(after_wasm) + + compare(before, after, 'Asyncify (before/after)') + compare(before, before_asyncify, 'Asyncify (before/before_asyncify)') + compare(before, after_asyncify, 'Asyncify (before/after_asyncify)') + + def can_run_on_feature_opts(self, feature_opts): + return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-tail-call']]) # The global list of all test case handlers testcase_handlers = [ - FuzzExec(), - CompareVMs(), - CheckDeterminism(), - Wasm2JS(), - Asyncify(), - FuzzExecImmediately(), + FuzzExec(), + CompareVMs(), + CheckDeterminism(), + Wasm2JS(), + Asyncify(), + FuzzExecImmediately(), ] # Do one test, given an input file for -ttf and some optimizations to run def test_one(random_input, opts): - randomize_pass_debug() - randomize_feature_opts() - - run([in_bin('wasm-opt'), random_input, '-ttf', '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS) - wasm_size = os.stat('a.wasm').st_size - bytes = wasm_size - print('pre wasm size:', wasm_size) - - # first, run all handlers that use get_commands(). those don't need the second wasm in the - # pair, since they all they do is return their commands, and expect us to run them, and - # those commands do the actual testing, by operating on the original input wasm file. by - # fuzzing the get_commands() ones first we can find bugs in creating the second wasm (that - # has the opts run on it) before we try to create it later down for the passes that - # expect to get it as one of their inputs. - for testcase_handler in testcase_handlers: - if testcase_handler.can_run_on_feature_opts(FEATURE_OPTS): - if hasattr(testcase_handler, 'get_commands'): - print('running testcase handler:', testcase_handler.__class__.__name__) - # if the testcase handler supports giving us a list of commands, then we can get those commands - # and use them to do useful things like automatic reduction. in this case we give it the input - # wasm plus opts and a random seed (if it needs any internal randomness; we want to have the same - # value there if we reduce). - random_seed = random.random() - - # gets commands from the handler, for a given set of optimizations. this is all the commands - # needed to run the testing that that handler wants to do. - def get_commands(opts): - return testcase_handler.get_commands(wasm='a.wasm', opts=opts + FUZZ_OPTS + FEATURE_OPTS, random_seed=random_seed) - - def write_commands_and_test(opts): - commands = get_commands(opts) - write_commands(commands, 't.sh') - subprocess.check_call(['bash', 't.sh']) - - try: - write_commands_and_test(opts) - except subprocess.CalledProcessError: - print('') - print('====================') - print('Found a problem! See "t.sh" for the commands, and "input.wasm" for the input. Auto-reducing to "reduced.wasm" and "tt.sh"...') - print('====================') - print('') - # first, reduce the fuzz opts: keep removing until we can't - while 1: - reduced = False - for i in range(len(opts)): - # some opts can't be removed, like --flatten --dfo requires flatten - if opts[i] == '--flatten': - if i != len(opts) - 1 and opts[i + 1] in ('--dfo', '--local-cse', '--rereloop'): - continue - shorter = opts[:i] + opts[i + 1:] - try: - write_commands_and_test(shorter) - except subprocess.CalledProcessError: - # great, the shorter one is good as well - opts = shorter - print('reduced opts to ' + ' '.join(opts)) - reduced = True - break - if not reduced: - break - # second, reduce the wasm - # copy a.wasm to a safe place as the reducer will use the commands on new inputs, and the commands work on a.wasm - shutil.copyfile('a.wasm', 'input.wasm') - # add a command to verify the input. this lets the reducer see that it is indeed working on the input correctly - commands = [in_bin('wasm-opt') + ' -all a.wasm'] + get_commands(opts) - write_commands(commands, 'tt.sh') - # reduce the input to something smaller with the same behavior on the script - subprocess.check_call([in_bin('wasm-reduce'), 'input.wasm', '--command=bash tt.sh', '-t', 'a.wasm', '-w', 'reduced.wasm']) - print('Finished reduction. See "tt.sh" and "reduced.wasm".') - sys.exit(1) - print('') - - # created a second wasm for handlers that want to look at pairs. - run([in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts + FUZZ_OPTS + FEATURE_OPTS) - wasm_size = os.stat('b.wasm').st_size - bytes += wasm_size - print('post wasm size:', wasm_size) - - for testcase_handler in testcase_handlers: - if testcase_handler.can_run_on_feature_opts(FEATURE_OPTS): - if not hasattr(testcase_handler, 'get_commands'): - print('running testcase handler:', testcase_handler.__class__.__name__) - # let the testcase handler handle this testcase however it wants. in this case we give it - # the input and both wasms. - testcase_handler.handle_pair(input=random_input, before_wasm='a.wasm', after_wasm='b.wasm', opts=opts + FUZZ_OPTS + FEATURE_OPTS) - print('') - - return bytes + randomize_pass_debug() + randomize_feature_opts() + + run([in_bin('wasm-opt'), random_input, '-ttf', '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS) + wasm_size = os.stat('a.wasm').st_size + bytes = wasm_size + print('pre wasm size:', wasm_size) + + # first, run all handlers that use get_commands(). those don't need the second wasm in the + # pair, since they all they do is return their commands, and expect us to run them, and + # those commands do the actual testing, by operating on the original input wasm file. by + # fuzzing the get_commands() ones first we can find bugs in creating the second wasm (that + # has the opts run on it) before we try to create it later down for the passes that + # expect to get it as one of their inputs. + for testcase_handler in testcase_handlers: + if testcase_handler.can_run_on_feature_opts(FEATURE_OPTS): + if hasattr(testcase_handler, 'get_commands'): + print('running testcase handler:', testcase_handler.__class__.__name__) + # if the testcase handler supports giving us a list of commands, then we can get those commands + # and use them to do useful things like automatic reduction. in this case we give it the input + # wasm plus opts and a random seed (if it needs any internal randomness; we want to have the same + # value there if we reduce). + random_seed = random.random() + + # gets commands from the handler, for a given set of optimizations. this is all the commands + # needed to run the testing that that handler wants to do. + def get_commands(opts): + return testcase_handler.get_commands(wasm='a.wasm', opts=opts + FUZZ_OPTS + FEATURE_OPTS, random_seed=random_seed) + + def write_commands_and_test(opts): + commands = get_commands(opts) + write_commands(commands, 't.sh') + subprocess.check_call(['bash', 't.sh']) + + try: + write_commands_and_test(opts) + except subprocess.CalledProcessError: + print('') + print('====================') + print('Found a problem! See "t.sh" for the commands, and "input.wasm" for the input. Auto-reducing to "reduced.wasm" and "tt.sh"...') + print('====================') + print('') + # first, reduce the fuzz opts: keep removing until we can't + while 1: + reduced = False + for i in range(len(opts)): + # some opts can't be removed, like --flatten --dfo requires flatten + if opts[i] == '--flatten': + if i != len(opts) - 1 and opts[i + 1] in ('--dfo', '--local-cse', '--rereloop'): + continue + shorter = opts[:i] + opts[i + 1:] + try: + write_commands_and_test(shorter) + except subprocess.CalledProcessError: + # great, the shorter one is good as well + opts = shorter + print('reduced opts to ' + ' '.join(opts)) + reduced = True + break + if not reduced: + break + # second, reduce the wasm + # copy a.wasm to a safe place as the reducer will use the commands on new inputs, and the commands work on a.wasm + shutil.copyfile('a.wasm', 'input.wasm') + # add a command to verify the input. this lets the reducer see that it is indeed working on the input correctly + commands = [in_bin('wasm-opt') + ' -all a.wasm'] + get_commands(opts) + write_commands(commands, 'tt.sh') + # reduce the input to something smaller with the same behavior on the script + subprocess.check_call([in_bin('wasm-reduce'), 'input.wasm', '--command=bash tt.sh', '-t', 'a.wasm', '-w', 'reduced.wasm']) + print('Finished reduction. See "tt.sh" and "reduced.wasm".') + sys.exit(1) + print('') + + # created a second wasm for handlers that want to look at pairs. + run([in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts + FUZZ_OPTS + FEATURE_OPTS) + wasm_size = os.stat('b.wasm').st_size + bytes += wasm_size + print('post wasm size:', wasm_size) + + for testcase_handler in testcase_handlers: + if testcase_handler.can_run_on_feature_opts(FEATURE_OPTS): + if not hasattr(testcase_handler, 'get_commands'): + print('running testcase handler:', testcase_handler.__class__.__name__) + # let the testcase handler handle this testcase however it wants. in this case we give it + # the input and both wasms. + testcase_handler.handle_pair(input=random_input, before_wasm='a.wasm', after_wasm='b.wasm', opts=opts + FUZZ_OPTS + FEATURE_OPTS) + print('') + + return bytes def write_commands(commands, filename): - with open(filename, 'w') as f: - f.write('set -e\n') - for command in commands: - f.write('echo "%s"\n' % command) - pre = 'BINARYEN_PASS_DEBUG=%s ' % (os.environ.get('BINARYEN_PASS_DEBUG') or '0') - f.write(pre + command + ' &> /dev/null\n') - f.write('echo "ok"\n') + with open(filename, 'w') as f: + f.write('set -e\n') + for command in commands: + f.write('echo "%s"\n' % command) + pre = 'BINARYEN_PASS_DEBUG=%s ' % (os.environ.get('BINARYEN_PASS_DEBUG') or '0') + f.write(pre + command + ' &> /dev/null\n') + f.write('echo "ok"\n') # main opt_choices = [ - [], - ['-O1'], ['-O2'], ['-O3'], ['-O4'], ['-Os'], ['-Oz'], - ["--coalesce-locals"], - # XXX slow, non-default ["--coalesce-locals-learning"], - ["--code-pushing"], - ["--code-folding"], - ["--const-hoisting"], - ["--dae"], - ["--dae-optimizing"], - ["--dce"], - ["--directize"], - ["--flatten", "--dfo"], - ["--duplicate-function-elimination"], - ["--flatten"], - # ["--fpcast-emu"], # removes indirect call failures as it makes them go through regardless of type - ["--inlining"], - ["--inlining-optimizing"], - ["--flatten", "--local-cse"], - ["--generate-stack-ir"], - ["--licm"], - ["--memory-packing"], - ["--merge-blocks"], - ['--merge-locals'], - ["--optimize-instructions"], - ["--optimize-stack-ir"], - ["--generate-stack-ir", "--optimize-stack-ir"], - ["--pick-load-signs"], - ["--precompute"], - ["--precompute-propagate"], - ["--print"], - ["--remove-unused-brs"], - ["--remove-unused-nonfunction-module-elements"], - ["--remove-unused-module-elements"], - ["--remove-unused-names"], - ["--reorder-functions"], - ["--reorder-locals"], - ["--flatten", "--rereloop"], - ["--rse"], - ["--simplify-locals"], - ["--simplify-locals-nonesting"], - ["--simplify-locals-nostructure"], - ["--simplify-locals-notee"], - ["--simplify-locals-notee-nostructure"], - ["--ssa"], - ["--vacuum"], + [], + ['-O1'], ['-O2'], ['-O3'], ['-O4'], ['-Os'], ['-Oz'], + ["--coalesce-locals"], + # XXX slow, non-default ["--coalesce-locals-learning"], + ["--code-pushing"], + ["--code-folding"], + ["--const-hoisting"], + ["--dae"], + ["--dae-optimizing"], + ["--dce"], + ["--directize"], + ["--flatten", "--dfo"], + ["--duplicate-function-elimination"], + ["--flatten"], + # ["--fpcast-emu"], # removes indirect call failures as it makes them go through regardless of type + ["--inlining"], + ["--inlining-optimizing"], + ["--flatten", "--local-cse"], + ["--generate-stack-ir"], + ["--licm"], + ["--memory-packing"], + ["--merge-blocks"], + ['--merge-locals'], + ["--optimize-instructions"], + ["--optimize-stack-ir"], + ["--generate-stack-ir", "--optimize-stack-ir"], + ["--pick-load-signs"], + ["--precompute"], + ["--precompute-propagate"], + ["--print"], + ["--remove-unused-brs"], + ["--remove-unused-nonfunction-module-elements"], + ["--remove-unused-module-elements"], + ["--remove-unused-names"], + ["--reorder-functions"], + ["--reorder-locals"], + ["--flatten", "--rereloop"], + ["--rse"], + ["--simplify-locals"], + ["--simplify-locals-nonesting"], + ["--simplify-locals-nostructure"], + ["--simplify-locals-notee"], + ["--simplify-locals-notee-nostructure"], + ["--ssa"], + ["--vacuum"], ] def get_multiple_opt_choices(): - ret = [] - # core opts - while 1: - ret += random.choice(opt_choices) - if len(ret) > 20 or random.random() < 0.3: - break - # modifiers (if not already implied by a -O? option) - if '-O' not in str(ret): - if random.random() < 0.5: - ret += ['--optimize-level=' + str(random.randint(0, 3))] - if random.random() < 0.5: - ret += ['--shrink-level=' + str(random.randint(0, 3))] - return ret + ret = [] + # core opts + while 1: + ret += random.choice(opt_choices) + if len(ret) > 20 or random.random() < 0.3: + break + # modifiers (if not already implied by a -O? option) + if '-O' not in str(ret): + if random.random() < 0.5: + ret += ['--optimize-level=' + str(random.randint(0, 3))] + if random.random() < 0.5: + ret += ['--shrink-level=' + str(random.randint(0, 3))] + return ret # main if not NANS: - FUZZ_OPTS += ['--no-fuzz-nans'] + FUZZ_OPTS += ['--no-fuzz-nans'] # possible feature options that are sometimes passed to the tools. this # contains the list of all possible feature flags we can disable (after @@ -535,21 +535,21 @@ POSSIBLE_FEATURE_OPTS = run([in_bin('wasm-opt'), '--print-features', '-all', in_ print('POSSIBLE_FEATURE_OPTS:', POSSIBLE_FEATURE_OPTS) if __name__ == '__main__': - print('checking infinite random inputs') - random.seed(time.time() * os.getpid()) - temp = 'input.dat' - counter = 0 - bytes = 0 # wasm bytes tested - start_time = time.time() - while True: - counter += 1 - f = open(temp, 'w') - size = random_size() - print('') - print('ITERATION:', counter, 'size:', size, 'speed:', counter / (time.time() - start_time), 'iters/sec, ', bytes / (time.time() - start_time), 'bytes/sec\n') - for x in range(size): - f.write(chr(random.randint(0, 255))) - f.close() - opts = get_multiple_opt_choices() - print('opts:', ' '.join(opts)) - bytes += test_one('input.dat', opts) + print('checking infinite random inputs') + random.seed(time.time() * os.getpid()) + temp = 'input.dat' + counter = 0 + bytes = 0 # wasm bytes tested + start_time = time.time() + while True: + counter += 1 + f = open(temp, 'w') + size = random_size() + print('') + print('ITERATION:', counter, 'size:', size, 'speed:', counter / (time.time() - start_time), 'iters/sec, ', bytes / (time.time() - start_time), 'bytes/sec\n') + for x in range(size): + f.write(chr(random.randint(0, 255))) + f.close() + opts = get_multiple_opt_choices() + print('opts:', ' '.join(opts)) + bytes += test_one('input.dat', opts) diff --git a/scripts/fuzz_passes.py b/scripts/fuzz_passes.py index 60ae95817..678e132c3 100755 --- a/scripts/fuzz_passes.py +++ b/scripts/fuzz_passes.py @@ -58,90 +58,90 @@ args = sys.argv[2:] def run(): - if os.path.exists(wast): - print('>>> running using a wast of size', os.stat(wast).st_size) - cmd = ['mozjs', base] + args - try: - return subprocess.check_output(cmd, stderr=subprocess.STDOUT) - except Exception as e: - print(">>> !!! ", e, " !!!") + if os.path.exists(wast): + print('>>> running using a wast of size', os.stat(wast).st_size) + cmd = ['mozjs', base] + args + try: + return subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except Exception as e: + print(">>> !!! ", e, " !!!") original_wast = None try: - # get normal output - - normal = run() - print('>>> normal output:\n', normal) - assert normal, 'must be output' - - # ensure we actually use the wast - - original_wast = wast + '.original.wast' - shutil.move(wast, original_wast) - assert run() != normal, 'running without the wast must fail' - - # ensure a bad pass makes it fail - - def apply_passes(passes): - wasm_opt = os.path.join('bin', 'wasm-opt') - subprocess.check_call([wasm_opt, original_wast] + passes + ['-o', wast]) + # get normal output + + normal = run() + print('>>> normal output:\n', normal) + assert normal, 'must be output' + + # ensure we actually use the wast + + original_wast = wast + '.original.wast' + shutil.move(wast, original_wast) + assert run() != normal, 'running without the wast must fail' + + # ensure a bad pass makes it fail + + def apply_passes(passes): + wasm_opt = os.path.join('bin', 'wasm-opt') + subprocess.check_call([wasm_opt, original_wast] + passes + ['-o', wast]) + + apply_passes(['--remove-imports']) + assert run() != normal, 'running after a breaking pass must fail' + + # loop, looking for failures + + def simplify(passes): + # passes is known to fail, try to simplify down by removing + more = True + while more: + more = False + print('>>> trying to reduce:', ' '.join(passes), ' [' + str(len(passes)) + ']') + for i in range(len(passes)): + smaller = passes[:i] + passes[i + 1:] + print('>>>>>> try to reduce to:', ' '.join(smaller), ' [' + str(len(smaller)) + ']') + try: + apply_passes(smaller) + assert run() == normal + except Exception: + # this failed too, so it's a good reduction + passes = smaller + print('>>> reduction successful') + more = True + break + print('>>> reduced to:', ' '.join(passes)) + + tested = set() + + def pick_passes(): + ret = [] + while 1: + str_ret = str(ret) + if random.random() < 0.1 and str_ret not in tested: + tested.add(str_ret) + return ret + ret.append('--' + random.choice(PASSES)) + + counter = 0 - apply_passes(['--remove-imports']) - assert run() != normal, 'running after a breaking pass must fail' - - # loop, looking for failures - - def simplify(passes): - # passes is known to fail, try to simplify down by removing - more = True - while more: - more = False - print('>>> trying to reduce:', ' '.join(passes), ' [' + str(len(passes)) + ']') - for i in range(len(passes)): - smaller = passes[:i] + passes[i + 1:] - print('>>>>>> try to reduce to:', ' '.join(smaller), ' [' + str(len(smaller)) + ']') - try: - apply_passes(smaller) - assert run() == normal - except Exception: - # this failed too, so it's a good reduction - passes = smaller - print('>>> reduction successful') - more = True - break - print('>>> reduced to:', ' '.join(passes)) - - tested = set() - - def pick_passes(): - ret = [] while 1: - str_ret = str(ret) - if random.random() < 0.1 and str_ret not in tested: - tested.add(str_ret) - return ret - ret.append('--' + random.choice(PASSES)) - - counter = 0 - - while 1: - passes = pick_passes() - print('>>> [' + str(counter) + '] testing:', ' '.join(passes)) - counter += 1 - try: - apply_passes(passes) - except Exception as e: - print(e) - simplify(passes) - break - seen = run() - if seen != normal: - print('>>> bad output:\n', seen) - simplify(passes) - break + passes = pick_passes() + print('>>> [' + str(counter) + '] testing:', ' '.join(passes)) + counter += 1 + try: + apply_passes(passes) + except Exception as e: + print(e) + simplify(passes) + break + seen = run() + if seen != normal: + print('>>> bad output:\n', seen) + simplify(passes) + break finally: - if original_wast: - shutil.move(original_wast, wast) + if original_wast: + shutil.move(original_wast, wast) diff --git a/scripts/fuzz_passes_wast.py b/scripts/fuzz_passes_wast.py index 18506c377..0ef452c7d 100755 --- a/scripts/fuzz_passes_wast.py +++ b/scripts/fuzz_passes_wast.py @@ -54,86 +54,86 @@ args = sys.argv[2:] def run(): - try: - cmd = ['bin/wasm-opt', wast] - print('run', cmd) - subprocess.check_call(cmd, stderr=open('/dev/null')) - except Exception as e: - return ">>> !!! ", e, " !!!" - return 'ok' + try: + cmd = ['bin/wasm-opt', wast] + print('run', cmd) + subprocess.check_call(cmd, stderr=open('/dev/null')) + except Exception as e: + return ">>> !!! ", e, " !!!" + return 'ok' original_wast = None try: - # get normal output - - normal = run() - print('>>> normal output:\n', normal) - assert normal, 'must be output' - - # ensure we actually use the wast - - original_wast = wast + '.original.wast' - shutil.move(wast, original_wast) - - def apply_passes(passes): - wasm_opt = os.path.join('bin', 'wasm-opt') - subprocess.check_call([wasm_opt, original_wast] + passes + ['-o', wast], - stderr=open('/dev/null')) + # get normal output + + normal = run() + print('>>> normal output:\n', normal) + assert normal, 'must be output' + + # ensure we actually use the wast + + original_wast = wast + '.original.wast' + shutil.move(wast, original_wast) + + def apply_passes(passes): + wasm_opt = os.path.join('bin', 'wasm-opt') + subprocess.check_call([wasm_opt, original_wast] + passes + ['-o', wast], + stderr=open('/dev/null')) + + # loop, looking for failures + + def simplify(passes): + # passes is known to fail, try to simplify down by removing + more = True + while more: + more = False + print('>>> trying to reduce:', ' '.join(passes), ' [' + str(len(passes)) + ']') + for i in range(len(passes)): + smaller = passes[:i] + passes[i + 1:] + print('>>>>>> try to reduce to:', ' '.join(smaller), ' [' + str(len(smaller)) + ']') + try: + apply_passes(smaller) + assert run() == normal + except Exception: + # this failed too, so it's a good reduction + passes = smaller + print('>>> reduction successful') + more = True + break + print('>>> reduced to:', ' '.join(passes)) + + tested = set() + + def pick_passes(): + # return '--waka'.split(' ') + ret = [] + while 1: + str_ret = str(ret) + if random.random() < 0.5 and str_ret not in tested: + tested.add(str_ret) + return ret + ret.append('--' + random.choice(PASSES)) + + counter = 0 - # loop, looking for failures - - def simplify(passes): - # passes is known to fail, try to simplify down by removing - more = True - while more: - more = False - print('>>> trying to reduce:', ' '.join(passes), ' [' + str(len(passes)) + ']') - for i in range(len(passes)): - smaller = passes[:i] + passes[i + 1:] - print('>>>>>> try to reduce to:', ' '.join(smaller), ' [' + str(len(smaller)) + ']') - try: - apply_passes(smaller) - assert run() == normal - except Exception: - # this failed too, so it's a good reduction - passes = smaller - print('>>> reduction successful') - more = True - break - print('>>> reduced to:', ' '.join(passes)) - - tested = set() - - def pick_passes(): - # return '--waka'.split(' ') - ret = [] while 1: - str_ret = str(ret) - if random.random() < 0.5 and str_ret not in tested: - tested.add(str_ret) - return ret - ret.append('--' + random.choice(PASSES)) - - counter = 0 - - while 1: - passes = pick_passes() - print('>>> [' + str(counter) + '] testing:', ' '.join(passes)) - counter += 1 - try: - apply_passes(passes) - except Exception as e: - print(e) - simplify(passes) - break - seen = run() - if seen != normal: - print('>>> bad output:\n', seen) - simplify(passes) - break + passes = pick_passes() + print('>>> [' + str(counter) + '] testing:', ' '.join(passes)) + counter += 1 + try: + apply_passes(passes) + except Exception as e: + print(e) + simplify(passes) + break + seen = run() + if seen != normal: + print('>>> bad output:\n', seen) + simplify(passes) + break finally: - if original_wast: - shutil.move(original_wast, wast) + if original_wast: + shutil.move(original_wast, wast) diff --git a/scripts/fuzz_relooper.py b/scripts/fuzz_relooper.py index 359b0fa90..badd939a6 100755 --- a/scripts/fuzz_relooper.py +++ b/scripts/fuzz_relooper.py @@ -6,7 +6,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -31,67 +31,67 @@ seed_init *= seed_init seed_init %= (2**32) if os.environ.get('LD_LIBRARY_PATH'): - os.environ['LD_LIBRARY_PATH'] += os.pathsep + 'lib' + os.environ['LD_LIBRARY_PATH'] += os.pathsep + 'lib' else: - os.environ['LD_LIBRARY_PATH'] = 'lib' + os.environ['LD_LIBRARY_PATH'] = 'lib' counter = 0 while True: - # Random decisions - seed = seed_init - random.seed(seed) - seed_init += 1 - num = random.randint(2, 250) - density = random.random() * random.random() - code_likelihood = random.random() - code_max = random.randint(0, num if random.random() < 0.5 else 3) - max_printed = random.randint(1, num if random.random() < 0.5 else 3) - max_decision = num * 20 - decisions = [random.randint(1, max_decision) for x in range(num * 3)] - branches = [0] * num - defaults = [0] * num - branch_codes = [0] * num # code on the branch, which may alter the global state - - # with some probability print the same id for different blocks, - # as the printing is the block contents - allow merging etc. opts - def printed_id(i): - if random.random() < 0.5: - return i - return i % max_printed - - printed_ids = [printed_id(i) for i in range(num)] - - def random_code(): - if code_max == 0 or random.random() > code_likelihood: - return 0 # no code - # A random number to perturb/increment the global state - return random.randint(1, code_max) - - for i in range(num): - b = set([]) - bs = random.randint(1, max(1, - round(density * random.random() * (num - 1)))) - for j in range(bs): - b.add(random.randint(1, num - 1)) - b = list(b) - defaults[i] = random.choice(b) - b.remove(defaults[i]) - branches[i] = b - branch_codes[i] = [random_code() for item in range(len(b) + 1)] # one for each branch, plus the default - optimize = random.random() < 0.5 - print(counter, ':', num, density, optimize, code_likelihood, code_max, max_printed, ', seed =', seed) - counter += 1 - - for temp in ['fuzz.wasm', 'fuzz.wast', 'fast.txt', 'fuzz.slow.js', - 'fuzz.c']: - try: - os.unlink(temp) - except OSError: - pass - - # parts - entry = ''' + # Random decisions + seed = seed_init + random.seed(seed) + seed_init += 1 + num = random.randint(2, 250) + density = random.random() * random.random() + code_likelihood = random.random() + code_max = random.randint(0, num if random.random() < 0.5 else 3) + max_printed = random.randint(1, num if random.random() < 0.5 else 3) + max_decision = num * 20 + decisions = [random.randint(1, max_decision) for x in range(num * 3)] + branches = [0] * num + defaults = [0] * num + branch_codes = [0] * num # code on the branch, which may alter the global state + + # with some probability print the same id for different blocks, + # as the printing is the block contents - allow merging etc. opts + def printed_id(i): + if random.random() < 0.5: + return i + return i % max_printed + + printed_ids = [printed_id(i) for i in range(num)] + + def random_code(): + if code_max == 0 or random.random() > code_likelihood: + return 0 # no code + # A random number to perturb/increment the global state + return random.randint(1, code_max) + + for i in range(num): + b = set([]) + bs = random.randint(1, max(1, + round(density * random.random() * (num - 1)))) + for j in range(bs): + b.add(random.randint(1, num - 1)) + b = list(b) + defaults[i] = random.choice(b) + b.remove(defaults[i]) + branches[i] = b + branch_codes[i] = [random_code() for item in range(len(b) + 1)] # one for each branch, plus the default + optimize = random.random() < 0.5 + print(counter, ':', num, density, optimize, code_likelihood, code_max, max_printed, ', seed =', seed) + counter += 1 + + for temp in ['fuzz.wasm', 'fuzz.wast', 'fast.txt', 'fuzz.slow.js', + 'fuzz.c']: + try: + os.unlink(temp) + except OSError: + pass + + # parts + entry = ''' var label = 0; var state; var decisions = %s; @@ -103,14 +103,14 @@ function check() { } ''' % str(decisions) - slow = entry + '\n' - slow += 'label = 0;\n' + slow = entry + '\n' + slow += 'label = 0;\n' - slow += ''' + slow += ''' while(1) switch(label) { ''' - fast = ''' + fast = ''' #include <assert.h> #include <stdio.h> @@ -190,27 +190,27 @@ int main() { ''' % len(decisions) - for i in range(num): - slow += ' case %d: console.log("(i32.const %d)"); state = check(); \n' % ( + for i in range(num): + slow += ' case %d: console.log("(i32.const %d)"); state = check(); \n' % ( i, printed_ids[i]) - b = branches[i] - bc = branch_codes[i] + b = branches[i] + bc = branch_codes[i] - def get_phi(j): - phi = '' - if bc[j]: - phi = 'index += %d; ' % bc[j] - return phi + def get_phi(j): + phi = '' + if bc[j]: + phi = 'index += %d; ' % bc[j] + return phi - for j in range(len(b)): - slow += ' if (state %% %d == %d) { %s label = %d; break }\n' % ( - len(b) + 1, j, get_phi(j), b[j]) # TODO: split range 1-n into these options - slow += ' %slabel = %d; break\n' % (get_phi(-1), defaults[i]) + for j in range(len(b)): + slow += ' if (state %% %d == %d) { %s label = %d; break }\n' % ( + len(b) + 1, j, get_phi(j), b[j]) # TODO: split range 1-n into these options + slow += ' %slabel = %d; break\n' % (get_phi(-1), defaults[i]) - use_switch = [random.random() < 0.5 for i in range(num)] + use_switch = [random.random() < 0.5 for i in range(num)] - for i in range(num): - fast += ''' + for i in range(num): + fast += ''' RelooperBlockRef b%d; { BinaryenExpressionRef args[] = { @@ -222,8 +222,8 @@ int main() { BinaryenTypeInt32())) }; ''' % (i, printed_ids[i]) - if use_switch[i]: - fast += ''' + if use_switch[i]: + fast += ''' b%d = RelooperAddBlockWithSwitch(relooper, BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()), BinaryenBinary(module, @@ -233,23 +233,23 @@ int main() { ) ); ''' % (i, len(branches[i]) + 1) - else: # non-switch - fast += ''' + else: # non-switch + fast += ''' b%d = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone())); ''' % i - fast += ''' + fast += ''' } ''' - for i in range(num): - b = branches[i] - bc = branch_codes[i] + for i in range(num): + b = branches[i] + bc = branch_codes[i] - def get_phi(j): - phi = 'NULL' - if bc[j]: - # increment the index of global state - phi = ''' + def get_phi(j): + phi = 'NULL' + if bc[j]: + # increment the index of global state + phi = ''' BinaryenStore(module, 4, 0, 0, BinaryenConst(module, BinaryenLiteralInt32(4)), @@ -261,22 +261,22 @@ int main() { ), BinaryenTypeInt32() )''' % bc[j] - return phi - - for j in range(len(b)): - if use_switch[i]: - total = len(b) + 1 - values = ','.join([str(x) for x in range(random.randint(len(b) + 1, - max_decision + 2)) if x % total == j]) - fast += ''' + return phi + + for j in range(len(b)): + if use_switch[i]: + total = len(b) + 1 + values = ','.join([str(x) for x in range(random.randint(len(b) + 1, + max_decision + 2)) if x % total == j]) + fast += ''' { BinaryenIndex values[] = { %s }; RelooperAddBranchForSwitch(b%d, b%d, values, sizeof(values) / sizeof(BinaryenIndex), %s); } ''' % (values, i, b[j], get_phi(j)) - else: # non-switch - fast += ''' + else: # non-switch + fast += ''' RelooperAddBranch(b%d, b%d, BinaryenBinary(module, BinaryenEqInt32(), BinaryenBinary(module, @@ -287,17 +287,17 @@ int main() { BinaryenConst(module, BinaryenLiteralInt32(%d)) ), %s); ''' % (i, b[j], len(b) + 1, j, get_phi(j)) - # default branch - if use_switch[i]: - fast += ''' + # default branch + if use_switch[i]: + fast += ''' RelooperAddBranchForSwitch(b%d, b%d, NULL, 0, %s); ''' % (i, defaults[i], get_phi(-1)) - else: - fast += ''' + else: + fast += ''' RelooperAddBranch(b%d, b%d, NULL, %s); ''' % (i, defaults[i], get_phi(-1)) - fast += ''' + fast += ''' BinaryenExpressionRef body = RelooperRenderAndDispose(relooper, b0, 1); int decisions[] = { %s }; @@ -357,33 +357,33 @@ int main() { } ''' % (', '.join(map(str, decisions)), optimize) - slow += '}' - - open('fuzz.slow.js', 'w').write(slow) - open('fuzz.c', 'w').write(fast) - - print('.') - cmd = [os.environ.get('CC') or 'gcc', 'fuzz.c', '-Isrc', - '-lbinaryen', '-lasmjs', - '-lsupport', '-Llib/.', '-pthread', '-o', 'fuzz'] - subprocess.check_call(cmd) - print('^') - subprocess.check_call(['./fuzz'], stdout=open('fuzz.wast', 'w')) - print('*') - fast_out = subprocess.Popen(['bin/wasm-shell', 'fuzz.wast'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE).communicate()[0] - print('-') - node = os.getenv('NODE', 'nodejs') - slow_out = subprocess.Popen([node, 'fuzz.slow.js'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE).communicate()[0] - print('_') - - if slow_out != fast_out: - print(''.join([a.rstrip() + '\n' for a in difflib.unified_diff( - slow_out.split('\n'), - fast_out.split('\n'), - fromfile='slow', - tofile='fast')])) - assert False + slow += '}' + + open('fuzz.slow.js', 'w').write(slow) + open('fuzz.c', 'w').write(fast) + + print('.') + cmd = [os.environ.get('CC') or 'gcc', 'fuzz.c', '-Isrc', + '-lbinaryen', '-lasmjs', + '-lsupport', '-Llib/.', '-pthread', '-o', 'fuzz'] + subprocess.check_call(cmd) + print('^') + subprocess.check_call(['./fuzz'], stdout=open('fuzz.wast', 'w')) + print('*') + fast_out = subprocess.Popen(['bin/wasm-shell', 'fuzz.wast'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate()[0] + print('-') + node = os.getenv('NODE', 'nodejs') + slow_out = subprocess.Popen([node, 'fuzz.slow.js'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate()[0] + print('_') + + if slow_out != fast_out: + print(''.join([a.rstrip() + '\n' for a in difflib.unified_diff( + slow_out.split('\n'), + fast_out.split('\n'), + fromfile='slow', + tofile='fast')])) + assert False diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 5b18ed340..d6653e112 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -434,143 +434,143 @@ instructions = [ class CodePrinter: - indents = 0 + indents = 0 - def __enter__(self): - CodePrinter.indents += 1 + def __enter__(self): + CodePrinter.indents += 1 - def __exit__(self, *args): - CodePrinter.indents -= 1 + def __exit__(self, *args): + CodePrinter.indents -= 1 - def indent(self): - # call in a 'with' statement - return self + def indent(self): + # call in a 'with' statement + return self - def print_line(self, line): - print(" " * CodePrinter.indents + line) + def print_line(self, line): + print(" " * CodePrinter.indents + line) class Node: - def __init__(self, expr=None, children=None, inst=None): - # the expression to return if this is the string has ended - self.expr = expr - # map unique strings to children nodes - self.children = children if children else {} - # full instruction leading to this node - self.inst = inst + def __init__(self, expr=None, children=None, inst=None): + # the expression to return if this is the string has ended + self.expr = expr + # map unique strings to children nodes + self.children = children if children else {} + # full instruction leading to this node + self.inst = inst - def _common_prefix(a, b): - """Return the common prefix of two strings.""" - prefix = [] - while a and b and a[0] == b[0]: - prefix.append(a[0]) - a = a[1:] - b = b[1:] - return "".join(prefix) + def _common_prefix(a, b): + """Return the common prefix of two strings.""" + prefix = [] + while a and b and a[0] == b[0]: + prefix.append(a[0]) + a = a[1:] + b = b[1:] + return "".join(prefix) - def do_insert(self, full_inst, inst, expr): - if not inst: - assert self.expr is None, "Repeated instruction " + full_inst - self.expr = expr - self.inst = full_inst - return - # find key with shared prefix - prefix, key = "", None - for k in self.children: - prefix = Node._common_prefix(inst, k) - if prefix: - key = k - break - if key is None: - # unique prefix, insert and stop - self.children[inst] = Node(expr, inst=full_inst) - return - key_remainder = key[len(prefix):] - if key_remainder: - # split key and move everything after the prefix to a new node - child = self.children.pop(key) - self.children[prefix] = Node(children={key_remainder: child}) - # update key for recursive insert - key = prefix - # chop off prefix and recurse - self.children[key].do_insert(full_inst, inst[len(key):], expr) + def do_insert(self, full_inst, inst, expr): + if not inst: + assert self.expr is None, "Repeated instruction " + full_inst + self.expr = expr + self.inst = full_inst + return + # find key with shared prefix + prefix, key = "", None + for k in self.children: + prefix = Node._common_prefix(inst, k) + if prefix: + key = k + break + if key is None: + # unique prefix, insert and stop + self.children[inst] = Node(expr, inst=full_inst) + return + key_remainder = key[len(prefix):] + if key_remainder: + # split key and move everything after the prefix to a new node + child = self.children.pop(key) + self.children[prefix] = Node(children={key_remainder: child}) + # update key for recursive insert + key = prefix + # chop off prefix and recurse + self.children[key].do_insert(full_inst, inst[len(key):], expr) - def insert(self, inst, expr): - self.do_insert(inst, inst, expr) + def insert(self, inst, expr): + self.do_insert(inst, inst, expr) def instruction_parser(): - """Build a trie out of all the instructions, then emit it as C++ code.""" - trie = Node() - inst_length = 0 - for inst, expr in instructions: - inst_length = max(inst_length, len(inst)) - trie.insert(inst, expr) + """Build a trie out of all the instructions, then emit it as C++ code.""" + trie = Node() + inst_length = 0 + for inst, expr in instructions: + inst_length = max(inst_length, len(inst)) + trie.insert(inst, expr) - printer = CodePrinter() + printer = CodePrinter() - printer.print_line("char op[{}] = {{'\\0'}};".format(inst_length + 1)) - printer.print_line("strncpy(op, s[0]->c_str(), {});".format(inst_length)) + printer.print_line("char op[{}] = {{'\\0'}};".format(inst_length + 1)) + printer.print_line("strncpy(op, s[0]->c_str(), {});".format(inst_length)) - def print_leaf(expr, inst): - printer.print_line("if (strcmp(op, \"{inst}\") == 0) {{ return {expr}; }}" - .format(inst=inst, expr=expr)) - printer.print_line("goto parse_error;") + def print_leaf(expr, inst): + printer.print_line("if (strcmp(op, \"{inst}\") == 0) {{ return {expr}; }}" + .format(inst=inst, expr=expr)) + printer.print_line("goto parse_error;") - def emit(node, idx=0): - assert node.children - printer.print_line("switch (op[{}]) {{".format(idx)) - with printer.indent(): - if node.expr: - printer.print_line("case '\\0':") + def emit(node, idx=0): + assert node.children + printer.print_line("switch (op[{}]) {{".format(idx)) with printer.indent(): - print_leaf(node.expr, node.inst) - children = sorted(node.children.items(), key=lambda pair: pair[0]) - for prefix, child in children: - if child.children: - printer.print_line("case '{}': {{".format(prefix[0])) - with printer.indent(): - emit(child, idx + len(prefix)) - printer.print_line("}") - else: - assert child.expr - printer.print_line("case '{}':".format(prefix[0])) - with printer.indent(): - print_leaf(child.expr, child.inst) - printer.print_line("default: goto parse_error;") - printer.print_line("}") + if node.expr: + printer.print_line("case '\\0':") + with printer.indent(): + print_leaf(node.expr, node.inst) + children = sorted(node.children.items(), key=lambda pair: pair[0]) + for prefix, child in children: + if child.children: + printer.print_line("case '{}': {{".format(prefix[0])) + with printer.indent(): + emit(child, idx + len(prefix)) + printer.print_line("}") + else: + assert child.expr + printer.print_line("case '{}':".format(prefix[0])) + with printer.indent(): + print_leaf(child.expr, child.inst) + printer.print_line("default: goto parse_error;") + printer.print_line("}") - emit(trie) - printer.print_line("parse_error:") - with printer.indent(): - printer.print_line("throw ParseException(std::string(op), s.line, s.col);") + emit(trie) + printer.print_line("parse_error:") + with printer.indent(): + printer.print_line("throw ParseException(std::string(op), s.line, s.col);") def print_header(): - print("// DO NOT EDIT! This file generated by scripts/gen-s-parser.py\n") - print("// clang-format off\n") + print("// DO NOT EDIT! This file generated by scripts/gen-s-parser.py\n") + print("// clang-format off\n") def print_footer(): - print("\n// clang-format on") + print("\n// clang-format on") def generate_with_guard(generator, guard): - print("#ifdef {}".format(guard)) - print("#undef {}".format(guard)) - generator() - print("#endif // {}".format(guard)) + print("#ifdef {}".format(guard)) + print("#undef {}".format(guard)) + generator() + print("#endif // {}".format(guard)) def main(): - if sys.version_info.major != 3: - import datetime - print("It's " + str(datetime.datetime.now().year) + "! Use Python 3!") - sys.exit(1) - print_header() - generate_with_guard(instruction_parser, "INSTRUCTION_PARSER") - print_footer() + if sys.version_info.major != 3: + import datetime + print("It's " + str(datetime.datetime.now().year) + "! Use Python 3!") + sys.exit(1) + print_header() + generate_with_guard(instruction_parser, "INSTRUCTION_PARSER") + print_footer() if __name__ == "__main__": - main() + main() diff --git a/scripts/process_optimize_instructions.py b/scripts/process_optimize_instructions.py index a9337b13b..f10e66a8b 100755 --- a/scripts/process_optimize_instructions.py +++ b/scripts/process_optimize_instructions.py @@ -11,6 +11,6 @@ outfile = os.path.join(root, 'src', 'passes', out = open(outfile, 'w') for line in open(infile): - out.write('"' + line.strip().replace('"', '\\"') + '\\n"\n') + out.write('"' + line.strip().replace('"', '\\"') + '\\n"\n') out.close() diff --git a/scripts/storage.py b/scripts/storage.py index 89e5f6107..3db76bf76 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -26,30 +26,30 @@ STORAGE_BASE = 'https://storage.googleapis.com/wasm-llvm/builds/git/' def download_revision(force_latest): - name = 'latest' if force_latest else 'lkgr' - downloaded = urllib2.urlopen(STORAGE_BASE + name).read().strip() - # TODO: for now try opening as JSON, if that doesn't work then the content is - # just a hash. The waterfall is in the process of migrating to JSON. - info = None - try: - info = json.loads(downloaded) - except ValueError: - pass - return info['build'] if type(info) == dict else downloaded + name = 'latest' if force_latest else 'lkgr' + downloaded = urllib2.urlopen(STORAGE_BASE + name).read().strip() + # TODO: for now try opening as JSON, if that doesn't work then the content is + # just a hash. The waterfall is in the process of migrating to JSON. + info = None + try: + info = json.loads(downloaded) + except ValueError: + pass + return info['build'] if type(info) == dict else downloaded def download_tar(tar_pattern, directory, revision): - tar_path = os.path.join(directory, tar_pattern) - revision_tar_path = tar_path % revision - if os.path.isfile(revision_tar_path): - print('Already have `%s`' % revision_tar_path) - else: - print('Downloading `%s`' % revision_tar_path) - with open(revision_tar_path, 'w+') as f: - f.write(urllib2.urlopen(STORAGE_BASE + tar_pattern % revision).read()) - # Remove any previous tarfiles. - for older_tar in glob.glob(tar_path % '*'): - if older_tar != revision_tar_path: - print('Removing older tar file `%s`' % older_tar) - os.remove(older_tar) - return revision_tar_path + tar_path = os.path.join(directory, tar_pattern) + revision_tar_path = tar_path % revision + if os.path.isfile(revision_tar_path): + print('Already have `%s`' % revision_tar_path) + else: + print('Downloading `%s`' % revision_tar_path) + with open(revision_tar_path, 'w+') as f: + f.write(urllib2.urlopen(STORAGE_BASE + tar_pattern % revision).read()) + # Remove any previous tarfiles. + for older_tar in glob.glob(tar_path % '*'): + if older_tar != revision_tar_path: + print('Removing older tar file `%s`' % older_tar) + os.remove(older_tar) + return revision_tar_path diff --git a/scripts/strip_local_names.py b/scripts/strip_local_names.py index bd10d8dab..959e56dd8 100644 --- a/scripts/strip_local_names.py +++ b/scripts/strip_local_names.py @@ -1,13 +1,12 @@ -''' -Removes local names. When you don't care about local names but do want +"""Removes local names. When you don't care about local names but do want to diff for structural changes, this can help. -''' +""" import sys for line in open(sys.argv[1]).readlines(): - if '(local.tee ' in line or '(local.set ' in line or '(local.get ' in line: - print(line[:line.find('$')]) - else: - print(line.rstrip()) + if '(local.tee ' in line or '(local.set ' in line or '(local.get ' in line: + print(line[:line.find('$')]) + else: + print(line.rstrip()) diff --git a/scripts/test/asm2wasm.py b/scripts/test/asm2wasm.py index 356d8c0f2..356a3d85c 100755 --- a/scripts/test/asm2wasm.py +++ b/scripts/test/asm2wasm.py @@ -25,137 +25,137 @@ from .shared import ( def test_asm2wasm(): - print('[ checking asm2wasm testcases... ]\n') - - for asm in tests: - if not asm.endswith('.asm.js'): - continue - for precise in [0, 1, 2]: - for opts in [1, 0]: - cmd = ASM2WASM + [os.path.join(options.binaryen_test, asm)] - if 'threads' in asm: - cmd += ['--enable-threads'] - wasm = asm.replace('.asm.js', '.fromasm') - if not precise: - cmd += ['--trap-mode=allow', '--ignore-implicit-traps'] - wasm += '.imprecise' - elif precise == 2: - cmd += ['--trap-mode=clamp'] - wasm += '.clamp' - if not opts: - wasm += '.no-opts' - if precise: - cmd += ['-O0'] # test that -O0 does nothing - else: - cmd += ['-O'] - if 'debugInfo' in asm: - cmd += ['-g'] - if 'noffi' in asm: - cmd += ['--no-legalize-javascript-ffi'] - if precise and opts: - # test mem init importing - open('a.mem', 'w').write(asm) - cmd += ['--mem-init=a.mem'] - if asm[0] == 'e': - cmd += ['--mem-base=1024'] - if '4GB' in asm: - cmd += ['--mem-max=4294967296'] - if 'i64' in asm or 'wasm-only' in asm or 'noffi' in asm: - cmd += ['--wasm-only'] - wasm = os.path.join(options.binaryen_test, wasm) - print('..', asm, wasm) - - def do_asm2wasm_test(): - actual = run_command(cmd) - - # verify output - if not os.path.exists(wasm): - fail_with_error('output .wast file %s does not exist' % wasm) - fail_if_not_identical_to_file(actual, wasm) - - binary_format_check(wasm, verify_final_result=False) - - # test both normally and with pass debug (so each inter-pass state - # is validated) - old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') - try: - os.environ['BINARYEN_PASS_DEBUG'] = '1' - print("With BINARYEN_PASS_DEBUG=1:") - do_asm2wasm_test() - del os.environ['BINARYEN_PASS_DEBUG'] - print("With BINARYEN_PASS_DEBUG disabled:") - do_asm2wasm_test() - finally: - if old_pass_debug is not None: - os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug - else: - if 'BINARYEN_PASS_DEBUG' in os.environ: - del os.environ['BINARYEN_PASS_DEBUG'] - - # verify in wasm - if options.interpreter: - # remove imports, spec interpreter doesn't know what to do with them - subprocess.check_call(WASM_OPT + ['--remove-imports', wasm], - stdout=open('ztemp.wast', 'w'), - stderr=subprocess.PIPE) - proc = subprocess.Popen([options.interpreter, 'ztemp.wast'], - stderr=subprocess.PIPE) - out, err = proc.communicate() - if proc.returncode != 0: - try: # to parse the error - reported = err.split(':')[1] - start, end = reported.split('-') - start_line, start_col = map(int, start.split('.')) - lines = open('ztemp.wast').read().split('\n') - print() - print('=' * 80) - print(lines[start_line - 1]) - print((' ' * (start_col - 1)) + '^') - print((' ' * (start_col - 2)) + '/_\\') - print('=' * 80) - print(err) - except Exception: - # failed to pretty-print - fail_with_error('wasm interpreter error: ' + err) - fail_with_error('wasm interpreter error') - - # verify debug info - if 'debugInfo' in asm: - jsmap = 'a.wasm.map' - cmd += ['--source-map', jsmap, - '--source-map-url', 'http://example.org/' + jsmap, - '-o', 'a.wasm'] - run_command(cmd) - if not os.path.isfile(jsmap): - fail_with_error('Debug info map not created: %s' % jsmap) - with open(jsmap, 'rb') as actual: - fail_if_not_identical_to_file(actual.read(), wasm + '.map') - with open('a.wasm', 'rb') as binary: - url_section_name = bytes([16]) + bytes('sourceMappingURL', 'utf-8') - url = 'http://example.org/' + jsmap - assert len(url) < 256, 'name too long' - url_section_contents = bytes([len(url)]) + bytes(url, 'utf-8') - print(url_section_name) - binary_contents = binary.read() - if url_section_name not in binary_contents: - fail_with_error('source map url section not found in binary') - url_section_index = binary_contents.index(url_section_name) - if url_section_contents not in binary_contents[url_section_index:]: - fail_with_error('source map url not found in url section') + print('[ checking asm2wasm testcases... ]\n') + + for asm in tests: + if not asm.endswith('.asm.js'): + continue + for precise in [0, 1, 2]: + for opts in [1, 0]: + cmd = ASM2WASM + [os.path.join(options.binaryen_test, asm)] + if 'threads' in asm: + cmd += ['--enable-threads'] + wasm = asm.replace('.asm.js', '.fromasm') + if not precise: + cmd += ['--trap-mode=allow', '--ignore-implicit-traps'] + wasm += '.imprecise' + elif precise == 2: + cmd += ['--trap-mode=clamp'] + wasm += '.clamp' + if not opts: + wasm += '.no-opts' + if precise: + cmd += ['-O0'] # test that -O0 does nothing + else: + cmd += ['-O'] + if 'debugInfo' in asm: + cmd += ['-g'] + if 'noffi' in asm: + cmd += ['--no-legalize-javascript-ffi'] + if precise and opts: + # test mem init importing + open('a.mem', 'w').write(asm) + cmd += ['--mem-init=a.mem'] + if asm[0] == 'e': + cmd += ['--mem-base=1024'] + if '4GB' in asm: + cmd += ['--mem-max=4294967296'] + if 'i64' in asm or 'wasm-only' in asm or 'noffi' in asm: + cmd += ['--wasm-only'] + wasm = os.path.join(options.binaryen_test, wasm) + print('..', asm, wasm) + + def do_asm2wasm_test(): + actual = run_command(cmd) + + # verify output + if not os.path.exists(wasm): + fail_with_error('output .wast file %s does not exist' % wasm) + fail_if_not_identical_to_file(actual, wasm) + + binary_format_check(wasm, verify_final_result=False) + + # test both normally and with pass debug (so each inter-pass state + # is validated) + old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') + try: + os.environ['BINARYEN_PASS_DEBUG'] = '1' + print("With BINARYEN_PASS_DEBUG=1:") + do_asm2wasm_test() + del os.environ['BINARYEN_PASS_DEBUG'] + print("With BINARYEN_PASS_DEBUG disabled:") + do_asm2wasm_test() + finally: + if old_pass_debug is not None: + os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug + else: + if 'BINARYEN_PASS_DEBUG' in os.environ: + del os.environ['BINARYEN_PASS_DEBUG'] + + # verify in wasm + if options.interpreter: + # remove imports, spec interpreter doesn't know what to do with them + subprocess.check_call(WASM_OPT + ['--remove-imports', wasm], + stdout=open('ztemp.wast', 'w'), + stderr=subprocess.PIPE) + proc = subprocess.Popen([options.interpreter, 'ztemp.wast'], + stderr=subprocess.PIPE) + out, err = proc.communicate() + if proc.returncode != 0: + try: # to parse the error + reported = err.split(':')[1] + start, end = reported.split('-') + start_line, start_col = map(int, start.split('.')) + lines = open('ztemp.wast').read().split('\n') + print() + print('=' * 80) + print(lines[start_line - 1]) + print((' ' * (start_col - 1)) + '^') + print((' ' * (start_col - 2)) + '/_\\') + print('=' * 80) + print(err) + except Exception: + # failed to pretty-print + fail_with_error('wasm interpreter error: ' + err) + fail_with_error('wasm interpreter error') + + # verify debug info + if 'debugInfo' in asm: + jsmap = 'a.wasm.map' + cmd += ['--source-map', jsmap, + '--source-map-url', 'http://example.org/' + jsmap, + '-o', 'a.wasm'] + run_command(cmd) + if not os.path.isfile(jsmap): + fail_with_error('Debug info map not created: %s' % jsmap) + with open(jsmap, 'rb') as actual: + fail_if_not_identical_to_file(actual.read(), wasm + '.map') + with open('a.wasm', 'rb') as binary: + url_section_name = bytes([16]) + bytes('sourceMappingURL', 'utf-8') + url = 'http://example.org/' + jsmap + assert len(url) < 256, 'name too long' + url_section_contents = bytes([len(url)]) + bytes(url, 'utf-8') + print(url_section_name) + binary_contents = binary.read() + if url_section_name not in binary_contents: + fail_with_error('source map url section not found in binary') + url_section_index = binary_contents.index(url_section_name) + if url_section_contents not in binary_contents[url_section_index:]: + fail_with_error('source map url not found in url section') def test_asm2wasm_binary(): - print('\n[ checking asm2wasm binary reading/writing... ]\n') + print('\n[ checking asm2wasm binary reading/writing... ]\n') - asmjs = os.path.join(options.binaryen_test, 'hello_world.asm.js') - delete_from_orbit('a.wasm') - delete_from_orbit('b.wast') - run_command(ASM2WASM + [asmjs, '-o', 'a.wasm']) - assert open('a.wasm', 'rb').read()[0] == 0, 'we emit binary by default' - run_command(ASM2WASM + [asmjs, '-o', 'b.wast', '-S']) - assert open('b.wast', 'rb').read()[0] != 0, 'we emit text with -S' + asmjs = os.path.join(options.binaryen_test, 'hello_world.asm.js') + delete_from_orbit('a.wasm') + delete_from_orbit('b.wast') + run_command(ASM2WASM + [asmjs, '-o', 'a.wasm']) + assert open('a.wasm', 'rb').read()[0] == 0, 'we emit binary by default' + run_command(ASM2WASM + [asmjs, '-o', 'b.wast', '-S']) + assert open('b.wast', 'rb').read()[0] != 0, 'we emit text with -S' if __name__ == '__main__': - test_asm2wasm() - test_asm2wasm_binary() + test_asm2wasm() + test_asm2wasm_binary() diff --git a/scripts/test/binaryenjs.py b/scripts/test/binaryenjs.py index f701f25c1..a102c2ae4 100755 --- a/scripts/test/binaryenjs.py +++ b/scripts/test/binaryenjs.py @@ -22,56 +22,56 @@ from .shared import BINARYEN_JS, MOZJS, NODEJS, options, fail def test_binaryen_js(): - if not (MOZJS or NODEJS): - print('no vm to run binaryen.js tests') - return + if not (MOZJS or NODEJS): + print('no vm to run binaryen.js tests') + return - node_has_wasm = NODEJS and node_has_webassembly(NODEJS) + node_has_wasm = NODEJS and node_has_webassembly(NODEJS) - if not os.path.exists(BINARYEN_JS): - print('no binaryen.js build to test') - return + if not os.path.exists(BINARYEN_JS): + print('no binaryen.js build to test') + return - print('\n[ checking binaryen.js testcases... ]\n') + print('\n[ checking binaryen.js testcases... ]\n') - for s in sorted(os.listdir(os.path.join(options.binaryen_test, 'binaryen.js'))): - if not s.endswith('.js'): - continue - print(s) - f = open('a.js', 'w') - # avoid stdout/stderr ordering issues in some js shells - use just stdout - f.write(''' - console.warn = function(x) { console.log(x) }; - ''') - binaryen_js = open(BINARYEN_JS).read() - f.write(binaryen_js) - if NODEJS: - f.write(node_test_glue()) - test_path = os.path.join(options.binaryen_test, 'binaryen.js', s) - test_src = open(test_path).read() - f.write(test_src) - f.close() + for s in sorted(os.listdir(os.path.join(options.binaryen_test, 'binaryen.js'))): + if not s.endswith('.js'): + continue + print(s) + f = open('a.js', 'w') + # avoid stdout/stderr ordering issues in some js shells - use just stdout + f.write(''' + console.warn = function(x) { console.log(x) }; + ''') + binaryen_js = open(BINARYEN_JS).read() + f.write(binaryen_js) + if NODEJS: + f.write(node_test_glue()) + test_path = os.path.join(options.binaryen_test, 'binaryen.js', s) + test_src = open(test_path).read() + f.write(test_src) + f.close() - def test(engine): - cmd = [engine, 'a.js'] - if 'fatal' not in s: - out = run_command(cmd, stderr=subprocess.STDOUT) - else: - # expect an error - the specific error code will depend on the vm - out = run_command(cmd, stderr=subprocess.STDOUT, expected_status=None) - expected = open(os.path.join(options.binaryen_test, 'binaryen.js', s + '.txt')).read() - if expected not in out: - fail(out, expected) + def test(engine): + cmd = [engine, 'a.js'] + if 'fatal' not in s: + out = run_command(cmd, stderr=subprocess.STDOUT) + else: + # expect an error - the specific error code will depend on the vm + out = run_command(cmd, stderr=subprocess.STDOUT, expected_status=None) + expected = open(os.path.join(options.binaryen_test, 'binaryen.js', s + '.txt')).read() + if expected not in out: + fail(out, expected) - # run in all possible shells - if MOZJS: - test(MOZJS) - if NODEJS: - if node_has_wasm or 'WebAssembly.' not in test_src: - test(NODEJS) - else: - print('Skipping ' + test_path + ' because WebAssembly might not be supported') + # run in all possible shells + if MOZJS: + test(MOZJS) + if NODEJS: + if node_has_wasm or 'WebAssembly.' not in test_src: + test(NODEJS) + else: + print('Skipping ' + test_path + ' because WebAssembly might not be supported') if __name__ == "__main__": - test_binaryen_js() + test_binaryen_js() diff --git a/scripts/test/generate_lld_tests.py b/scripts/test/generate_lld_tests.py index 9447fe973..b5cb6b140 100755 --- a/scripts/test/generate_lld_tests.py +++ b/scripts/test/generate_lld_tests.py @@ -23,69 +23,69 @@ import shared def files_with_extensions(path, extensions): - for file in sorted(os.listdir(path)): - ext = os.path.splitext(file)[1] - if ext in extensions: - yield file, ext + for file in sorted(os.listdir(path)): + ext = os.path.splitext(file)[1] + if ext in extensions: + yield file, ext def generate_wast_files(llvm_bin, emscripten_root): - print('\n[ building wast files from C sources... ]\n') + print('\n[ building wast files from C sources... ]\n') - lld_path = os.path.join(shared.options.binaryen_test, 'lld') - for src_file, ext in files_with_extensions(lld_path, ['.c', '.cpp']): - print('..', src_file) - obj_file = src_file.replace(ext, '.o') + lld_path = os.path.join(shared.options.binaryen_test, 'lld') + for src_file, ext in files_with_extensions(lld_path, ['.c', '.cpp']): + print('..', src_file) + obj_file = src_file.replace(ext, '.o') - src_path = os.path.join(lld_path, src_file) - obj_path = os.path.join(lld_path, obj_file) + src_path = os.path.join(lld_path, src_file) + obj_path = os.path.join(lld_path, obj_file) - wasm_file = src_file.replace(ext, '.wasm') - wast_file = src_file.replace(ext, '.wast') + wasm_file = src_file.replace(ext, '.wasm') + wast_file = src_file.replace(ext, '.wast') - obj_path = os.path.join(lld_path, obj_file) - wasm_path = os.path.join(lld_path, wasm_file) - wast_path = os.path.join(lld_path, wast_file) - is_shared = 'shared' in src_file + obj_path = os.path.join(lld_path, obj_file) + wasm_path = os.path.join(lld_path, wasm_file) + wast_path = os.path.join(lld_path, wast_file) + is_shared = 'shared' in src_file - compile_cmd = [ - os.path.join(llvm_bin, 'clang'), src_path, '-o', obj_path, - '--target=wasm32-unknown-unknown-wasm', - '-c', - '-nostdinc', - '-Xclang', '-nobuiltininc', - '-Xclang', '-nostdsysteminc', - '-Xclang', '-I%s/system/include' % emscripten_root, - '-O1', - ] + compile_cmd = [ + os.path.join(llvm_bin, 'clang'), src_path, '-o', obj_path, + '--target=wasm32-unknown-unknown-wasm', + '-c', + '-nostdinc', + '-Xclang', '-nobuiltininc', + '-Xclang', '-nostdsysteminc', + '-Xclang', '-I%s/system/include' % emscripten_root, + '-O1', + ] - link_cmd = [ - os.path.join(llvm_bin, 'wasm-ld'), '-flavor', 'wasm', - '-z', '-stack-size=1048576', - obj_path, '-o', wasm_path, - '--allow-undefined', - '--export', '__wasm_call_ctors', - '--global-base=568', - ] - if is_shared: - compile_cmd.append('-fPIC') - compile_cmd.append('-fvisibility=default') - link_cmd.append('-shared') - else: - link_cmd.append('--entry=main') + link_cmd = [ + os.path.join(llvm_bin, 'wasm-ld'), '-flavor', 'wasm', + '-z', '-stack-size=1048576', + obj_path, '-o', wasm_path, + '--allow-undefined', + '--export', '__wasm_call_ctors', + '--global-base=568', + ] + if is_shared: + compile_cmd.append('-fPIC') + compile_cmd.append('-fvisibility=default') + link_cmd.append('-shared') + else: + link_cmd.append('--entry=main') - try: - run_command(compile_cmd) - run_command(link_cmd) - run_command(shared.WASM_DIS + [wasm_path, '-o', wast_path]) - finally: - # Don't need the .o or .wasm files, don't leave them around - shared.delete_from_orbit(obj_path) - shared.delete_from_orbit(wasm_path) + try: + run_command(compile_cmd) + run_command(link_cmd) + run_command(shared.WASM_DIS + [wasm_path, '-o', wast_path]) + finally: + # Don't need the .o or .wasm files, don't leave them around + shared.delete_from_orbit(obj_path) + shared.delete_from_orbit(wasm_path) if __name__ == '__main__': - if len(shared.options.positional_args) != 2: - print('Usage: generate_lld_tests.py [llvm/bin/dir] [path/to/emscripten]') - sys.exit(1) - generate_wast_files(*shared.options.positional_args) + if len(shared.options.positional_args) != 2: + print('Usage: generate_lld_tests.py [llvm/bin/dir] [path/to/emscripten]') + sys.exit(1) + generate_wast_files(*shared.options.positional_args) diff --git a/scripts/test/lld.py b/scripts/test/lld.py index 1a553506f..4aafd9549 100755 --- a/scripts/test/lld.py +++ b/scripts/test/lld.py @@ -22,72 +22,72 @@ from .shared import ( def args_for_finalize(filename): - if 'safe_stack' in filename: - return ['--check-stack-overflow', '--global-base=568'] - elif 'shared' in filename: - return ['--side-module'] - else: - return ['--global-base=568'] + if 'safe_stack' in filename: + return ['--check-stack-overflow', '--global-base=568'] + elif 'shared' in filename: + return ['--side-module'] + else: + return ['--global-base=568'] def test_wasm_emscripten_finalize(): - print('\n[ checking wasm-emscripten-finalize testcases... ]\n') + print('\n[ checking wasm-emscripten-finalize testcases... ]\n') - for wast_path in files_with_pattern(options.binaryen_test, 'lld', '*.wast'): - print('..', wast_path) - is_passive = '.passive.' in wast_path - mem_file = wast_path + '.mem' - extension_arg_map = { - '.out': [], - } - if not is_passive: - extension_arg_map.update({ - '.mem.out': ['--separate-data-segments', mem_file], - }) - for ext, ext_args in extension_arg_map.items(): - expected_file = wast_path + ext - if ext != '.out' and not os.path.exists(expected_file): - continue + for wast_path in files_with_pattern(options.binaryen_test, 'lld', '*.wast'): + print('..', wast_path) + is_passive = '.passive.' in wast_path + mem_file = wast_path + '.mem' + extension_arg_map = { + '.out': [], + } + if not is_passive: + extension_arg_map.update({ + '.mem.out': ['--separate-data-segments', mem_file], + }) + for ext, ext_args in extension_arg_map.items(): + expected_file = wast_path + ext + if ext != '.out' and not os.path.exists(expected_file): + continue - cmd = WASM_EMSCRIPTEN_FINALIZE + [wast_path, '-S'] + ext_args - cmd += args_for_finalize(os.path.basename(wast_path)) - actual = run_command(cmd) + cmd = WASM_EMSCRIPTEN_FINALIZE + [wast_path, '-S'] + ext_args + cmd += args_for_finalize(os.path.basename(wast_path)) + actual = run_command(cmd) - if not os.path.exists(expected_file): - print(actual) - fail_with_error('output ' + expected_file + ' does not exist') - fail_if_not_identical_to_file(actual, expected_file) - if ext == '.mem.out': - with open(mem_file) as mf: - mem = mf.read() - fail_if_not_identical_to_file(mem, wast_path + '.mem.mem') - os.remove(mem_file) + if not os.path.exists(expected_file): + print(actual) + fail_with_error('output ' + expected_file + ' does not exist') + fail_if_not_identical_to_file(actual, expected_file) + if ext == '.mem.out': + with open(mem_file) as mf: + mem = mf.read() + fail_if_not_identical_to_file(mem, wast_path + '.mem.mem') + os.remove(mem_file) def update_lld_tests(): - print('\n[ updatring wasm-emscripten-finalize testcases... ]\n') + print('\n[ updatring wasm-emscripten-finalize testcases... ]\n') - for wast_path in files_with_pattern(options.binaryen_test, 'lld', '*.wast'): - print('..', wast_path) - is_passive = '.passive.' in wast_path - mem_file = wast_path + '.mem' - extension_arg_map = { - '.out': [], - } - if not is_passive: - extension_arg_map.update({ - '.mem.out': ['--separate-data-segments', mem_file + '.mem'], - }) - for ext, ext_args in extension_arg_map.items(): - out_path = wast_path + ext - if ext != '.out' and not os.path.exists(out_path): - continue - cmd = WASM_EMSCRIPTEN_FINALIZE + [wast_path, '-S'] + ext_args - cmd += args_for_finalize(os.path.basename(wast_path)) - actual = run_command(cmd) - with open(out_path, 'w') as o: - o.write(actual) + for wast_path in files_with_pattern(options.binaryen_test, 'lld', '*.wast'): + print('..', wast_path) + is_passive = '.passive.' in wast_path + mem_file = wast_path + '.mem' + extension_arg_map = { + '.out': [], + } + if not is_passive: + extension_arg_map.update({ + '.mem.out': ['--separate-data-segments', mem_file + '.mem'], + }) + for ext, ext_args in extension_arg_map.items(): + out_path = wast_path + ext + if ext != '.out' and not os.path.exists(out_path): + continue + cmd = WASM_EMSCRIPTEN_FINALIZE + [wast_path, '-S'] + ext_args + cmd += args_for_finalize(os.path.basename(wast_path)) + actual = run_command(cmd) + with open(out_path, 'w') as o: + o.write(actual) if __name__ == '__main__': - test_wasm_emscripten_finalize() + test_wasm_emscripten_finalize() diff --git a/scripts/test/shared.py b/scripts/test/shared.py index a712f5176..2440077a8 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -24,61 +24,61 @@ import sys def parse_args(args): - usage_str = ("usage: 'python check.py [options]'\n\n" - "Runs the Binaryen test suite.") - parser = argparse.ArgumentParser(description=usage_str) - parser.add_argument( - '--torture', dest='torture', action='store_true', default=True, - help='Chooses whether to run the torture testcases. Default: true.') - parser.add_argument( - '--no-torture', dest='torture', action='store_false', - help='Disables running the torture testcases.') - parser.add_argument( - '--abort-on-first-failure', dest='abort_on_first_failure', - action='store_true', default=True, - help=('Specifies whether to halt test suite execution on first test error.' - ' Default: true.')) - parser.add_argument( - '--no-abort-on-first-failure', dest='abort_on_first_failure', - action='store_false', - help=('If set, the whole test suite will run to completion independent of' - ' earlier errors.')) - - parser.add_argument( - '--interpreter', dest='interpreter', default='', - help='Specifies the wasm interpreter executable to run tests on.') - parser.add_argument( - '--binaryen-bin', dest='binaryen_bin', default='', - help=('Specifies a path to where the built Binaryen executables reside at.' - ' Default: bin/ of current directory (i.e. assume an in-tree build).' - ' If not specified, the environment variable BINARYEN_ROOT= can also' - ' be used to adjust this.')) - parser.add_argument( - '--binaryen-root', dest='binaryen_root', default='', - help=('Specifies a path to the root of the Binaryen repository tree.' - ' Default: the directory where this file check.py resides.')) - parser.add_argument( - '--valgrind', dest='valgrind', default='', - help=('Specifies a path to Valgrind tool, which will be used to validate' - ' execution if specified. (Pass --valgrind=valgrind to search in' - ' PATH)')) - parser.add_argument( - '--valgrind-full-leak-check', dest='valgrind_full_leak_check', - action='store_true', default=False, - help=('If specified, all unfreed (but still referenced) pointers at the' - ' end of execution are considered memory leaks. Default: disabled.')) - parser.add_argument( - '--spec-test', action='append', nargs='*', default=[], dest='spec_tests', - help='Names specific spec tests to run.') - parser.add_argument( - 'positional_args', metavar='TEST_SUITE', nargs='*', - help=('Names specific test suites to run. Use --list-suites to see a ' - 'list of all test suites')) - parser.add_argument( - '--list-suites', action='store_true', - help='List the test suites that can be run.') - - return parser.parse_args(args) + usage_str = ("usage: 'python check.py [options]'\n\n" + "Runs the Binaryen test suite.") + parser = argparse.ArgumentParser(description=usage_str) + parser.add_argument( + '--torture', dest='torture', action='store_true', default=True, + help='Chooses whether to run the torture testcases. Default: true.') + parser.add_argument( + '--no-torture', dest='torture', action='store_false', + help='Disables running the torture testcases.') + parser.add_argument( + '--abort-on-first-failure', dest='abort_on_first_failure', + action='store_true', default=True, + help=('Specifies whether to halt test suite execution on first test error.' + ' Default: true.')) + parser.add_argument( + '--no-abort-on-first-failure', dest='abort_on_first_failure', + action='store_false', + help=('If set, the whole test suite will run to completion independent of' + ' earlier errors.')) + + parser.add_argument( + '--interpreter', dest='interpreter', default='', + help='Specifies the wasm interpreter executable to run tests on.') + parser.add_argument( + '--binaryen-bin', dest='binaryen_bin', default='', + help=('Specifies a path to where the built Binaryen executables reside at.' + ' Default: bin/ of current directory (i.e. assume an in-tree build).' + ' If not specified, the environment variable BINARYEN_ROOT= can also' + ' be used to adjust this.')) + parser.add_argument( + '--binaryen-root', dest='binaryen_root', default='', + help=('Specifies a path to the root of the Binaryen repository tree.' + ' Default: the directory where this file check.py resides.')) + parser.add_argument( + '--valgrind', dest='valgrind', default='', + help=('Specifies a path to Valgrind tool, which will be used to validate' + ' execution if specified. (Pass --valgrind=valgrind to search in' + ' PATH)')) + parser.add_argument( + '--valgrind-full-leak-check', dest='valgrind_full_leak_check', + action='store_true', default=False, + help=('If specified, all unfreed (but still referenced) pointers at the' + ' end of execution are considered memory leaks. Default: disabled.')) + parser.add_argument( + '--spec-test', action='append', nargs='*', default=[], dest='spec_tests', + help='Names specific spec tests to run.') + parser.add_argument( + 'positional_args', metavar='TEST_SUITE', nargs='*', + help=('Names specific test suites to run. Use --list-suites to see a ' + 'list of all test suites')) + parser.add_argument( + '--list-suites', action='store_true', + help='List the test suites that can be run.') + + return parser.parse_args(args) options = parse_args(sys.argv[1:]) @@ -89,23 +89,23 @@ warnings = [] def warn(text): - global warnings - warnings.append(text) - print('warning:', text, file=sys.stderr) + global warnings + warnings.append(text) + print('warning:', text, file=sys.stderr) # setup # Locate Binaryen build artifacts directory (bin/ by default) if not options.binaryen_bin: - if os.environ.get('BINARYEN_ROOT'): - if os.path.isdir(os.path.join(os.environ.get('BINARYEN_ROOT'), 'bin')): - options.binaryen_bin = os.path.join( - os.environ.get('BINARYEN_ROOT'), 'bin') + if os.environ.get('BINARYEN_ROOT'): + if os.path.isdir(os.path.join(os.environ.get('BINARYEN_ROOT'), 'bin')): + options.binaryen_bin = os.path.join( + os.environ.get('BINARYEN_ROOT'), 'bin') + else: + options.binaryen_bin = os.environ.get('BINARYEN_ROOT') else: - options.binaryen_bin = os.environ.get('BINARYEN_ROOT') - else: - options.binaryen_bin = 'bin' + options.binaryen_bin = 'bin' options.binaryen_bin = os.path.normpath(os.path.abspath(options.binaryen_bin)) @@ -115,12 +115,12 @@ os.environ['BINARYEN_ROOT'] = os.path.dirname(options.binaryen_bin) wasm_dis_filenames = ['wasm-dis', 'wasm-dis.exe'] if not any(os.path.isfile(os.path.join(options.binaryen_bin, f)) for f in wasm_dis_filenames): - warn('Binaryen not found (or has not been successfully built to bin/ ?') + warn('Binaryen not found (or has not been successfully built to bin/ ?') # Locate Binaryen source directory if not specified. if not options.binaryen_root: - path_parts = os.path.abspath(__file__).split(os.path.sep) - options.binaryen_root = os.path.sep.join(path_parts[:-3]) + path_parts = os.path.abspath(__file__).split(os.path.sep) + options.binaryen_root = os.path.sep.join(path_parts[:-3]) options.binaryen_test = os.path.join(options.binaryen_root, 'test') @@ -128,30 +128,29 @@ options.binaryen_test = os.path.join(options.binaryen_root, 'test') # Finds the given executable 'program' in PATH. # Operates like the Unix tool 'which'. def which(program): - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - if '.' not in fname: - if is_exe(exe_file + '.exe'): - return exe_file + '.exe' - if is_exe(exe_file + '.cmd'): - return exe_file + '.cmd' - if is_exe(exe_file + '.bat'): - return exe_file + '.bat' + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + if '.' not in fname: + if is_exe(exe_file + '.exe'): + return exe_file + '.exe' + if is_exe(exe_file + '.cmd'): + return exe_file + '.cmd' + if is_exe(exe_file + '.bat'): + return exe_file + '.bat' WATERFALL_BUILD_DIR = os.path.join(options.binaryen_test, 'wasm-install') -BIN_DIR = os.path.abspath(os.path.join( - WATERFALL_BUILD_DIR, 'wasm-install', 'bin')) +BIN_DIR = os.path.abspath(os.path.join(WATERFALL_BUILD_DIR, 'wasm-install', 'bin')) NATIVECC = (os.environ.get('CC') or which('mingw32-gcc') or which('gcc') or which('clang')) @@ -178,53 +177,53 @@ BINARYEN_JS = os.path.join(options.binaryen_root, 'out', 'binaryen.js') def wrap_with_valgrind(cmd): - # Exit code 97 is arbitrary, used to easily detect when an error occurs that - # is detected by Valgrind. - valgrind = [options.valgrind, '--quiet', '--error-exitcode=97'] - if options.valgrind_full_leak_check: - valgrind += ['--leak-check=full', '--show-leak-kinds=all'] - return valgrind + cmd + # Exit code 97 is arbitrary, used to easily detect when an error occurs that + # is detected by Valgrind. + valgrind = [options.valgrind, '--quiet', '--error-exitcode=97'] + if options.valgrind_full_leak_check: + valgrind += ['--leak-check=full', '--show-leak-kinds=all'] + return valgrind + cmd if options.valgrind: - WASM_OPT = wrap_with_valgrind(WASM_OPT) - WASM_AS = wrap_with_valgrind(WASM_AS) - WASM_DIS = wrap_with_valgrind(WASM_DIS) - ASM2WASM = wrap_with_valgrind(ASM2WASM) - WASM_SHELL = wrap_with_valgrind(WASM_SHELL) + WASM_OPT = wrap_with_valgrind(WASM_OPT) + WASM_AS = wrap_with_valgrind(WASM_AS) + WASM_DIS = wrap_with_valgrind(WASM_DIS) + ASM2WASM = wrap_with_valgrind(ASM2WASM) + WASM_SHELL = wrap_with_valgrind(WASM_SHELL) def in_binaryen(*args): - __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - return os.path.join(__rootpath__, *args) + __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + return os.path.join(__rootpath__, *args) os.environ['BINARYEN'] = in_binaryen() def get_platform(): - return {'linux': 'linux', - 'linux2': 'linux', - 'darwin': 'mac', - 'win32': 'windows', - 'cygwin': 'windows'}[sys.platform] + return {'linux': 'linux', + 'linux2': 'linux', + 'darwin': 'mac', + 'win32': 'windows', + 'cygwin': 'windows'}[sys.platform] def has_shell_timeout(): - return get_platform() != 'windows' and os.system('timeout 1s pwd') == 0 + return get_platform() != 'windows' and os.system('timeout 1s pwd') == 0 # Default options to pass to v8. These enable all features. V8_OPTS = [ - '--experimental-wasm-eh', - '--experimental-wasm-mv', - '--experimental-wasm-sat-f2i-conversions', - '--experimental-wasm-se', - '--experimental-wasm-threads', - '--experimental-wasm-simd', - '--experimental-wasm-anyref', - '--experimental-wasm-bulk-memory', - '--experimental-wasm-return-call' + '--experimental-wasm-eh', + '--experimental-wasm-mv', + '--experimental-wasm-sat-f2i-conversions', + '--experimental-wasm-se', + '--experimental-wasm-threads', + '--experimental-wasm-simd', + '--experimental-wasm-anyref', + '--experimental-wasm-bulk-memory', + '--experimental-wasm-return-call' ] has_vanilla_llvm = False @@ -232,232 +231,234 @@ has_vanilla_llvm = False # external tools try: - if NODEJS is not None: - subprocess.check_call( - [NODEJS, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if NODEJS is not None: + subprocess.check_call([NODEJS, '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) except (OSError, subprocess.CalledProcessError): - NODEJS = None + NODEJS = None if NODEJS is None: - warn('no node found (did not check proper js form)') + warn('no node found (did not check proper js form)') try: - if MOZJS is not None: - subprocess.check_call( - [MOZJS, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if MOZJS is not None: + subprocess.check_call([MOZJS, '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) except (OSError, subprocess.CalledProcessError): - MOZJS = None + MOZJS = None if MOZJS is None: - warn('no mozjs found (did not check native wasm support nor asm.js' - ' validation)') + warn('no mozjs found (did not check native wasm support nor asm.js' + ' validation)') try: - if EMCC is not None: - subprocess.check_call( - [EMCC, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if EMCC is not None: + subprocess.check_call([EMCC, '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) except (OSError, subprocess.CalledProcessError): - EMCC = None + EMCC = None if EMCC is None: - warn('no emcc found (did not check non-vanilla emscripten/binaryen' - ' integration)') + warn('no emcc found (did not check non-vanilla emscripten/binaryen' + ' integration)') has_vanilla_emcc = False try: - subprocess.check_call( - [os.path.join(options.binaryen_test, 'emscripten', 'emcc'), '--version'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - has_vanilla_emcc = True + subprocess.check_call( + [os.path.join(options.binaryen_test, 'emscripten', 'emcc'), '--version'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + has_vanilla_emcc = True except (OSError, subprocess.CalledProcessError): - pass + pass # utilities # removes a file if it exists, using any and all ways of doing so def delete_from_orbit(filename): - try: - os.unlink(filename) - except OSError: - pass - if not os.path.exists(filename): - return - try: - shutil.rmtree(filename, ignore_errors=True) - except OSError: - pass - if not os.path.exists(filename): - return - try: - import stat - os.chmod(filename, os.stat(filename).st_mode | stat.S_IWRITE) - - def remove_readonly_and_try_again(func, path, exc_info): - if not (os.stat(path).st_mode & stat.S_IWRITE): - os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE) - func(path) - else: - raise - shutil.rmtree(filename, onerror=remove_readonly_and_try_again) - except OSError: - pass + try: + os.unlink(filename) + except OSError: + pass + if not os.path.exists(filename): + return + try: + shutil.rmtree(filename, ignore_errors=True) + except OSError: + pass + if not os.path.exists(filename): + return + try: + import stat + os.chmod(filename, os.stat(filename).st_mode | stat.S_IWRITE) + + def remove_readonly_and_try_again(func, path, exc_info): + if not (os.stat(path).st_mode & stat.S_IWRITE): + os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE) + func(path) + else: + raise + shutil.rmtree(filename, onerror=remove_readonly_and_try_again) + except OSError: + pass # This is a workaround for https://bugs.python.org/issue9400 class Py2CalledProcessError(subprocess.CalledProcessError): - def __init__(self, returncode, cmd, output=None, stderr=None): - super(Exception, self).__init__(returncode, cmd, output, stderr) - self.returncode = returncode - self.cmd = cmd - self.output = output - self.stderr = stderr + def __init__(self, returncode, cmd, output=None, stderr=None): + super(Exception, self).__init__(returncode, cmd, output, stderr) + self.returncode = returncode + self.cmd = cmd + self.output = output + self.stderr = stderr def run_process(cmd, check=True, input=None, capture_output=False, *args, **kw): - if input and type(input) == str: - input = bytes(input, 'utf-8') - if capture_output: - kw['stdout'] = subprocess.PIPE - kw['stderr'] = subprocess.PIPE - ret = subprocess.run(cmd, check=check, input=input, *args, **kw) - if ret.stdout is not None: - ret.stdout = ret.stdout.decode('utf-8') - if ret.stderr is not None: - ret.stderr = ret.stderr.decode('utf-8') - return ret + if input and type(input) == str: + input = bytes(input, 'utf-8') + if capture_output: + kw['stdout'] = subprocess.PIPE + kw['stderr'] = subprocess.PIPE + ret = subprocess.run(cmd, check=check, input=input, *args, **kw) + if ret.stdout is not None: + ret.stdout = ret.stdout.decode('utf-8') + if ret.stderr is not None: + ret.stderr = ret.stderr.decode('utf-8') + return ret def fail_with_error(msg): - global num_failures - try: - num_failures += 1 - raise Exception(msg) - except Exception as e: - print(str(e)) - if options.abort_on_first_failure: - raise + global num_failures + try: + num_failures += 1 + raise Exception(msg) + except Exception as e: + print(str(e)) + if options.abort_on_first_failure: + raise def fail(actual, expected, fromfile='expected'): - diff_lines = difflib.unified_diff( - expected.split('\n'), actual.split('\n'), - fromfile=fromfile, tofile='actual') - diff_str = ''.join([a.rstrip() + '\n' for a in diff_lines])[:] - fail_with_error("incorrect output, diff:\n\n%s" % diff_str) + diff_lines = difflib.unified_diff( + expected.split('\n'), actual.split('\n'), + fromfile=fromfile, tofile='actual') + diff_str = ''.join([a.rstrip() + '\n' for a in diff_lines])[:] + fail_with_error("incorrect output, diff:\n\n%s" % diff_str) def fail_if_not_identical(actual, expected, fromfile='expected'): - if expected != actual: - fail(actual, expected, fromfile=fromfile) + if expected != actual: + fail(actual, expected, fromfile=fromfile) def fail_if_not_contained(actual, expected): - if expected not in actual: - fail(actual, expected) + if expected not in actual: + fail(actual, expected) def fail_if_not_identical_to_file(actual, expected_file): - binary = expected_file.endswith(".wasm") or type(actual) == bytes - with open(expected_file, 'rb' if binary else 'r') as f: - fail_if_not_identical(actual, f.read(), fromfile=expected_file) + binary = expected_file.endswith(".wasm") or type(actual) == bytes + with open(expected_file, 'rb' if binary else 'r') as f: + fail_if_not_identical(actual, f.read(), fromfile=expected_file) if len(requested) == 0: - tests = sorted(os.listdir(os.path.join(options.binaryen_test))) + tests = sorted(os.listdir(os.path.join(options.binaryen_test))) else: - tests = requested[:] + tests = requested[:] if not options.interpreter: - warn('no interpreter provided (did not test spec interpreter validation)') + warn('no interpreter provided (did not test spec interpreter validation)') if not has_vanilla_emcc: - warn('no functional emcc submodule found') + warn('no functional emcc submodule found') # check utilities def validate_binary(wasm): - if V8: - cmd = [V8] + V8_OPTS + [in_binaryen('scripts', 'validation_shell.js'), '--', wasm] - print(' ', ' '.join(cmd)) - subprocess.check_call(cmd, stdout=subprocess.PIPE) - else: - print('(skipping v8 binary validation)') + if V8: + cmd = [V8] + V8_OPTS + [in_binaryen('scripts', 'validation_shell.js'), '--', wasm] + print(' ', ' '.join(cmd)) + subprocess.check_call(cmd, stdout=subprocess.PIPE) + else: + print('(skipping v8 binary validation)') def binary_format_check(wast, verify_final_result=True, wasm_as_args=['-g'], binary_suffix='.fromBinary', original_wast=None): - # checks we can convert the wast to binary and back - - print(' (binary format check)') - cmd = WASM_AS + [wast, '-o', 'a.wasm', '-all'] + wasm_as_args - print(' ', ' '.join(cmd)) - if os.path.exists('a.wasm'): - os.unlink('a.wasm') - subprocess.check_call(cmd, stdout=subprocess.PIPE) - assert os.path.exists('a.wasm') - - # make sure it is a valid wasm, using a real wasm VM - if os.path.basename(original_wast or wast) not in [ - 'atomics.wast', # https://bugs.chromium.org/p/v8/issues/detail?id=9425 - 'simd.wast', # https://bugs.chromium.org/p/v8/issues/detail?id=8460 - ]: - validate_binary('a.wasm') - - cmd = WASM_DIS + ['a.wasm', '-o', 'ab.wast'] - print(' ', ' '.join(cmd)) - if os.path.exists('ab.wast'): - os.unlink('ab.wast') - subprocess.check_call(cmd, stdout=subprocess.PIPE) - assert os.path.exists('ab.wast') - - # make sure it is a valid wast - cmd = WASM_OPT + ['ab.wast', '-all'] - print(' ', ' '.join(cmd)) - subprocess.check_call(cmd, stdout=subprocess.PIPE) - - if verify_final_result: - actual = open('ab.wast').read() - fail_if_not_identical_to_file(actual, wast + binary_suffix) - - return 'ab.wast' + # checks we can convert the wast to binary and back + + print(' (binary format check)') + cmd = WASM_AS + [wast, '-o', 'a.wasm', '-all'] + wasm_as_args + print(' ', ' '.join(cmd)) + if os.path.exists('a.wasm'): + os.unlink('a.wasm') + subprocess.check_call(cmd, stdout=subprocess.PIPE) + assert os.path.exists('a.wasm') + + # make sure it is a valid wasm, using a real wasm VM + if os.path.basename(original_wast or wast) not in [ + 'atomics.wast', # https://bugs.chromium.org/p/v8/issues/detail?id=9425 + 'simd.wast', # https://bugs.chromium.org/p/v8/issues/detail?id=8460 + ]: + validate_binary('a.wasm') + + cmd = WASM_DIS + ['a.wasm', '-o', 'ab.wast'] + print(' ', ' '.join(cmd)) + if os.path.exists('ab.wast'): + os.unlink('ab.wast') + subprocess.check_call(cmd, stdout=subprocess.PIPE) + assert os.path.exists('ab.wast') + + # make sure it is a valid wast + cmd = WASM_OPT + ['ab.wast', '-all'] + print(' ', ' '.join(cmd)) + subprocess.check_call(cmd, stdout=subprocess.PIPE) + + if verify_final_result: + actual = open('ab.wast').read() + fail_if_not_identical_to_file(actual, wast + binary_suffix) + + return 'ab.wast' def minify_check(wast, verify_final_result=True): - # checks we can parse minified output - - print(' (minify check)') - cmd = WASM_OPT + [wast, '--print-minified', '-all'] - print(' ', ' '.join(cmd)) - subprocess.check_call(cmd, stdout=open('a.wast', 'w'), stderr=subprocess.PIPE) - assert os.path.exists('a.wast') - subprocess.check_call( - WASM_OPT + ['a.wast', '--print-minified', '-all'], - stdout=open('b.wast', 'w'), stderr=subprocess.PIPE) - assert os.path.exists('b.wast') - if verify_final_result: - expected = open('a.wast').read() - actual = open('b.wast').read() - if actual != expected: - fail(actual, expected) - if os.path.exists('a.wast'): - os.unlink('a.wast') - if os.path.exists('b.wast'): - os.unlink('b.wast') + # checks we can parse minified output + + print(' (minify check)') + cmd = WASM_OPT + [wast, '--print-minified', '-all'] + print(' ', ' '.join(cmd)) + subprocess.check_call(cmd, stdout=open('a.wast', 'w'), stderr=subprocess.PIPE) + assert os.path.exists('a.wast') + subprocess.check_call(WASM_OPT + ['a.wast', '--print-minified', '-all'], + stdout=open('b.wast', 'w'), stderr=subprocess.PIPE) + assert os.path.exists('b.wast') + if verify_final_result: + expected = open('a.wast').read() + actual = open('b.wast').read() + if actual != expected: + fail(actual, expected) + if os.path.exists('a.wast'): + os.unlink('a.wast') + if os.path.exists('b.wast'): + os.unlink('b.wast') def files_with_pattern(*path_pattern): - return sorted(glob.glob(os.path.join(*path_pattern))) + return sorted(glob.glob(os.path.join(*path_pattern))) # run a check with BINARYEN_PASS_DEBUG set, to do full validation def with_pass_debug(check): - old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') - try: - os.environ['BINARYEN_PASS_DEBUG'] = '1' - check() - finally: - if old_pass_debug is not None: - os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug - else: - if 'BINARYEN_PASS_DEBUG' in os.environ: - del os.environ['BINARYEN_PASS_DEBUG'] + old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') + try: + os.environ['BINARYEN_PASS_DEBUG'] = '1' + check() + finally: + if old_pass_debug is not None: + os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug + else: + if 'BINARYEN_PASS_DEBUG' in os.environ: + del os.environ['BINARYEN_PASS_DEBUG'] diff --git a/scripts/test/support.py b/scripts/test/support.py index 90bc531eb..8f48a7af9 100644 --- a/scripts/test/support.py +++ b/scripts/test/support.py @@ -21,178 +21,178 @@ import tempfile def _open_archive(tarfile, tmp_dir): - with tempfile.TemporaryFile(mode='w+') as f: - try: - subprocess.check_call(['tar', '-xvf', tarfile], cwd=tmp_dir, stdout=f) - except Exception: - f.seek(0) - sys.stderr.write(f.read()) - raise - return os.listdir(tmp_dir) + with tempfile.TemporaryFile(mode='w+') as f: + try: + subprocess.check_call(['tar', '-xvf', tarfile], cwd=tmp_dir, stdout=f) + except Exception: + f.seek(0) + sys.stderr.write(f.read()) + raise + return os.listdir(tmp_dir) def _files_same(dir1, dir2, basenames): - diff = filecmp.cmpfiles(dir1, dir2, basenames) - return 0 == len(diff[1] + diff[2]) + diff = filecmp.cmpfiles(dir1, dir2, basenames) + return 0 == len(diff[1] + diff[2]) def _dirs_same(dir1, dir2, basenames): - for d in basenames: - left = os.path.join(dir1, d) - right = os.path.join(dir2, d) - if not (os.path.isdir(left) and os.path.isdir(right)): - return False - diff = filecmp.dircmp(right, right) - if 0 != len(diff.left_only + diff.right_only + diff.diff_files + - diff.common_funny + diff.funny_files): - return False - return True + for d in basenames: + left = os.path.join(dir1, d) + right = os.path.join(dir2, d) + if not (os.path.isdir(left) and os.path.isdir(right)): + return False + diff = filecmp.dircmp(right, right) + if 0 != len(diff.left_only + diff.right_only + diff.diff_files + + diff.common_funny + diff.funny_files): + return False + return True def _move_files(dirfrom, dirto, basenames): - for f in basenames: - from_file = os.path.join(dirfrom, f) - to_file = os.path.join(dirto, f) - if os.path.isfile(to_file): - os.path.remove(to_file) - shutil.move(from_file, to_file) + for f in basenames: + from_file = os.path.join(dirfrom, f) + to_file = os.path.join(dirto, f) + if os.path.isfile(to_file): + os.path.remove(to_file) + shutil.move(from_file, to_file) def _move_dirs(dirfrom, dirto, basenames): for d in basenames: - from_dir = os.path.join(dirfrom, d) - to_dir = os.path.join(dirto, d) - if os.path.isdir(to_dir): - shutil.rmtree(to_dir) - shutil.move(from_dir, to_dir) + from_dir = os.path.join(dirfrom, d) + to_dir = os.path.join(dirto, d) + if os.path.isdir(to_dir): + shutil.rmtree(to_dir) + shutil.move(from_dir, to_dir) def untar(tarfile, outdir): - """Returns True if untar content differs from pre-existing outdir content.""" - tmpdir = tempfile.mkdtemp() - try: - untared = _open_archive(tarfile, tmpdir) - files = [f for f in untared if os.path.isfile(os.path.join(tmpdir, f))] - dirs = [d for d in untared if os.path.isdir(os.path.join(tmpdir, d))] - assert len(files) + len(dirs) == len(untared), 'Only files and directories' - if _files_same(tmpdir, outdir, files) and _dirs_same(tmpdir, outdir, dirs): - # Nothing new or different in the tarfile. - return False - # Some or all of the files / directories are new. - _move_files(tmpdir, outdir, files) - _move_dirs(tmpdir, outdir, dirs) - return True - finally: - if os.path.isdir(tmpdir): - shutil.rmtree(tmpdir) + """Returns True if untar content differs from pre-existing outdir content.""" + tmpdir = tempfile.mkdtemp() + try: + untared = _open_archive(tarfile, tmpdir) + files = [f for f in untared if os.path.isfile(os.path.join(tmpdir, f))] + dirs = [d for d in untared if os.path.isdir(os.path.join(tmpdir, d))] + assert len(files) + len(dirs) == len(untared), 'Only files and directories' + if _files_same(tmpdir, outdir, files) and _dirs_same(tmpdir, outdir, dirs): + # Nothing new or different in the tarfile. + return False + # Some or all of the files / directories are new. + _move_files(tmpdir, outdir, files) + _move_dirs(tmpdir, outdir, dirs) + return True + finally: + if os.path.isdir(tmpdir): + shutil.rmtree(tmpdir) def split_wast(wastFile): - # if it's a binary, leave it as is, we can't split it - wast = None - if not wastFile.endswith('.wasm'): - try: - wast = open(wastFile, 'r').read() - except Exception: - pass - - if not wast: - return ((open(wastFile, 'rb').read(), []),) - - # .wast files can contain multiple modules, and assertions for each one. - # this splits out a wast into [(module, assertions), ..] - # we ignore module invalidity tests here. - ret = [] - - def to_end(j): - depth = 1 - while depth > 0 and j < len(wast): - if wast[j] == '"': - while 1: - j = wast.find('"', j + 1) - if wast[j - 1] == '\\': + # if it's a binary, leave it as is, we can't split it + wast = None + if not wastFile.endswith('.wasm'): + try: + wast = open(wastFile, 'r').read() + except Exception: + pass + + if not wast: + return ((open(wastFile, 'rb').read(), []),) + + # .wast files can contain multiple modules, and assertions for each one. + # this splits out a wast into [(module, assertions), ..] + # we ignore module invalidity tests here. + ret = [] + + def to_end(j): + depth = 1 + while depth > 0 and j < len(wast): + if wast[j] == '"': + while 1: + j = wast.find('"', j + 1) + if wast[j - 1] == '\\': + continue + break + assert j > 0 + elif wast[j] == '(': + depth += 1 + elif wast[j] == ')': + depth -= 1 + elif wast[j] == ';' and wast[j + 1] == ';': + j = wast.find('\n', j) + j += 1 + return j + + i = 0 + while i >= 0: + start = wast.find('(', i) + if start >= 0 and wast[start + 1] == ';': + # block comment + i = wast.find(';)', start + 2) + assert i > 0, wast[start:] + i += 2 + continue + skip = wast.find(';', i) + if skip >= 0 and skip < start and skip + 1 < len(wast): + if wast[skip + 1] == ';': + i = wast.find('\n', i) + 1 + continue + if start < 0: + break + i = to_end(start + 1) + chunk = wast[start:i] + if chunk.startswith('(module'): + ret += [(chunk, [])] + elif chunk.startswith('(assert_invalid'): continue - break - assert j > 0 - elif wast[j] == '(': - depth += 1 - elif wast[j] == ')': - depth -= 1 - elif wast[j] == ';' and wast[j + 1] == ';': - j = wast.find('\n', j) - j += 1 - return j - - i = 0 - while i >= 0: - start = wast.find('(', i) - if start >= 0 and wast[start + 1] == ';': - # block comment - i = wast.find(';)', start + 2) - assert i > 0, wast[start:] - i += 2 - continue - skip = wast.find(';', i) - if skip >= 0 and skip < start and skip + 1 < len(wast): - if wast[skip + 1] == ';': - i = wast.find('\n', i) + 1 - continue - if start < 0: - break - i = to_end(start + 1) - chunk = wast[start:i] - if chunk.startswith('(module'): - ret += [(chunk, [])] - elif chunk.startswith('(assert_invalid'): - continue - elif chunk.startswith(('(assert', '(invoke')): - ret[-1][1].append(chunk) - return ret + elif chunk.startswith(('(assert', '(invoke')): + ret[-1][1].append(chunk) + return ret # write a split wast from split_wast. the wast may be binary if the original # file was binary def write_wast(filename, wast, asserts=[]): - if type(wast) == bytes: - assert not asserts - with open(filename, 'wb') as o: - o.write(wast) - else: - with open(filename, 'w') as o: - o.write(wast + '\n'.join(asserts)) + if type(wast) == bytes: + assert not asserts + with open(filename, 'wb') as o: + o.write(wast) + else: + with open(filename, 'w') as o: + o.write(wast + '\n'.join(asserts)) def run_command(cmd, expected_status=0, stderr=None, expected_err=None, err_contains=False, err_ignore=None): - if expected_err is not None: - assert stderr == subprocess.PIPE or stderr is None,\ - "Can't redirect stderr if using expected_err" - stderr = subprocess.PIPE - print('executing: ', ' '.join(cmd)) - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=stderr, universal_newlines=True) - out, err = proc.communicate() - code = proc.returncode - if expected_status is not None and code != expected_status: - raise Exception(('run_command failed (%s)' % code, out + str(err or ''))) - if expected_err is not None: - if err_ignore is not None: - err = "\n".join([line for line in err.split('\n') if err_ignore not in line]) - err_correct = expected_err in err if err_contains else expected_err == err - if not err_correct: - raise Exception(('run_command unexpected stderr', - "expected '%s', actual '%s'" % (expected_err, err))) - return out + if expected_err is not None: + assert stderr == subprocess.PIPE or stderr is None,\ + "Can't redirect stderr if using expected_err" + stderr = subprocess.PIPE + print('executing: ', ' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=stderr, universal_newlines=True) + out, err = proc.communicate() + code = proc.returncode + if expected_status is not None and code != expected_status: + raise Exception(('run_command failed (%s)' % code, out + str(err or ''))) + if expected_err is not None: + if err_ignore is not None: + err = "\n".join([line for line in err.split('\n') if err_ignore not in line]) + err_correct = expected_err in err if err_contains else expected_err == err + if not err_correct: + raise Exception(('run_command unexpected stderr', + "expected '%s', actual '%s'" % (expected_err, err))) + return out def node_has_webassembly(cmd): - cmd = [cmd, '-e', 'process.stdout.write(typeof WebAssembly)'] - return run_command(cmd) == 'object' + cmd = [cmd, '-e', 'process.stdout.write(typeof WebAssembly)'] + return run_command(cmd) == 'object' def node_test_glue(): - # running concatenated files (a.js) in node interferes with module loading - # because the concatenated file expects a 'var Binaryen' but binaryen.js - # assigned to module.exports. this is correct behavior but tests then need - # a workaround: - return ('if (typeof module === "object" && typeof exports === "object")\n' - ' Binaryen = module.exports;\n') + # running concatenated files (a.js) in node interferes with module loading + # because the concatenated file expects a 'var Binaryen' but binaryen.js + # assigned to module.exports. this is correct behavior but tests then need + # a workaround: + return ('if (typeof module === "object" && typeof exports === "object")\n' + ' Binaryen = module.exports;\n') diff --git a/scripts/test/wasm2js.py b/scripts/test/wasm2js.py index 8c8f9a840..39ba946fe 100755 --- a/scripts/test/wasm2js.py +++ b/scripts/test/wasm2js.py @@ -36,155 +36,155 @@ wasm2js_blacklist = ['empty_imported_table.wast'] def test_wasm2js_output(): - for opt in (0, 1): - for wasm in tests + spec_tests + extra_wasm2js_tests: - if not wasm.endswith('.wast'): - continue - basename = os.path.basename(wasm) - if basename in wasm2js_blacklist: - continue + for opt in (0, 1): + for wasm in tests + spec_tests + extra_wasm2js_tests: + if not wasm.endswith('.wast'): + continue + basename = os.path.basename(wasm) + if basename in wasm2js_blacklist: + continue - asm = basename.replace('.wast', '.2asm.js') - expected_file = os.path.join(wasm2js_dir, asm) - if opt: - expected_file += '.opt' + asm = basename.replace('.wast', '.2asm.js') + expected_file = os.path.join(wasm2js_dir, asm) + if opt: + expected_file += '.opt' - if not os.path.exists(expected_file): - continue + if not os.path.exists(expected_file): + continue - print('..', wasm) + print('..', wasm) - t = os.path.join(options.binaryen_test, wasm) + t = os.path.join(options.binaryen_test, wasm) - all_out = [] + all_out = [] - for module, asserts in split_wast(t): - write_wast('split.wast', module, asserts) + for module, asserts in split_wast(t): + write_wast('split.wast', module, asserts) - cmd = WASM2JS + ['split.wast'] - if opt: - cmd += ['-O'] - if 'emscripten' in wasm: - cmd += ['--emscripten'] - out = run_command(cmd) - all_out.append(out) + cmd = WASM2JS + ['split.wast'] + if opt: + cmd += ['-O'] + if 'emscripten' in wasm: + cmd += ['--emscripten'] + out = run_command(cmd) + all_out.append(out) - if not NODEJS and not MOZJS: - print('No JS interpreters. Skipping spec tests.') - continue + if not NODEJS and not MOZJS: + print('No JS interpreters. Skipping spec tests.') + continue - open('a.2asm.mjs', 'w').write(out) + open('a.2asm.mjs', 'w').write(out) - cmd += ['--allow-asserts'] - out = run_command(cmd) - # also verify it passes pass-debug verifications - with_pass_debug(lambda: run_command(cmd)) + cmd += ['--allow-asserts'] + out = run_command(cmd) + # also verify it passes pass-debug verifications + with_pass_debug(lambda: run_command(cmd)) - open('a.2asm.asserts.mjs', 'w').write(out) + open('a.2asm.asserts.mjs', 'w').write(out) - # verify asm.js is valid js, note that we're using --experimental-modules - # to enable ESM syntax and we're also passing a custom loader to handle the - # `spectest` and `env` modules in our tests. - if NODEJS: - node = [NODEJS, '--experimental-modules', '--loader', './scripts/test/node-esm-loader.mjs'] - cmd = node[:] - cmd.append('a.2asm.mjs') - out = run_command(cmd) - fail_if_not_identical(out, '') - cmd = node[:] - cmd.append('a.2asm.asserts.mjs') - out = run_command(cmd, expected_err='', err_ignore='The ESM module loader is experimental') - fail_if_not_identical(out, '') + # verify asm.js is valid js, note that we're using --experimental-modules + # to enable ESM syntax and we're also passing a custom loader to handle the + # `spectest` and `env` modules in our tests. + if NODEJS: + node = [NODEJS, '--experimental-modules', '--loader', './scripts/test/node-esm-loader.mjs'] + cmd = node[:] + cmd.append('a.2asm.mjs') + out = run_command(cmd) + fail_if_not_identical(out, '') + cmd = node[:] + cmd.append('a.2asm.asserts.mjs') + out = run_command(cmd, expected_err='', err_ignore='The ESM module loader is experimental') + fail_if_not_identical(out, '') - fail_if_not_identical_to_file(''.join(all_out), expected_file) + fail_if_not_identical_to_file(''.join(all_out), expected_file) def test_asserts_output(): - for wasm in assert_tests: - print('..', wasm) + for wasm in assert_tests: + print('..', wasm) - asserts = os.path.basename(wasm).replace('.wast.asserts', '.asserts.js') - traps = os.path.basename(wasm).replace('.wast.asserts', '.traps.js') - asserts_expected_file = os.path.join(options.binaryen_test, asserts) - traps_expected_file = os.path.join(options.binaryen_test, traps) + asserts = os.path.basename(wasm).replace('.wast.asserts', '.asserts.js') + traps = os.path.basename(wasm).replace('.wast.asserts', '.traps.js') + asserts_expected_file = os.path.join(options.binaryen_test, asserts) + traps_expected_file = os.path.join(options.binaryen_test, traps) - wasm = os.path.join(wasm2js_dir, wasm) - cmd = WASM2JS + [wasm, '--allow-asserts'] - out = run_command(cmd) - fail_if_not_identical_to_file(out, asserts_expected_file) + wasm = os.path.join(wasm2js_dir, wasm) + cmd = WASM2JS + [wasm, '--allow-asserts'] + out = run_command(cmd) + fail_if_not_identical_to_file(out, asserts_expected_file) - cmd += ['--pedantic'] - out = run_command(cmd) - fail_if_not_identical_to_file(out, traps_expected_file) + cmd += ['--pedantic'] + out = run_command(cmd) + fail_if_not_identical_to_file(out, traps_expected_file) def test_wasm2js(): - print('\n[ checking wasm2js testcases... ]\n') - test_wasm2js_output() - test_asserts_output() + print('\n[ checking wasm2js testcases... ]\n') + test_wasm2js_output() + test_asserts_output() def update_wasm2js_tests(): - print('\n[ checking wasm2js ]\n') + print('\n[ checking wasm2js ]\n') - for opt in (0, 1): - for wasm in tests + spec_tests + extra_wasm2js_tests: - if not wasm.endswith('.wast'): - continue + for opt in (0, 1): + for wasm in tests + spec_tests + extra_wasm2js_tests: + if not wasm.endswith('.wast'): + continue - if os.path.basename(wasm) in wasm2js_blacklist: - continue + if os.path.basename(wasm) in wasm2js_blacklist: + continue - asm = os.path.basename(wasm).replace('.wast', '.2asm.js') - expected_file = os.path.join(wasm2js_dir, asm) - if opt: - expected_file += '.opt' + asm = os.path.basename(wasm).replace('.wast', '.2asm.js') + expected_file = os.path.join(wasm2js_dir, asm) + if opt: + expected_file += '.opt' - # we run wasm2js on tests and spec tests only if the output - # exists - only some work so far. the tests in extra are in - # the test/wasm2js dir and so are specific to wasm2js, and - # we run all of those. - if wasm not in extra_wasm2js_tests and not os.path.exists(expected_file): - continue + # we run wasm2js on tests and spec tests only if the output + # exists - only some work so far. the tests in extra are in + # the test/wasm2js dir and so are specific to wasm2js, and + # we run all of those. + if wasm not in extra_wasm2js_tests and not os.path.exists(expected_file): + continue - print('..', wasm) + print('..', wasm) - t = os.path.join(options.binaryen_test, wasm) + t = os.path.join(options.binaryen_test, wasm) - all_out = [] + all_out = [] - for module, asserts in split_wast(t): - write_wast('split.wast', module, asserts) + for module, asserts in split_wast(t): + write_wast('split.wast', module, asserts) - cmd = WASM2JS + ['split.wast'] - if opt: - cmd += ['-O'] - if 'emscripten' in wasm: - cmd += ['--emscripten'] - out = run_command(cmd) - all_out.append(out) + cmd = WASM2JS + ['split.wast'] + if opt: + cmd += ['-O'] + if 'emscripten' in wasm: + cmd += ['--emscripten'] + out = run_command(cmd) + all_out.append(out) - with open(expected_file, 'w') as o: - o.write(''.join(all_out)) + with open(expected_file, 'w') as o: + o.write(''.join(all_out)) - for wasm in assert_tests: - print('..', wasm) + for wasm in assert_tests: + print('..', wasm) - asserts = os.path.basename(wasm).replace('.wast.asserts', '.asserts.js') - traps = os.path.basename(wasm).replace('.wast.asserts', '.traps.js') - asserts_expected_file = os.path.join('test', asserts) - traps_expected_file = os.path.join('test', traps) + asserts = os.path.basename(wasm).replace('.wast.asserts', '.asserts.js') + traps = os.path.basename(wasm).replace('.wast.asserts', '.traps.js') + asserts_expected_file = os.path.join('test', asserts) + traps_expected_file = os.path.join('test', traps) - cmd = WASM2JS + [os.path.join(wasm2js_dir, wasm), '--allow-asserts'] - out = run_command(cmd) - with open(asserts_expected_file, 'w') as o: - o.write(out) + cmd = WASM2JS + [os.path.join(wasm2js_dir, wasm), '--allow-asserts'] + out = run_command(cmd) + with open(asserts_expected_file, 'w') as o: + o.write(out) - cmd += ['--pedantic'] - out = run_command(cmd) - with open(traps_expected_file, 'w') as o: - o.write(out) + cmd += ['--pedantic'] + out = run_command(cmd) + with open(traps_expected_file, 'w') as o: + o.write(out) if __name__ == "__main__": - test_wasm2js() + test_wasm2js() |