diff options
author | Alon Zakai <alonzakai@gmail.com> | 2016-07-13 12:15:20 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2016-07-13 16:07:59 -0700 |
commit | 9e2f21c9aa1993325b5a7043d206e2baf07e9180 (patch) | |
tree | 7fc213c0bde0d4da747d85ec791f08c7f083029a | |
parent | 9d3801039d2e2f9738d2c4829ee59aa047da1cc6 (diff) | |
download | binaryen-9e2f21c9aa1993325b5a7043d206e2baf07e9180.tar.gz binaryen-9e2f21c9aa1993325b5a7043d206e2baf07e9180.tar.bz2 binaryen-9e2f21c9aa1993325b5a7043d206e2baf07e9180.zip |
separate wasm-opt out from wasm-shell: opt optimizes, shell runs wast shell tests
-rw-r--r-- | CMakeLists.txt | 11 | ||||
-rwxr-xr-x | auto_update_tests.py | 12 | ||||
-rwxr-xr-x | check.py | 86 | ||||
-rwxr-xr-x | scripts/support.py | 57 | ||||
-rw-r--r-- | src/tools/wasm-as.cpp | 2 | ||||
-rw-r--r-- | src/tools/wasm-opt.cpp | 102 | ||||
-rw-r--r-- | src/tools/wasm-shell.cpp | 45 | ||||
m--------- | test/waterfall | 0 |
8 files changed, 200 insertions, 115 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 10120844e..38b2df763 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,17 @@ SET_PROPERTY(TARGET wasm-shell PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET wasm-shell PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS wasm-shell DESTINATION bin) +SET(wasm-opt_SOURCES + src/tools/wasm-opt.cpp + src/wasm.cpp +) +ADD_EXECUTABLE(wasm-opt + ${wasm-opt_SOURCES}) +TARGET_LINK_LIBRARIES(wasm-opt asmjs emscripten-optimizer ${all_passes} support) +SET_PROPERTY(TARGET wasm-opt PROPERTY CXX_STANDARD 11) +SET_PROPERTY(TARGET wasm-opt PROPERTY CXX_STANDARD_REQUIRED ON) +INSTALL(TARGETS wasm-opt DESTINATION bin) + SET(asm2wasm_SOURCES src/tools/asm2wasm.cpp src/wasm.cpp diff --git a/auto_update_tests.py b/auto_update_tests.py index 9505c2dc4..3b6c9bf24 100755 --- a/auto_update_tests.py +++ b/auto_update_tests.py @@ -2,6 +2,8 @@ import os, sys, subprocess, difflib +from scripts.support import run_command, split_wast + print '[ processing and updating testcases... ]\n' for asm in sorted(os.listdir('test')): @@ -68,9 +70,13 @@ for t in sorted(os.listdir(os.path.join('test', 'passes'))): print '..', t passname = os.path.basename(t).replace('.wast', '') opts = ['-O'] if passname == 'O' else ['--' + p for p in passname.split('_')] - cmd = [os.path.join('bin', 'wasm-shell')] + opts + [os.path.join('test', 'passes', t), '--print'] - print ' ', ' '.join(cmd) - actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + t = os.path.join('test', 'passes', t) + actual = '' + for module, asserts in split_wast(t): + assert len(asserts) == 0 + open('split.wast', 'w').write(module) + cmd = [os.path.join('bin', 'wasm-opt')] + opts + ['split.wast', '--print'] + actual += run_command(cmd) open(os.path.join('test', 'passes', passname + '.txt'), 'w').write(actual) print '\n[ checking binary format testcases... ]\n' @@ -17,7 +17,7 @@ import os, shutil, sys, subprocess, difflib, json, time, urllib2 import scripts.storage -import scripts.support +from scripts.support import run_command, split_wast interpreter = None requested = [] @@ -155,13 +155,6 @@ def delete_from_orbit(filename): # removes a file if it exists, using any and al except: pass -def run_command(cmd, expected_status=0, stderr=None): - print 'executing: ', ' '.join(cmd) - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=stderr) - out, err = proc.communicate() - if proc.returncode != expected_status: raise Exception(('run_command failed', err)) - return out - def fail(actual, expected): raise Exception("incorrect output, diff:\n\n%s" % ( ''.join([a.rstrip()+'\n' for a in difflib.unified_diff(expected.split('\n'), actual.split('\n'), fromfile='expected', tofile='actual')])[:] @@ -193,50 +186,6 @@ if not has_vanilla_emcc: # check utilities -def split_wast(wast): - # .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. - wast = open(wast).read() - ret = [] - def to_end(j): - depth = 1 - while depth > 0 and j < len(wast): - if wast[j] == '"': - j = wast.find('"', j + 1) - 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 - def binary_format_check(wast, verify_final_result=True): # checks we can convert the wast to binary and back @@ -270,11 +219,11 @@ def minify_check(wast, verify_final_result=True): # checks we can parse minified output print ' (minify check)' - cmd = [os.path.join('bin', 'wasm-shell'), wast, '--print-minified'] + cmd = [os.path.join('bin', 'wasm-opt'), wast, '--print-minified'] print ' ', ' '.join(cmd) - subprocess.check_call([os.path.join('bin', 'wasm-shell'), wast, '--print-minified'], stdout=open('a.wasm', 'w'), stderr=subprocess.PIPE) + subprocess.check_call([os.path.join('bin', 'wasm-opt'), wast, '--print-minified'], stdout=open('a.wasm', 'w'), stderr=subprocess.PIPE) assert os.path.exists('a.wasm') - subprocess.check_call([os.path.join('bin', 'wasm-shell'), 'a.wasm', '--print-minified'], stdout=open('b.wasm', 'w'), stderr=subprocess.PIPE) + subprocess.check_call([os.path.join('bin', 'wasm-opt'), 'a.wasm', '--print-minified'], stdout=open('b.wasm', 'w'), stderr=subprocess.PIPE) assert os.path.exists('b.wasm') if verify_final_result: expected = open('a.wasm').read() @@ -301,23 +250,28 @@ for e in executables: assert e in err, 'Expected help to contain program name, got:\n%s' % err assert len(err.split('\n')) > 8, 'Expected some help, got:\n%s' % err -print '\n[ checking wasm-shell -o notation... ]\n' +print '\n[ checking wasm-opt -o notation... ]\n' wast = os.path.join('test', 'hello_world.wast') delete_from_orbit('a.wast') -cmd = [os.path.join('bin', 'wasm-shell'), wast, '-o', 'a.wast'] +cmd = [os.path.join('bin', 'wasm-opt'), wast, '-o', 'a.wast'] run_command(cmd) fail_if_not_identical(open('a.wast').read(), open(wast).read()) -print '\n[ checking wasm-shell passes... ]\n' +print '\n[ checking wasm-opt passes... ]\n' for t in sorted(os.listdir(os.path.join('test', 'passes'))): if t.endswith('.wast'): print '..', t passname = os.path.basename(t).replace('.wast', '') opts = ['-O'] if passname == 'O' else ['--' + p for p in passname.split('_')] - cmd = [os.path.join('bin', 'wasm-shell')] + opts + [os.path.join('test', 'passes', t), '--print'] - actual = run_command(cmd) + t = os.path.join('test', 'passes', t) + actual = '' + for module, asserts in split_wast(t): + assert len(asserts) == 0 + open('split.wast', 'w').write(module) + cmd = [os.path.join('bin', 'wasm-opt')] + opts + ['split.wast', '--print'] + actual += run_command(cmd) fail_if_not_identical(actual, open(os.path.join('test', 'passes', passname + '.txt')).read()) print '[ checking asm2wasm testcases... ]\n' @@ -350,7 +304,7 @@ for asm in tests: # verify in wasm if interpreter: # remove imports, spec interpreter doesn't know what to do with them - subprocess.check_call([os.path.join('bin', 'wasm-shell'), '--remove-imports', '--print', wasm], stdout=open('ztemp.wast', 'w'), stderr=subprocess.PIPE) + subprocess.check_call([os.path.join('bin', 'wasm-opt'), '--remove-imports', wasm], stdout=open('ztemp.wast', 'w'), stderr=subprocess.PIPE) proc = subprocess.Popen([interpreter, 'ztemp.wast'], stderr=subprocess.PIPE) out, err = proc.communicate() if proc.returncode != 0: @@ -370,28 +324,28 @@ for asm in tests: raise Exception('wasm interpreter error: ' + err) # failed to pretty-print raise Exception('wasm interpreter error') -print '\n[ checking wasm-shell parsing & printing... ]\n' +print '\n[ checking wasm-opt parsing & printing... ]\n' for t in sorted(os.listdir(os.path.join('test', 'print'))): if t.endswith('.wast'): print '..', t wasm = os.path.basename(t).replace('.wast', '') - cmd = [os.path.join('bin', 'wasm-shell'), os.path.join('test', 'print', t), '--print'] + cmd = [os.path.join('bin', 'wasm-opt'), os.path.join('test', 'print', t), '--print'] print ' ', ' '.join(cmd) actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() fail_if_not_identical(actual, open(os.path.join('test', 'print', wasm + '.txt')).read()) - cmd = [os.path.join('bin', 'wasm-shell'), os.path.join('test', 'print', t), '--print-minified'] + cmd = [os.path.join('bin', 'wasm-opt'), os.path.join('test', 'print', t), '--print-minified'] print ' ', ' '.join(cmd) actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() fail_if_not_identical(actual.strip(), open(os.path.join('test', 'print', wasm + '.minified.txt')).read().strip()) -print '\n[ checking wasm-shell testcases... ]\n' +print '\n[ checking wasm-opt testcases... ]\n' for t in tests: if t.endswith('.wast') and not t.startswith('spec'): print '..', t t = os.path.join('test', t) - cmd = [os.path.join('bin', 'wasm-shell'), t, '--print'] + cmd = [os.path.join('bin', 'wasm-opt'), t, '--print'] actual = run_command(cmd) actual = actual.replace('printing before:\n', '') diff --git a/scripts/support.py b/scripts/support.py index a99fb7604..2d4318980 100755 --- a/scripts/support.py +++ b/scripts/support.py @@ -87,3 +87,60 @@ def untar(tarfile, outdir): finally: if os.path.isdir(tmpdir): shutil.rmtree(tmpdir) + + +def split_wast(wast): + # .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. + wast = open(wast).read() + ret = [] + + def to_end(j): + depth = 1 + while depth > 0 and j < len(wast): + if wast[j] == '"': + j = wast.find('"', j + 1) + 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 + + +def run_command(cmd, expected_status=0, stderr=None): + print 'executing: ', ' '.join(cmd) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=stderr) + out, err = proc.communicate() + if proc.returncode != expected_status: + raise Exception(('run_command failed', err)) + return out diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp index e2425b24a..376c1288c 100644 --- a/src/tools/wasm-as.cpp +++ b/src/tools/wasm-as.cpp @@ -56,7 +56,7 @@ int main(int argc, const char *argv[]) { Module wasm; - try{ + try { if (options.debug) std::cerr << "s-parsing..." << std::endl; SExpressionParser parser(const_cast<char*>(input.c_str())); Element& root = *parser.root; diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp new file mode 100644 index 000000000..a992f77dd --- /dev/null +++ b/src/tools/wasm-opt.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2016 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// A WebAssembly optimizer, loads code, optionally runs passes on it, +// then writes it. +// + +#include <memory> + +#include "pass.h" +#include "support/command-line.h" +#include "support/file.h" +#include "wasm-printing.h" +#include "wasm-s-parser.h" +#include "wasm-validator.h" + +using namespace wasm; + +// +// main +// + +int main(int argc, const char* argv[]) { + Name entry; + std::vector<std::string> passes; + + Options options("wasm-opt", "Optimize .wast files"); + options + .add("--output", "-o", "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add("", "-O", "execute default optimization passes", + Options::Arguments::Zero, + [&passes](Options*, const std::string&) { + passes.push_back("O"); + }) + .add_positional("INFILE", Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); + for (const auto& p : PassRegistry::get()->getRegisteredNames()) { + options.add( + std::string("--") + p, "", PassRegistry::get()->getPassDescription(p), + Options::Arguments::Zero, + [&passes, p](Options*, const std::string&) { passes.push_back(p); }); + } + options.parse(argc, argv); + + auto input(read_file<std::string>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); + + Module wasm; + + try { + if (options.debug) std::cerr << "s-parsing..." << std::endl; + SExpressionParser parser(const_cast<char*>(input.c_str())); + Element& root = *parser.root; + if (options.debug) std::cerr << "w-parsing..." << std::endl; + SExpressionWasmBuilder builder(wasm, *root[0]); + assert(WasmValidator().validate(wasm)); + } catch (ParseException& p) { + p.dump(std::cerr); + Fatal() << "error in parsing input"; + } + + if (passes.size() > 0) { + if (options.debug) std::cerr << "running passes...\n"; + PassRunner passRunner(&wasm); + if (options.debug) passRunner.setDebug(true); + for (auto& passName : passes) { + if (passName == "O") { + passRunner.addDefaultOptimizationPasses(); + } else { + passRunner.add(passName); + } + } + passRunner.run(); + assert(WasmValidator().validate(wasm)); + } + + if (options.extra.count("output") > 0) { + if (options.debug) std::cerr << "writing..." << std::endl; + Output output(options.extra["output"], Flags::Text, options.debug ? Flags::Debug : Flags::Release); + WasmPrinter::printModule(&wasm, output.getStream()); + } +} diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 27fa6cd50..72e8e13ba 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -169,44 +169,21 @@ static void run_asserts(size_t* i, bool* checked, Module* wasm, int main(int argc, const char* argv[]) { Name entry; - std::vector<std::string> passes; Options options("wasm-shell", "Execute .wast files"); options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) .add( "--entry", "-e", "call the entry point after parsing the module", Options::Arguments::One, [&entry](Options*, const std::string& argument) { entry = argument; }) - .add("", "-O", "execute default optimization passes", - Options::Arguments::Zero, - [&passes](Options*, const std::string&) { - passes.push_back("O"); - }) .add_positional("INFILE", Options::Arguments::One, [](Options* o, const std::string& argument) { o->extra["infile"] = argument; }); - for (const auto& p : PassRegistry::get()->getRegisteredNames()) { - options.add( - std::string("--") + p, "", PassRegistry::get()->getPassDescription(p), - Options::Arguments::Zero, - [&passes, p](Options*, const std::string&) { passes.push_back(p); }); - } options.parse(argc, argv); auto input(read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); - std::unique_ptr<Output> output; - if (options.extra.count("output") > 0) { - output = wasm::make_unique<Output>(options.extra["output"], Flags::Text, options.debug ? Flags::Debug : Flags::Release); - } - bool checked = false; try { @@ -226,29 +203,7 @@ int main(int argc, const char* argv[]) { builder = wasm::make_unique<SExpressionWasmBuilder>(wasm, *root[i]); i++; assert(WasmValidator().validate(wasm)); - - MixedArena moreModuleAllocations; - - if (passes.size() > 0) { - if (options.debug) std::cerr << "running passes...\n"; - PassRunner passRunner(&wasm); - if (options.debug) passRunner.setDebug(true); - for (auto& passName : passes) { - if (passName == "O") { - passRunner.addDefaultOptimizationPasses(); - } else { - passRunner.add(passName); - } - } - passRunner.run(); - assert(WasmValidator().validate(wasm)); - } - run_asserts(&i, &checked, &wasm, &root, &builder, entry); - - if (output) { - WasmPrinter::printModule(&wasm, output->getStream()); - } } else { run_asserts(&i, &checked, nullptr, &root, nullptr, entry); } diff --git a/test/waterfall b/test/waterfall -Subproject cba60ec0417d7d6a741ab42de0f46eee79e4f1e +Subproject bc52e2063dd1bd06df64b7dfeceeb6e00437b90 |