/* * 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 #include "execution-results.h" #include "ir/element-utils.h" #include "parser/lexer.h" #include "parser/wat-parser.h" #include "pass.h" #include "shell-interface.h" #include "support/command-line.h" #include "support/file.h" #include "support/result.h" #include "wasm-binary.h" #include "wasm-interpreter.h" #include "wasm-validator.h" using namespace wasm; using namespace wasm::WATParser; struct Shell { std::map> modules; std::map> interfaces; std::map> instances; // used for imports std::map> linkedInstances; Name lastModule; Options& options; Shell(Options& options) : options(options) { buildSpectestModule(); } Result<> run(WASTScript& script) { size_t i = 0; for (auto& entry : script) { Colors::red(std::cerr); std::cerr << i++ << ' '; Colors::normal(std::cerr); if (std::get_if(&entry.cmd)) { Colors::green(std::cerr); std::cerr << "BUILDING MODULE [line: " << entry.line << "]\n"; Colors::normal(std::cerr); } else if (auto* reg = std::get_if(&entry.cmd)) { Colors::green(std::cerr); std::cerr << "REGISTER MODULE INSTANCE AS \"" << reg->name << "\" [line: " << entry.line << "]\n"; Colors::normal(std::cerr); } else { Colors::green(std::cerr); std::cerr << "CHECKING [line: " << entry.line << "]\n"; Colors::normal(std::cerr); } CHECK_ERR(runCommand(entry.cmd)); } return Ok{}; } Result<> runCommand(WASTCommand& cmd) { if (auto* mod = std::get_if(&cmd)) { return addModule(*mod); } else if (auto* reg = std::get_if(&cmd)) { return addRegistration(*reg); } else if (auto* act = std::get_if(&cmd)) { doAction(*act); return Ok{}; } else if (auto* assn = std::get_if(&cmd)) { return doAssertion(*assn); } else { WASM_UNREACHABLE("unexpected command"); } } Result> makeModule(WASTModule& mod) { std::shared_ptr wasm; if (auto* quoted = std::get_if(&mod)) { wasm = std::make_shared(); switch (quoted->type) { case QuotedModuleType::Text: { CHECK_ERR(parseModule(*wasm, quoted->module)); break; } case QuotedModuleType::Binary: { std::vector buffer(quoted->module.begin(), quoted->module.end()); WasmBinaryReader reader(*wasm, FeatureSet::All, buffer); try { reader.read(); } catch (ParseException& p) { std::stringstream ss; p.dump(ss); return Err{ss.str()}; } break; } } } else if (auto* ptr = std::get_if>(&mod)) { wasm = *ptr; } else { WASM_UNREACHABLE("unexpected module kind"); } wasm->features = FeatureSet::All; return wasm; } Result<> validateModule(Module& wasm) { if (!WasmValidator().validate(wasm)) { return Err{"failed validation"}; } return Ok{}; } using InstanceInfo = std::pair, std::shared_ptr>; Result instantiate(Module& wasm) { try { auto interface = std::make_shared(linkedInstances); auto instance = std::make_shared(wasm, interface.get(), linkedInstances); return {{std::move(interface), std::move(instance)}}; } catch (...) { return Err{"failed to instantiate module"}; } } Result<> addModule(WASTModule& mod) { auto module = makeModule(mod); CHECK_ERR(module); auto wasm = *module; CHECK_ERR(validateModule(*wasm)); auto instanceInfo = instantiate(*wasm); CHECK_ERR(instanceInfo); auto& [interface, instance] = *instanceInfo; lastModule = wasm->name; modules[lastModule] = std::move(wasm); interfaces[lastModule] = std::move(interface); instances[lastModule] = std::move(instance); return Ok{}; } Result<> addRegistration(Register& reg) { auto instance = instances[lastModule]; if (!instance) { return Err{"register called without a module"}; } linkedInstances[reg.name] = instance; // We copy pointers as a registered module's name might still be used // in an assertion or invoke command. modules[reg.name] = modules[lastModule]; interfaces[reg.name] = interfaces[lastModule]; instances[reg.name] = instances[lastModule]; return Ok{}; } struct TrapResult {}; struct HostLimitResult {}; struct ExceptionResult {}; using ActionResult = std::variant; std::string resultToString(ActionResult& result) { if (std::get_if(&result)) { return "trap"; } else if (std::get_if(&result)) { return "exceeded host limit"; } else if (std::get_if(&result)) { return "exception"; } else if (auto* vals = std::get_if(&result)) { std::stringstream ss; ss << *vals; return ss.str(); } else { WASM_UNREACHABLE("unexpected result"); } } ActionResult doAction(Action& act) { assert(instances[lastModule].get()); if (auto* invoke = std::get_if(&act)) { auto it = instances.find(invoke->base ? *invoke->base : lastModule); if (it == instances.end()) { return TrapResult{}; } auto& instance = it->second; try { return instance->callExport(invoke->name, invoke->args); } catch (TrapException&) { return TrapResult{}; } catch (HostLimitException&) { return HostLimitResult{}; } catch (WasmException&) { return ExceptionResult{}; } catch (...) { WASM_UNREACHABLE("unexpected error"); } } else if (auto* get = std::get_if(&act)) { auto it = instances.find(get->base ? *get->base : lastModule); if (it == instances.end()) { return TrapResult{}; } auto& instance = it->second; try { return instance->getExport(get->name); } catch (TrapException&) { return TrapResult{}; } catch (...) { WASM_UNREACHABLE("unexpected error"); } } else { WASM_UNREACHABLE("unexpected action"); } } Result<> doAssertion(Assertion& assn) { if (auto* ret = std::get_if(&assn)) { return assertReturn(*ret); } else if (auto* act = std::get_if(&assn)) { return assertAction(*act); } else if (auto* mod = std::get_if(&assn)) { return assertModule(*mod); } else { WASM_UNREACHABLE("unexpected assertion"); } } Result<> checkNaN(Literal val, NaNResult nan) { std::stringstream err; switch (nan.kind) { case NaNKind::Canonical: if (val.type != nan.type || !val.isCanonicalNaN()) { err << "expected canonical " << nan.type << " NaN, got " << val; return Err{err.str()}; } break; case NaNKind::Arithmetic: if (val.type != nan.type || !val.isArithmeticNaN()) { err << "expected arithmetic " << nan.type << " NaN, got " << val; return Err{err.str()}; } break; } return Ok{}; } Result<> checkLane(Literal val, LaneResult expected, Index index) { std::stringstream err; if (auto* e = std::get_if(&expected)) { if (*e != val) { err << "expected " << *e << ", got " << val << " at lane " << index; return Err{err.str()}; } } else if (auto* nan = std::get_if(&expected)) { auto check = checkNaN(val, *nan); if (auto* e = check.getErr()) { err << e->msg << " at lane " << index; return Err{err.str()}; } } else { WASM_UNREACHABLE("unexpected lane expectation"); } return Ok{}; } Result<> assertReturn(AssertReturn& assn) { std::stringstream err; auto result = doAction(assn.action); auto* values = std::get_if(&result); if (!values) { return Err{std::string("expected return, got ") + resultToString(result)}; } if (values->size() != assn.expected.size()) { err << "expected " << assn.expected.size() << " values, got " << resultToString(result); return Err{err.str()}; } for (Index i = 0; i < values->size(); ++i) { auto atIndex = [&]() { if (values->size() <= 1) { return std::string{}; } std::stringstream ss; ss << " at index " << i; return ss.str(); }; Literal val = (*values)[i]; auto& expected = assn.expected[i]; if (auto* v = std::get_if(&expected)) { if (val != *v) { err << "expected " << *v << ", got " << val << atIndex(); return Err{err.str()}; } } else if (auto* ref = std::get_if(&expected)) { if (!val.type.isRef() || !HeapType::isSubType(val.type.getHeapType(), ref->type)) { err << "expected " << ref->type << " reference, got " << val << atIndex(); return Err{err.str()}; } } else if (auto* nan = std::get_if(&expected)) { auto check = checkNaN(val, *nan); if (auto* e = check.getErr()) { err << e->msg << atIndex(); return Err{err.str()}; } } else if (auto* lanes = std::get_if(&expected)) { switch (lanes->size()) { case 4: { auto vals = val.getLanesF32x4(); for (Index i = 0; i < 4; ++i) { auto check = checkLane(vals[i], (*lanes)[i], i); if (auto* e = check.getErr()) { err << e->msg << atIndex(); return Err{err.str()}; } } break; } case 2: { auto vals = val.getLanesF64x2(); for (Index i = 0; i < 2; ++i) { auto check = checkLane(vals[i], (*lanes)[i], i); if (auto* e = check.getErr()) { err << e->msg << atIndex(); return Err{err.str()}; } } break; } default: WASM_UNREACHABLE("unexpected number of lanes"); } } else { WASM_UNREACHABLE("unexpected expectation"); } } return Ok{}; } Result<> assertAction(AssertAction& assn) { std::stringstream err; auto result = doAction(assn.action); switch (assn.type) { case ActionAssertionType::Trap: if (std::get_if(&result)) { return Ok{}; } err << "expected trap"; break; case ActionAssertionType::Exhaustion: if (std::get_if(&result)) { return Ok{}; } err << "expected exhaustion"; break; case ActionAssertionType::Exception: if (std::get_if(&result)) { return Ok{}; } err << "expected exception"; break; } err << ", got " << resultToString(result); return Err{err.str()}; } Result<> assertModule(AssertModule& assn) { auto wasm = makeModule(assn.wasm); if (const auto* err = wasm.getErr()) { if (assn.type == ModuleAssertionType::Malformed || assn.type == ModuleAssertionType::Invalid) { return Ok{}; } return Err{err->msg}; } if (assn.type == ModuleAssertionType::Malformed) { return Err{"expected malformed module"}; } auto valid = validateModule(**wasm); if (auto* err = valid.getErr()) { if (assn.type == ModuleAssertionType::Invalid) { return Ok{}; } return Err{err->msg}; } if (assn.type == ModuleAssertionType::Invalid) { return Err{"expected invalid module"}; } auto instance = instantiate(**wasm); if (auto* err = instance.getErr()) { if (assn.type == ModuleAssertionType::Unlinkable || assn.type == ModuleAssertionType::Trap) { return Ok{}; } return Err{err->msg}; } if (assn.type == ModuleAssertionType::Unlinkable) { return Err{"expected unlinkable module"}; } if (assn.type == ModuleAssertionType::Trap) { return Err{"expected instantiation to trap"}; } WASM_UNREACHABLE("unexpected module assertion"); } // 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_shared(); spectest->features = FeatureSet::All; Builder builder(*spectest); spectest->addGlobal(builder.makeGlobal(Name::fromInt(0), Type::i32, builder.makeConst(666), Builder::Immutable)); spectest->addGlobal(builder.makeGlobal(Name::fromInt(1), Type::i64, builder.makeConst(666), Builder::Immutable)); spectest->addGlobal(builder.makeGlobal(Name::fromInt(2), Type::f32, builder.makeConst(666.6f), Builder::Immutable)); spectest->addGlobal(builder.makeGlobal(Name::fromInt(3), Type::f64, builder.makeConst(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)); spectest->addTable(builder.makeTable( Name::fromInt(0), Type(HeapType::func, Nullable), 10, 20)); spectest->addExport( builder.makeExport("table", Name::fromInt(0), ExternalKind::Table)); spectest->addTable(builder.makeTable( Name::fromInt(1), Type(HeapType::func, Nullable), 10, 20, Type::i64)); spectest->addExport( builder.makeExport("table64", Name::fromInt(1), ExternalKind::Table)); Memory* memory = spectest->addMemory(builder.makeMemory(Name::fromInt(0), 1, 2)); spectest->addExport( builder.makeExport("memory", memory->name, ExternalKind::Memory)); // print_* functions are handled separately, no need to define here. WASTModule mod = std::move(spectest); auto added = addModule(mod); if (added.getErr()) { WASM_UNREACHABLE("error building spectest module"); } Register registration{"spectest"}; auto registered = addRegistration(registration); if (registered.getErr()) { WASM_UNREACHABLE("error registering spectest module"); } } }; int main(int argc, const char* argv[]) { Name entry; std::set skipped; // Read stdin by default. std::string infile = "-"; Options options("wasm-shell", "Execute .wast files"); options.add_positional( "INFILE", Options::Arguments::One, [&](Options* o, const std::string& argument) { infile = argument; }); options.parse(argc, argv); auto input = read_file(infile, Flags::Text); // Check that we can parse the script correctly with the new parser. auto script = WATParser::parseScript(input); if (auto* err = script.getErr()) { std::cerr << err->msg << '\n'; exit(1); } Lexer lexer(input); auto result = Shell(options).run(*script); if (auto* err = result.getErr()) { std::cerr << err->msg << '\n'; exit(1); } Colors::green(std::cerr); Colors::bold(std::cerr); std::cerr << "all checks passed.\n"; Colors::normal(std::cerr); }