diff options
Diffstat (limited to 'src/tools/wasm-shell.cpp')
-rw-r--r-- | src/tools/wasm-shell.cpp | 575 |
1 files changed, 329 insertions, 246 deletions
diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 6d2077e1c..a2d35f444 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -19,7 +19,6 @@ // 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" @@ -41,300 +40,384 @@ Name ASSERT_INVALID("assert_invalid"); Name ASSERT_MALFORMED("assert_malformed"); Name ASSERT_UNLINKABLE("assert_unlinkable"); Name INVOKE("invoke"); +Name REGISTER("register"); Name GET("get"); -// -// An operation on a module -// +struct ShellOptions : public Options { + Name entry; + std::set<size_t> skipped; -struct Operation { - ModuleInstance* instance; - Name operation; - Name name; - LiteralList arguments; - - Operation(Element& element, - ModuleInstance* instanceInit, - SExpressionWasmBuilder& builder, - std::map<Name, std::unique_ptr<ModuleInstance>>& instances) - : 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(getLiteralFromConstExpression(argument)); - } + ShellOptions(const std::string& command, const std::string& description) + : Options(command, description) { + (*this) + .add("--entry", + "-e", + "Call the entry point after parsing the module", + Options::Arguments::One, + [this](Options*, const std::string& argument) { entry = argument; }) + .add("--skip", + "-s", + "Skip input on certain lines (comma-separated-list)", + Options::Arguments::One, + [this](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; + }); } +}; - Literals operate() { - if (operation == INVOKE) { - return instance->callExport(name, arguments); - } else if (operation == GET) { - return {instance->getExport(name)}; - } else { - WASM_UNREACHABLE("unknown operation"); - } +class Shell { +protected: + 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::shared_ptr<ModuleInstance>> instances; + // used for imports + std::map<Name, std::shared_ptr<ModuleInstance>> linkedInstances; + + Name lastModule; + + void instantiate(Module* wasm) { + auto tempInterface = + wasm::make_unique<ShellExternalInterface>(linkedInstances); + auto tempInstance = std::make_shared<ModuleInstance>( + *wasm, tempInterface.get(), linkedInstances); + interfaces[wasm->name].swap(tempInterface); + instances[wasm->name].swap(tempInstance); } -}; -static void -run_asserts(Name moduleName, - size_t* i, - bool* checked, - Module* wasm, - Element* root, - SExpressionWasmBuilder* builder, - Name entry, - std::map<Name, std::unique_ptr<ShellExternalInterface>>& interfaces, - std::map<Name, std::unique_ptr<ModuleInstance>>& instances) { - 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 (const auto& param : function->sig.params) { - arguments.push_back(Literal(param)); - } - try { - instance->callExport(entry, arguments); - } catch (ExitException&) { - } - } + void parse(Element& s) { + IString id = s[0]->str(); + if (id == MODULE) { + parseModule(s); + } else if (id == REGISTER) { + parseRegister(s); + } else if (id == INVOKE) { + parseOperation(s); + } else if (id == ASSERT_RETURN) { + parseAssertReturn(s); + } else if (id == ASSERT_TRAP) { + parseAssertTrap(s); + } else if ((id == ASSERT_INVALID) || (id == ASSERT_MALFORMED)) { + parseModuleAssertion(s); } } - while (*i < root->size()) { - Element& curr = *(*root)[*i]; - IString id = curr[0]->str(); - if (id == MODULE) { - break; + + Module* parseModule(Element& s) { + if (options.debug) { + std::cerr << "parsing s-expressions to wasm...\n"; } - *checked = true; - Colors::red(std::cerr); - std::cerr << *i << '/' << (root->size() - 1); Colors::green(std::cerr); - std::cerr << " CHECKING: "; + std::cerr << "BUILDING MODULE [line: " << s.line << "]\n"; Colors::normal(std::cerr); - std::cerr << curr; + auto module = wasm::make_unique<Module>(); + auto builder = + wasm::make_unique<SExpressionWasmBuilder>(*module, s, IRProfile::Normal); + auto moduleName = module->name; + lastModule = module->name; + builders[moduleName].swap(builder); + modules[moduleName].swap(module); + modules[moduleName]->features = FeatureSet::All; + bool valid = WasmValidator().validate(*modules[moduleName]); + if (!valid) { + std::cout << *modules[moduleName] << '\n'; + Fatal() << "module failed to validate, see above"; + } + + instantiate(modules[moduleName].get()); + + return modules[moduleName].get(); + } + + void parseRegister(Element& s) { + auto instance = instances[lastModule]; + if (!instance) { + Fatal() << "register called without a module"; + } + auto name = s[1]->str(); + linkedInstances[name] = instance; + + // swap to the new name in all maps + modules[name].swap(modules[lastModule]); + modules.erase(lastModule); + builders[name].swap(builders[lastModule]); + builders.erase(lastModule); + interfaces[name].swap(interfaces[lastModule]); + interfaces.erase(lastModule); + instances[name].swap(instances[lastModule]); + instances.erase(lastModule); + Colors::green(std::cerr); - std::cerr << " [line: " << curr.line << "]\n"; + std::cerr << "REGISTER MODULE INSTANCE AS \"" << name.c_str() + << "\" [line: " << s.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], IRProfile::Normal)); - } catch (const ParseException&) { - invalid = true; - } - if (!invalid) { - // maybe parsed ok, but otherwise incorrect - invalid = !WasmValidator().validate(wasm); + } + + Literals parseOperation(Element& s) { + Index i = 1; + Name moduleName = lastModule; + if (s[i]->dollared()) { + moduleName = s[i++]->str(); + } + ModuleInstance* instance = instances[moduleName].get(); + assert(instance); + + Name base = s[i++]->str(); + + if (s[0]->str() == INVOKE) { + LiteralList args; + while (i < s.size()) { + Expression* argument = builders[moduleName]->parseExpression(*s[i++]); + args.push_back(getLiteralFromConstExpression(argument)); } - if (!invalid && id == ASSERT_UNLINKABLE) { - // validate "instantiating" the mdoule - auto reportUnknownImport = [&](Importable* import) { + + return instance->callExport(base, args); + } else if (s[0]->str() == GET) { + return instance->getExport(base); + } + + Fatal() << "Invalid operation " << s[0]->c_str(); + } + + void parseAssertTrap(Element& s) { + bool trapped = false; + auto& inner = *s[1]; + if (inner[0]->str() == MODULE) { + return parseModuleAssertion(s); + } + + try { + parseOperation(inner); + } catch (const TrapException&) { + trapped = true; + } catch (const WasmException& e) { + std::cout << "[exception thrown: " << e << "]" << std::endl; + trapped = true; + } + + assert(trapped); + } + + void parseAssertReturn(Element& s) { + Literals actual; + Literals expected; + if (s.size() >= 3) { + expected = getLiteralsFromConstExpression( + builders[lastModule]->parseExpression(*s[2])); + } + bool trapped = false; + try { + actual = parseOperation(*s[1]); + } catch (const TrapException&) { + trapped = true; + } catch (const WasmException& e) { + std::cout << "[exception thrown: " << e << "]" << std::endl; + trapped = true; + } + assert(!trapped); + std::cerr << "seen " << actual << ", expected " << expected << '\n'; + if (expected != actual) { + Fatal() << "unexpected, should be identical\n"; + } + } + + void parseModuleAssertion(Element& s) { + Module wasm; + wasm.features = FeatureSet::All; + std::unique_ptr<SExpressionWasmBuilder> builder; + auto id = s[0]->str(); + + bool invalid = false; + try { + SExpressionWasmBuilder(wasm, *s[1], IRProfile::Normal); + } 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) { + auto it = linkedInstances.find(import->module); + if (it == linkedInstances.end() || + it->second->wasm.getExportOrNull(import->base) == nullptr) { std::cerr << "unknown import: " << import->module << '.' << import->base << '\n'; invalid = true; - }; - ModuleUtils::iterImportedGlobals(wasm, reportUnknownImport); - ModuleUtils::iterImportedTables(wasm, reportUnknownImport); - ModuleUtils::iterImportedFunctions(wasm, [&](Importable* import) { - if (import->module == SPECTEST && import->base.startsWith(PRINT)) { - // We can handle it. - } else { - reportUnknownImport(import); - } - }); - ElementUtils::iterAllElementFunctionNames(&wasm, [&](Name name) { - // 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; - } + } + }; + ModuleUtils::iterImportedGlobals(wasm, reportUnknownImport); + ModuleUtils::iterImportedTables(wasm, reportUnknownImport); + ModuleUtils::iterImportedFunctions(wasm, [&](Importable* import) { + if (import->module == SPECTEST && import->base.startsWith(PRINT)) { + // We can handle it. + } else { + reportUnknownImport(import); + } + }); + ElementUtils::iterAllElementFunctionNames(&wasm, [&](Name name) { + // 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 (wasm.memory.imported()) { - reportUnknownImport(&wasm.memory); } + }); + if (wasm.memory.imported()) { + reportUnknownImport(&wasm.memory); } - if (!invalid) { - Colors::red(std::cerr); - std::cerr << "[should have been invalid]\n"; - Colors::normal(std::cerr); - Fatal() << &wasm << '\n'; - } - } else if (id == INVOKE) { - assert(wasm); - Operation operation(curr, instance, *builder, instances); - operation.operate(); - } else if (wasm) { // if no wasm, we skipped the module - // an invoke test - bool trapped = false; - WASM_UNUSED(trapped); - Literals result; + } + + if (!invalid && id == ASSERT_TRAP) { try { - Operation operation(*curr[1], instance, *builder, instances); - result = operation.operate(); + instantiate(&wasm); } catch (const TrapException&) { - trapped = true; + invalid = true; } catch (const WasmException& e) { std::cout << "[exception thrown: " << e << "]" << 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\n"; - } - } - if (id == ASSERT_TRAP) { - assert(trapped); + invalid = true; } } - *i += 1; - } -} -// -// main -// + if (!invalid) { + Colors::red(std::cerr); + std::cerr << "[should have been invalid]\n"; + Colors::normal(std::cerr); + Fatal() << &wasm << '\n'; + } + } -int main(int argc, const char* argv[]) { - Name entry; - std::set<size_t> skipped; +protected: + ShellOptions& options; - 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); + // spectest module is a default host-provided module defined by the spec's + // reference interpreter. It's been replaced by the `(register ...)` + // mechanism in the recent spec tests, and is kept for legacy tests only. + // + // TODO: spectest module is considered deprecated by the spec. Remove when + // is actually removed from the spec test. + void buildSpectestModule() { + auto spectest = std::make_unique<Module>(); + spectest->name = "spectest"; + Builder builder(*spectest); - auto input( - read_file<std::vector<char>>(options.extra["infile"], Flags::Text)); + spectest->addGlobal(builder.makeGlobal(Name::fromInt(0), + Type::i32, + builder.makeConst<uint32_t>(666), + Builder::Immutable)); + spectest->addGlobal(builder.makeGlobal(Name::fromInt(1), + Type::i64, + builder.makeConst<uint64_t>(666), + Builder::Immutable)); + spectest->addGlobal(builder.makeGlobal(Name::fromInt(2), + Type::f32, + builder.makeConst<float>(666.6), + Builder::Immutable)); + spectest->addGlobal(builder.makeGlobal(Name::fromInt(3), + Type::f64, + builder.makeConst<double>(666.6), + Builder::Immutable)); + spectest->addExport( + builder.makeExport("global_i32", Name::fromInt(0), ExternalKind::Global)); + spectest->addExport( + builder.makeExport("global_i64", Name::fromInt(1), ExternalKind::Global)); + spectest->addExport( + builder.makeExport("global_f32", Name::fromInt(2), ExternalKind::Global)); + spectest->addExport( + builder.makeExport("global_f64", Name::fromInt(3), ExternalKind::Global)); - bool checked = false; + spectest->addTable( + builder.makeTable(Name::fromInt(0), Type::funcref, 10, 20)); + spectest->addExport( + builder.makeExport("table", Name::fromInt(0), ExternalKind::Table)); - // Modules named in the file + spectest->memory.exists = true; + spectest->memory.initial = 1; + spectest->memory.max = 2; + spectest->addExport(builder.makeExport( + "memory", spectest->memory.name, ExternalKind::Memory)); - 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; + modules["spectest"].swap(spectest); + modules["spectest"]->features = FeatureSet::All; + instantiate(modules["spectest"].get()); + linkedInstances["spectest"] = instances["spectest"]; + // print_* functions are handled separately, no need to define here. + } - try { - if (options.debug) { - std::cerr << "parsing text to s-expressions...\n"; - } - SExpressionParser parser(input.data()); - Element& root = *parser.root; +public: + Shell(ShellOptions& options) : options(options) { buildSpectestModule(); } - // A .wast may have multiple modules, with some asserts after them + bool parseAndRun(Element& root) { size_t i = 0; while (i < root.size()) { Element& curr = *root[i]; - if (skipped.count(curr.line) > 0) { + + if (options.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"; - } + + if (curr[0]->str() != MODULE) { + Colors::red(std::cerr); + std::cerr << i << '/' << (root.size() - 1); Colors::green(std::cerr); - std::cerr << "BUILDING MODULE [line: " << curr.line << "]\n"; + std::cerr << " CHECKING: "; + Colors::normal(std::cerr); + std::cerr << curr; + Colors::green(std::cerr); + std::cerr << " [line: " << curr.line << "]\n"; Colors::normal(std::cerr); - auto module = wasm::make_unique<Module>(); - auto builder = wasm::make_unique<SExpressionWasmBuilder>( - *module, *root[i], IRProfile::Normal); - auto moduleName = module->name; - builders[moduleName].swap(builder); - modules[moduleName].swap(module); - i++; - modules[moduleName]->features = FeatureSet::All; - bool valid = WasmValidator().validate(*modules[moduleName]); - if (!valid) { - std::cout << *modules[moduleName] << '\n'; - Fatal() << "module failed to validate, see above"; - } - run_asserts(moduleName, - &i, - &checked, - modules[moduleName].get(), - &root, - builders[moduleName].get(), - entry, - interfaces, - instances); - } else { - run_asserts(Name(), - &i, - &checked, - nullptr, - &root, - nullptr, - entry, - interfaces, - instances); } + + parse(curr); + + i += 1; } + + return false; + } +}; + +int main(int argc, const char* argv[]) { + Name entry; + std::set<size_t> skipped; + + ShellOptions options("wasm-shell", "Execute .wast files"); + 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; + checked = Shell(options).parseAndRun(root); } catch (ParseException& p) { p.dump(std::cerr); exit(1); |