summaryrefslogtreecommitdiff
path: root/src/tools/wasm-shell.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/wasm-shell.cpp')
-rw-r--r--src/tools/wasm-shell.cpp575
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);