blob: bd8d987bda3cb779718d8201a747a30dfb4cf1ae [file] [log] [blame]
/*
* 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 "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"),
ASSERT_TRAP("assert_trap"),
ASSERT_INVALID("assert_invalid"),
ASSERT_MALFORMED("assert_malformed"),
ASSERT_UNLINKABLE("assert_unlinkable"),
INVOKE("invoke"),
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(argument->dynCast<Const>()->value);
}
}
Literal operate() {
if (operation == INVOKE) {
return instance->callExport(name, arguments);
} else if (operation == GET) {
return instance->getExport(name);
} else {
Fatal() << "unknown operation: " << operation << '\n';
WASM_UNREACHABLE();
}
}
};
static void verify_result(Literal a, Literal b) {
if (a == b) return;
// accept equal nans if equal in all bits
assert(a.type == b.type);
if (a.type == f32) {
assert(a.reinterpreti32() == b.reinterpreti32());
} else if (a.type == f64) {
assert(a.reinterpreti64() == b.reinterpreti64());
} else {
abort();
}
}
static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm,
Element* root,
SExpressionWasmBuilder* builder,
Name entry) {
ModuleInstance* instance = nullptr;
if (wasm) {
auto tempInterface = wasm::make_unique<ShellExternalInterface>(); // prefix make_unique to work around visual studio bugs
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 (WasmType param : function->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;
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
for (auto& import : wasm.imports) {
if (import->module == SPECTEST && import->base == PRINT) {
if (import->kind != ExternalKind::Function) {
std::cerr << "spectest.print should be a function, but is " << int32_t(import->kind) << '\n';
invalid = true;
break;
}
} else {
std::cerr << "unknown import: " << import->module << '.' << import->base << '\n';
invalid = true;
break;
}
}
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.checkImport(name)) {
if (import->module == SPECTEST && import->base == PRINT) {
std::cerr << "cannot put spectest.print in table\n";
invalid = true;
}
}
}
}
}
if (!invalid) {
Colors::red(std::cerr);
std::cerr << "[should have been invalid]\n";
Colors::normal(std::cerr);
std::cerr << &wasm << '\n';
abort();
}
} 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);
Literal result;
try {
Operation operation(*curr[1], instance, *builder);
result = operation.operate();
} catch (const TrapException&) {
trapped = true;
}
if (id == ASSERT_RETURN) {
assert(!trapped);
if (curr.size() >= 3) {
Literal expected = builder
->parseExpression(*curr[2])
->dynCast<Const>()
->value;
std::cerr << "seen " << result << ", expected " << expected << '\n';
verify_result(expected, result);
} else {
Literal expected;
std::cerr << "seen " << result << ", expected " << expected << '\n';
verify_result(expected, result);
}
}
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, options.debug ? Flags::Debug : Flags::Release));
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++;
assert(WasmValidator().validate(*modules[moduleName]));
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);
abort();
}
if (checked) {
Colors::green(std::cerr);
Colors::bold(std::cerr);
std::cerr << "all checks passed.\n";
Colors::normal(std::cerr);
}
}