| /* |
| * Copyright 2015 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 shell, loads a .wast file (WebAssembly in S-Expression format) |
| // and executes it. This provides similar functionality as the reference |
| // interpreter, like assert_* calls, so it can run the spec test suite. |
| // |
| |
| #include <memory> |
| |
| #include "execution-results.h" |
| #include "pass.h" |
| #include "shell-interface.h" |
| #include "support/command-line.h" |
| #include "support/file.h" |
| #include "wasm-interpreter.h" |
| #include "wasm-printing.h" |
| #include "wasm-s-parser.h" |
| #include "wasm-validator.h" |
| |
| using namespace cashew; |
| using namespace wasm; |
| |
| Name ASSERT_RETURN("assert_return"); |
| Name ASSERT_TRAP("assert_trap"); |
| Name ASSERT_INVALID("assert_invalid"); |
| Name ASSERT_MALFORMED("assert_malformed"); |
| Name ASSERT_UNLINKABLE("assert_unlinkable"); |
| Name INVOKE("invoke"); |
| Name GET("get"); |
| |
| // Modules named in the file |
| |
| std::map<Name, std::unique_ptr<Module>> modules; |
| std::map<Name, std::unique_ptr<SExpressionWasmBuilder>> builders; |
| std::map<Name, std::unique_ptr<ShellExternalInterface>> interfaces; |
| std::map<Name, std::unique_ptr<ModuleInstance>> instances; |
| |
| // |
| // An operation on a module |
| // |
| |
| struct Operation { |
| ModuleInstance* instance; |
| Name operation; |
| Name name; |
| LiteralList arguments; |
| |
| Operation(Element& element, |
| ModuleInstance* instanceInit, |
| SExpressionWasmBuilder& builder) |
| : instance(instanceInit) { |
| operation = element[0]->str(); |
| Index i = 1; |
| if (element.size() >= 3 && element[2]->isStr()) { |
| // module also specified |
| Name moduleName = element[i++]->str(); |
| instance = instances[moduleName].get(); |
| } |
| name = element[i++]->str(); |
| for (size_t j = i; j < element.size(); j++) { |
| Expression* argument = builder.parseExpression(*element[j]); |
| arguments.push_back(getSingleLiteralFromConstExpression(argument)); |
| } |
| } |
| |
| Literals operate() { |
| if (operation == INVOKE) { |
| return instance->callExport(name, arguments); |
| } else if (operation == GET) { |
| return {instance->getExport(name)}; |
| } else { |
| WASM_UNREACHABLE("unknown operation"); |
| } |
| } |
| }; |
| |
| static void run_asserts(Name moduleName, |
| size_t* i, |
| bool* checked, |
| Module* wasm, |
| Element* root, |
| SExpressionWasmBuilder* builder, |
| Name entry) { |
| ModuleInstance* instance = nullptr; |
| if (wasm) { |
| // prefix make_unique to work around visual studio bugs |
| auto tempInterface = wasm::make_unique<ShellExternalInterface>(); |
| auto tempInstance = |
| wasm::make_unique<ModuleInstance>(*wasm, tempInterface.get()); |
| interfaces[moduleName].swap(tempInterface); |
| instances[moduleName].swap(tempInstance); |
| instance = instances[moduleName].get(); |
| if (entry.is()) { |
| Function* function = wasm->getFunction(entry); |
| if (!function) { |
| std::cerr << "Unknown entry " << entry << std::endl; |
| } else { |
| LiteralList arguments; |
| for (auto& param : function->sig.params) { |
| arguments.push_back(Literal(param)); |
| } |
| try { |
| instance->callExport(entry, arguments); |
| } catch (ExitException&) { |
| } |
| } |
| } |
| } |
| while (*i < root->size()) { |
| Element& curr = *(*root)[*i]; |
| IString id = curr[0]->str(); |
| if (id == MODULE) { |
| break; |
| } |
| *checked = true; |
| Colors::red(std::cerr); |
| std::cerr << *i << '/' << (root->size() - 1); |
| Colors::green(std::cerr); |
| std::cerr << " CHECKING: "; |
| Colors::normal(std::cerr); |
| std::cerr << curr; |
| Colors::green(std::cerr); |
| std::cerr << " [line: " << curr.line << "]\n"; |
| Colors::normal(std::cerr); |
| if (id == ASSERT_INVALID || id == ASSERT_MALFORMED || |
| id == ASSERT_UNLINKABLE) { |
| // a module invalidity test |
| Module wasm; |
| wasm.features = FeatureSet::All; |
| bool invalid = false; |
| std::unique_ptr<SExpressionWasmBuilder> builder; |
| try { |
| builder = std::unique_ptr<SExpressionWasmBuilder>( |
| new SExpressionWasmBuilder(wasm, *curr[1])); |
| } catch (const ParseException&) { |
| invalid = true; |
| } |
| if (!invalid) { |
| // maybe parsed ok, but otherwise incorrect |
| invalid = !WasmValidator().validate(wasm); |
| } |
| if (!invalid && id == ASSERT_UNLINKABLE) { |
| // validate "instantiating" the mdoule |
| auto reportUnknownImport = [&](Importable* import) { |
| std::cerr << "unknown import: " << import->module << '.' |
| << import->base << '\n'; |
| invalid = true; |
| }; |
| ModuleUtils::iterImportedGlobals(wasm, reportUnknownImport); |
| ModuleUtils::iterImportedFunctions(wasm, [&](Importable* import) { |
| if (import->module == SPECTEST && import->base.startsWith(PRINT)) { |
| // We can handle it. |
| } else { |
| reportUnknownImport(import); |
| } |
| }); |
| if (wasm.memory.imported()) { |
| reportUnknownImport(&wasm.memory); |
| } |
| if (wasm.table.imported()) { |
| reportUnknownImport(&wasm.table); |
| } |
| for (auto& segment : wasm.table.segments) { |
| for (auto name : segment.data) { |
| // spec tests consider it illegal to use spectest.print in a table |
| if (auto* import = wasm.getFunction(name)) { |
| if (import->imported() && import->module == SPECTEST && |
| import->base.startsWith(PRINT)) { |
| std::cerr << "cannot put spectest.print in table\n"; |
| invalid = true; |
| } |
| } |
| } |
| } |
| } |
| if (!invalid) { |
| Colors::red(std::cerr); |
| Colors::normal(std::cerr); |
| std::cerr << &wasm << '\n'; |
| Fatal() << "[should have been invalid]"; |
| } |
| } else if (id == INVOKE) { |
| assert(wasm); |
| Operation operation(curr, instance, *builder); |
| operation.operate(); |
| } else if (wasm) { // if no wasm, we skipped the module |
| // an invoke test |
| bool trapped = false; |
| WASM_UNUSED(trapped); |
| Literals result; |
| try { |
| Operation operation(*curr[1], instance, *builder); |
| result = operation.operate(); |
| } catch (const TrapException&) { |
| trapped = true; |
| } catch (const WasmException& e) { |
| std::cout << "[exception thrown: " << e.exn << "]" << std::endl; |
| trapped = true; |
| } |
| if (id == ASSERT_RETURN) { |
| assert(!trapped); |
| Literals expected; |
| if (curr.size() >= 3) { |
| expected = |
| getLiteralsFromConstExpression(builder->parseExpression(*curr[2])); |
| } |
| std::cerr << "seen " << result << ", expected " << expected << '\n'; |
| if (expected != result) { |
| Fatal() << "unexpected, should be identical"; |
| } |
| } |
| if (id == ASSERT_TRAP) { |
| assert(trapped); |
| } |
| } |
| *i += 1; |
| } |
| } |
| |
| // |
| // main |
| // |
| |
| int main(int argc, const char* argv[]) { |
| Name entry; |
| std::set<size_t> skipped; |
| |
| Options options("wasm-shell", "Execute .wast files"); |
| options |
| .add("--entry", |
| "-e", |
| "Call the entry point after parsing the module", |
| Options::Arguments::One, |
| [&entry](Options*, const std::string& argument) { entry = argument; }) |
| .add("--skip", |
| "-s", |
| "Skip input on certain lines (comma-separated-list)", |
| Options::Arguments::One, |
| [&skipped](Options*, const std::string& argument) { |
| size_t i = 0; |
| while (i < argument.size()) { |
| auto ending = argument.find(',', i); |
| if (ending == std::string::npos) { |
| ending = argument.size(); |
| } |
| auto sub = argument.substr(i, ending - i); |
| skipped.insert(atoi(sub.c_str())); |
| i = ending + 1; |
| } |
| }) |
| .add_positional("INFILE", |
| Options::Arguments::One, |
| [](Options* o, const std::string& argument) { |
| o->extra["infile"] = argument; |
| }); |
| options.parse(argc, argv); |
| |
| auto input( |
| read_file<std::vector<char>>(options.extra["infile"], Flags::Text)); |
| |
| bool checked = false; |
| |
| try { |
| if (options.debug) { |
| std::cerr << "parsing text to s-expressions...\n"; |
| } |
| SExpressionParser parser(input.data()); |
| Element& root = *parser.root; |
| |
| // A .wast may have multiple modules, with some asserts after them |
| size_t i = 0; |
| while (i < root.size()) { |
| Element& curr = *root[i]; |
| if (skipped.count(curr.line) > 0) { |
| Colors::green(std::cerr); |
| std::cerr << "SKIPPING [line: " << curr.line << "]\n"; |
| Colors::normal(std::cerr); |
| i++; |
| continue; |
| } |
| IString id = curr[0]->str(); |
| if (id == MODULE) { |
| if (options.debug) { |
| std::cerr << "parsing s-expressions to wasm...\n"; |
| } |
| Colors::green(std::cerr); |
| std::cerr << "BUILDING MODULE [line: " << curr.line << "]\n"; |
| Colors::normal(std::cerr); |
| auto module = wasm::make_unique<Module>(); |
| Name moduleName; |
| auto builder = wasm::make_unique<SExpressionWasmBuilder>( |
| *module, *root[i], &moduleName); |
| builders[moduleName].swap(builder); |
| modules[moduleName].swap(module); |
| i++; |
| modules[moduleName]->features = FeatureSet::All; |
| bool valid = WasmValidator().validate(*modules[moduleName]); |
| if (!valid) { |
| WasmPrinter::printModule(modules[moduleName].get()); |
| } |
| if (!valid) { |
| Fatal() << "Invalid module!"; |
| } |
| run_asserts(moduleName, |
| &i, |
| &checked, |
| modules[moduleName].get(), |
| &root, |
| builders[moduleName].get(), |
| entry); |
| } else { |
| run_asserts(Name(), &i, &checked, nullptr, &root, nullptr, entry); |
| } |
| } |
| } catch (ParseException& p) { |
| p.dump(std::cerr); |
| Fatal() << "parse exception"; |
| } |
| |
| if (checked) { |
| Colors::green(std::cerr); |
| Colors::bold(std::cerr); |
| std::cerr << "all checks passed.\n"; |
| Colors::normal(std::cerr); |
| } |
| } |