diff options
author | Ben Smith <binji@chromium.org> | 2020-02-21 14:11:44 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-21 14:11:44 -0800 |
commit | 24a12ecfdde0246527506c87bd82460beb761c57 (patch) | |
tree | 242550e84d516926a71bef00342f4f57d011d4f6 /src/tools | |
parent | bb4b65186668ff3ebd2f088778337608969a9567 (diff) | |
download | wabt-24a12ecfdde0246527506c87bd82460beb761c57.tar.gz wabt-24a12ecfdde0246527506c87bd82460beb761c57.tar.bz2 wabt-24a12ecfdde0246527506c87bd82460beb761c57.zip |
New interpreter (#1330)
It's modeled closely on the wasm-c-api. All runtime objects are
garbage-collected. The GC'd objects are subclasses of `Object` and all
stored in the `Store`. GC roots can be created using a RefPtr<T> struct.
The `Module` stores no runtime objects, only description structs (named
`*Desc`, e.g. `FuncDesc`). Instantiation takes these descriptors and
turns them into runtime objects (e.g. `FuncDesc` is used to create a
`Func`). Only import-matching occurs during instantiation; all other
validation is assumed to have occurred during Module compilation.
As with the previous interpreter, the wasm instructions are not executed
directly, and instead compiled to an instruction stream that is easier
to interpret (e.g. all branches become gotos). It's still a stack
machine, though.
The previous interpreter would always compile and instantiate in one
step, which means that it was always known whether an imported function
is a host function or wasm function. As a result, calls between modules
that stayed in wasm could be very cheap (just update PC). Now that the
module is compiled before instantiation, an imported function call
always has to check first, which may be a slight performance hit.
One major difference to the structure of the interpreter is that
floating-point values are passed directly, instead of using their
equivalent bit pattern. This is more convenient in general, but requires
annotating all functions with WABT_VECTORCALL so Visual Studio x86 works
properly (otherwise floats are passed in the x87 FP stack).
Instruction stream decoding/tracing/disassembling is now all handled in
the `Istream` class. This reduces a lot of duplication between the
three, which makes the logging-all-opcodes and tracing-all-opcodes less
valuable, so I've removed them.
Here are the changes to files:
binary-reader-metadata.{h,cc} -> [removed]
interp-{disassemble.trace}.cc -> istream.{h,cc}
There are new helper files:
interp-util.{h,cc}: Primarily print debugging functions
interp-math.h: Templates used for handling wasm math
Diffstat (limited to 'src/tools')
-rw-r--r-- | src/tools/spectest-interp.cc | 567 | ||||
-rw-r--r-- | src/tools/wasm-interp.cc | 183 |
2 files changed, 404 insertions, 346 deletions
diff --git a/src/tools/spectest-interp.cc b/src/tools/spectest-interp.cc index 047a3dc1..b0837ca7 100644 --- a/src/tools/spectest-interp.cc +++ b/src/tools/spectest-interp.cc @@ -19,6 +19,7 @@ #include <cinttypes> #include <cstdio> #include <cstdlib> +#include <map> #include <memory> #include <string> #include <vector> @@ -29,6 +30,7 @@ #include "src/error-formatter.h" #include "src/feature.h" #include "src/interp/binary-reader-interp.h" +#include "src/interp/interp-util.h" #include "src/interp/interp.h" #include "src/literal.h" #include "src/option-parser.h" @@ -139,7 +141,8 @@ class Action { ActionType type = ActionType::Invoke; std::string module_name; std::string field_name; - TypedValues args; + ValueTypes types; + Values args; }; template <CommandType TypeEnum> @@ -222,9 +225,9 @@ class JSONParser { wabt::Result ParseTypeVector(TypeVector* out_types); wabt::Result ParseConst(TypedValue* out_value); wabt::Result ParseConstValue(TypedValue* out_value, - string_view type_str, - string_view value_str); - wabt::Result ParseConstVector(TypedValues* out_values); + string_view type_str, + string_view value_str); + wabt::Result ParseConstVector(ValueTypes* out_types, Values* out_values); wabt::Result ParseExpectedValue(ExpectedValue* out_value); wabt::Result ParseExpectedValues(std::vector<ExpectedValue>* out_values); wabt::Result ParseAction(Action* out_action); @@ -500,8 +503,8 @@ wabt::Result JSONParser::ParseConst(TypedValue* out_value) { } wabt::Result JSONParser::ParseConstValue(TypedValue* out_value, - string_view type_str, - string_view value_str) { + string_view type_str, + string_view value_str) { const char* value_start = value_str.data(); const char* value_end = value_str.data() + value_str.size(); if (type_str == "i32") { @@ -511,8 +514,8 @@ wabt::Result JSONParser::ParseConstValue(TypedValue* out_value, PrintError("invalid i32 literal"); return wabt::Result::Error; } - out_value->type = Type::I32; - out_value->value.i32 = value; + out_value->type = ValueType::I32; + out_value->value.Set(value); } else if (type_str == "f32") { uint32_t value_bits; if (Failed(ParseInt32(value_start, value_end, &value_bits, @@ -520,8 +523,8 @@ wabt::Result JSONParser::ParseConstValue(TypedValue* out_value, PrintError("invalid f32 literal"); return wabt::Result::Error; } - out_value->type = Type::F32; - out_value->value.f32_bits = value_bits; + out_value->type = ValueType::F32; + out_value->value.Set(Bitcast<f32>(value_bits)); } else if (type_str == "i64") { uint64_t value; if (Failed(ParseInt64(value_start, value_end, &value, @@ -529,8 +532,8 @@ wabt::Result JSONParser::ParseConstValue(TypedValue* out_value, PrintError("invalid i64 literal"); return wabt::Result::Error; } - out_value->type = Type::I64; - out_value->value.i64 = value; + out_value->type = ValueType::I64; + out_value->value.Set(value); } else if (type_str == "f64") { uint64_t value_bits; if (Failed((ParseInt64(value_start, value_end, &value_bits, @@ -538,19 +541,19 @@ wabt::Result JSONParser::ParseConstValue(TypedValue* out_value, PrintError("invalid f64 literal"); return wabt::Result::Error; } - out_value->type = Type::F64; - out_value->value.f64_bits = value_bits; + out_value->type = ValueType::F64; + out_value->value.Set(Bitcast<f64>(value_bits)); } else if (type_str == "v128") { v128 value_bits; if (Failed(ParseUint128(value_start, value_end, &value_bits))) { PrintError("invalid v128 literal"); return wabt::Result::Error; } - out_value->type = Type::V128; - out_value->value.vec128 = value_bits; + out_value->type = ValueType::V128; + out_value->value.Set(value_bits); } else if (type_str == "nullref") { - out_value->type = Type::Nullref; - out_value->value.ref = {RefType::Null, 0}; + out_value->type = ValueType::Nullref; + out_value->value.Set(Ref::Null); } else if (type_str == "hostref") { uint32_t value; if (Failed(ParseInt32(value_start, value_end, &value, @@ -558,16 +561,19 @@ wabt::Result JSONParser::ParseConstValue(TypedValue* out_value, PrintError("invalid hostref literal"); return wabt::Result::Error; } - out_value->type = Type::Hostref; - out_value->value.ref = {RefType::Host, value}; + out_value->type = ValueType::Hostref; + // TODO: hack, just whatever ref is at this index; but skip null (which is + // always 0). + out_value->value.Set(Ref{value + 1}); } else if (type_str == "funcref") { uint32_t value; - if (Failed(ParseInt32(value_start, value_end, &value, ParseIntType::UnsignedOnly))) { + if (Failed(ParseInt32(value_start, value_end, &value, + ParseIntType::UnsignedOnly))) { PrintError("invalid funcref literal"); return wabt::Result::Error; } - out_value->type = Type::Funcref; - out_value->value.ref = {RefType::Func, value}; + out_value->type = ValueType::Funcref; + out_value->value.Set(Ref{value}); } else { PrintError("unknown concrete type: \"%s\"", type_str.to_string().c_str()); return wabt::Result::Error; @@ -586,13 +592,12 @@ wabt::Result JSONParser::ParseExpectedValue(ExpectedValue* out_value) { EXPECT("}"); if (type_str == "f32" || type_str == "f64") { + out_value->value.type = type_str == "f32" ? ValueType::F32 : ValueType::F64; if (value_str == "nan:canonical") { - out_value->value.type = type_str == "f32" ? Type::F32 : Type::F64; out_value->is_expected_nan = true; out_value->expectedNan = ExpectedNan::Canonical; return wabt::Result::Ok; } else if (value_str == "nan:arithmetic") { - out_value->value.type = type_str == "f32" ? Type::F32 : Type::F64; out_value->is_expected_nan = true; out_value->expectedNan = ExpectedNan::Arithmetic; return wabt::Result::Ok; @@ -603,7 +608,8 @@ wabt::Result JSONParser::ParseExpectedValue(ExpectedValue* out_value) { return ParseConstValue(&out_value->value, type_str, value_str); } -wabt::Result JSONParser::ParseExpectedValues(std::vector<ExpectedValue>* out_values) { +wabt::Result JSONParser::ParseExpectedValues( + std::vector<ExpectedValue>* out_values) { out_values->clear(); EXPECT("["); bool first = true; @@ -619,7 +625,7 @@ wabt::Result JSONParser::ParseExpectedValues(std::vector<ExpectedValue>* out_val return wabt::Result::Ok; } -wabt::Result JSONParser::ParseConstVector(TypedValues* out_values) { +wabt::Result JSONParser::ParseConstVector(ValueTypes* out_types, Values* out_values) { out_values->clear(); EXPECT("["); bool first = true; @@ -627,9 +633,10 @@ wabt::Result JSONParser::ParseConstVector(TypedValues* out_values) { if (!first) { EXPECT(","); } - TypedValue value; - CHECK_RESULT(ParseConst(&value)); - out_values->push_back(value); + TypedValue tv; + CHECK_RESULT(ParseConst(&tv)); + out_types->push_back(tv.type); + out_values->push_back(tv.value); first = false; } return wabt::Result::Ok; @@ -655,7 +662,7 @@ wabt::Result JSONParser::ParseAction(Action* out_action) { if (out_action->type == ActionType::Invoke) { EXPECT(","); EXPECT_KEY("args"); - CHECK_RESULT(ParseConstVector(&out_action->args)); + CHECK_RESULT(ParseConstVector(&out_action->types, &out_action->args)); } EXPECT("}"); return wabt::Result::Ok; @@ -859,21 +866,34 @@ wabt::Result JSONParser::ParseScript(Script* out_script) { return wabt::Result::Ok; } +struct ActionResult { + ValueTypes types; + Values values; + Trap::Ptr trap; +}; + class CommandRunner { public: CommandRunner(); - wabt::Result Run(const Script& script); int passed() const { return passed_; } int total() const { return total_; } private: + using ExportMap = std::map<std::string, Extern::Ptr>; + using Registry = std::map<std::string, ExportMap>; + void WABT_PRINTF_FORMAT(3, 4) PrintError(uint32_t line_number, const char* format, ...); - ExecResult RunAction(int line_number, - const Action* action, - RunVerbosity verbose); + ActionResult RunAction(int line_number, + const Action* action, + RunVerbosity verbose); + + interp::Module::Ptr ReadModule(string_view module_filename, Errors* errors); + Extern::Ptr GetImport(const std::string&, const std::string&); + void PopulateImports(const interp::Module::Ptr&, RefVec*); + void PopulateExports(const Instance::Ptr&, ExportMap*); wabt::Result OnModuleCommand(const ModuleCommand*); wabt::Result OnActionCommand(const ActionCommand*); @@ -890,61 +910,67 @@ class CommandRunner { void TallyCommand(wabt::Result); wabt::Result ReadInvalidTextModule(string_view module_filename, - Environment* env, const std::string& header); wabt::Result ReadInvalidModule(int line_number, - string_view module_filename, - Environment* env, - ModuleType module_type, - const char* desc); + string_view module_filename, + ModuleType module_type, + const char* desc); wabt::Result ReadUnlinkableModule(int line_number, - string_view module_filename, - Environment* env, - ModuleType module_type, - const char* desc); - - Environment env_; - Executor executor_; - DefinedModule* last_module_ = nullptr; + string_view module_filename, + ModuleType module_type, + const char* desc); + + Store store_; + Registry registry_; // Used when importing. + Registry instances_; // Used when referencing module by name in invoke. + ExportMap last_instance_; int passed_ = 0; int total_ = 0; std::string source_filename_; }; -static interp::Result PrintCallback(const HostFunc* func, - const interp::FuncSignature* sig, - const TypedValues& args, - TypedValues& results) { - printf("called host "); - WriteCall(s_stdout_stream.get(), func->module_name, func->field_name, args, - results, interp::ResultType::Ok); - return interp::ResultType::Ok; -} +CommandRunner::CommandRunner() : store_(s_features) { + auto&& spectest = registry_["spectest"]; + + // Initialize print functions for the spec test. + struct { + const char* name; + interp::FuncType type; + } const print_funcs[] = { + {"print", interp::FuncType{{}, {}}}, + {"print_i32", interp::FuncType{{ValueType::I32}, {}}}, + {"print_f32", interp::FuncType{{ValueType::F32}, {}}}, + {"print_f64", interp::FuncType{{ValueType::F64}, {}}}, + {"print_i32_f32", interp::FuncType{{ValueType::I32, ValueType::F32}, {}}}, + {"print_f64_f64", interp::FuncType{{ValueType::F64, ValueType::F64}, {}}}, + }; + + for (auto&& print : print_funcs) { + auto import_name = StringPrintf("spectest.%s", print.name); + spectest[print.name] = HostFunc::New( + store_, print.type, + [=](const Values& params, Values& results, Trap::Ptr* trap) -> wabt::Result { + printf("called host "); + WriteCall(s_stdout_stream.get(), import_name, print.type, params, + results, *trap); + return wabt::Result::Ok; + }); + } -static void InitEnvironment(Environment* env) { - HostModule* host_module = env->AppendHostModule("spectest"); - host_module->AppendFuncExport("print", {{}, {}}, PrintCallback); - host_module->AppendFuncExport("print_i32", {{Type::I32}, {}}, PrintCallback); - host_module->AppendFuncExport("print_f32", {{Type::F32}, {}}, PrintCallback); - host_module->AppendFuncExport("print_f64", {{Type::F64}, {}}, PrintCallback); - host_module->AppendFuncExport("print_i32_f32", {{Type::I32, Type::F32}, {}}, - PrintCallback); - host_module->AppendFuncExport("print_f64_f64", {{Type::F64, Type::F64}, {}}, - PrintCallback); - - host_module->AppendTableExport("table", Type::Funcref, Limits(10, 20)); - host_module->AppendMemoryExport("memory", Limits(1, 2)); - - host_module->AppendGlobalExport("global_i32", false, uint32_t(666)); - host_module->AppendGlobalExport("global_i64", false, uint64_t(666)); - host_module->AppendGlobalExport("global_f32", false, float(666.6f)); - host_module->AppendGlobalExport("global_f64", false, double(666.6)); -} + spectest["table"] = + interp::Table::New(store_, TableType{ValueType::Funcref, Limits{10, 20}}); -CommandRunner::CommandRunner() - : env_(s_features), executor_(&env_, s_trace_stream, s_thread_options) { - InitEnvironment(&env_); + spectest["memory"] = interp::Memory::New(store_, MemoryType{Limits{1, 2}}); + + spectest["global_i32"] = interp::Global::New( + store_, GlobalType{ValueType::I32, Mutability::Const}, Value::Make(u32{666})); + spectest["global_i64"] = interp::Global::New( + store_, GlobalType{ValueType::I64, Mutability::Const}, Value::Make(u64{666})); + spectest["global_f32"] = interp::Global::New( + store_, GlobalType{ValueType::F32, Mutability::Const}, Value::Make(f32{666})); + spectest["global_f64"] = interp::Global::New( + store_, GlobalType{ValueType::F64, Mutability::Const}, Value::Make(f64{666})); } wabt::Result CommandRunner::Run(const Script& script) { @@ -1009,58 +1035,50 @@ void CommandRunner::PrintError(uint32_t line_number, const char* format, ...) { printf("%s:%u: %s\n", source_filename_.c_str(), line_number, buffer); } -static ExecResult GetGlobalExportByName(Environment* env, - interp::Module* module, - string_view name) { - interp::Export* export_ = module->GetExport(name); - if (!export_) { - return ExecResult(interp::ResultType::UnknownExport); - } - if (export_->kind != ExternalKind::Global) { - return ExecResult(interp::ResultType::ExportKindMismatch); +ActionResult CommandRunner::RunAction(int line_number, + const Action* action, + RunVerbosity verbose) { + ExportMap& module = !action->module_name.empty() + ? instances_[action->module_name] + : last_instance_; + Extern::Ptr extern_ = module[action->field_name]; + if (!extern_) { + PrintError(line_number, "unknown invoke \"%s.%s\"", + action->module_name.c_str(), action->field_name.c_str()); + return {}; } - interp::Global* global = env->GetGlobal(export_->index); - return ExecResult(interp::ResultType::Ok, {global->typed_value}); -} - -ExecResult CommandRunner::RunAction(int line_number, - const Action* action, - RunVerbosity verbose) { - interp::Module* module; - if (!action->module_name.empty()) { - module = env_.FindModule(action->module_name); - } else { - module = env_.GetLastModule(); - } - assert(module); - - ExecResult exec_result; + ActionResult result; switch (action->type) { - case ActionType::Invoke: - exec_result = - executor_.RunExportByName(module, action->field_name, action->args); + case ActionType::Invoke: { + auto* func = cast<interp::Func>(extern_.get()); + func->Call(store_, action->args, result.values, &result.trap, + s_trace_stream); + result.types = func->type().results; if (verbose == RunVerbosity::Verbose) { - WriteCall(s_stdout_stream.get(), string_view(), action->field_name, - action->args, exec_result.values, exec_result.result); + WriteCall(s_stdout_stream.get(), action->field_name, func->type(), + action->args, result.values, result.trap); } break; + } - case ActionType::Get: - exec_result = GetGlobalExportByName(&env_, module, action->field_name); + case ActionType::Get: { + auto* global = cast<interp::Global>(extern_.get()); + result.values.push_back(global->Get()); + result.types.push_back(global->type().type); break; + } default: WABT_UNREACHABLE; } - return exec_result; + return result; } wabt::Result CommandRunner::ReadInvalidTextModule(string_view module_filename, - Environment* env, - const std::string& header) { + const std::string& header) { std::vector<uint8_t> file_data; wabt::Result result = ReadFile(module_filename, &file_data); std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer( @@ -1078,101 +1096,128 @@ wabt::Result CommandRunner::ReadInvalidTextModule(string_view module_filename, return result; } -static wabt::Result ReadModule(string_view module_filename, - Environment* env, - Errors* errors, - DefinedModule** out_module) { - wabt::Result result; +interp::Module::Ptr CommandRunner::ReadModule(string_view module_filename, + Errors* errors) { std::vector<uint8_t> file_data; - *out_module = nullptr; + if (Failed(ReadFile(module_filename, &file_data))) { + return {}; + } - result = ReadFile(module_filename, &file_data); - if (Succeeded(result)) { - const bool kReadDebugNames = true; - const bool kStopOnFirstError = true; - const bool kFailOnCustomSectionError = true; - ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, - kStopOnFirstError, kFailOnCustomSectionError); - result = ReadBinaryInterp(env, file_data.data(), file_data.size(), options, - errors, out_module); - - if (Succeeded(result)) { - if (s_verbose) { - env->DisassembleModule(s_stdout_stream.get(), *out_module); - } - } + const bool kReadDebugNames = true; + const bool kStopOnFirstError = true; + const bool kFailOnCustomSectionError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, + kStopOnFirstError, kFailOnCustomSectionError); + ModuleDesc module_desc; + if (Failed(ReadBinaryInterp(file_data.data(), file_data.size(), options, + errors, &module_desc))) { + return {}; } - return result; + + if (s_verbose) { + module_desc.istream.Disassemble(s_stdout_stream.get()); + } + + return interp::Module::New(store_, module_desc); } wabt::Result CommandRunner::ReadInvalidModule(int line_number, - string_view module_filename, - Environment* env, - ModuleType module_type, - const char* desc) { + string_view module_filename, + ModuleType module_type, + const char* desc) { std::string header = StringPrintf( "%s:%d: %s passed", source_filename_.c_str(), line_number, desc); switch (module_type) { case ModuleType::Text: { - return ReadInvalidTextModule(module_filename, env, header); + return ReadInvalidTextModule(module_filename, header); } case ModuleType::Binary: { - DefinedModule* module; Errors errors; - wabt::Result result = ReadModule(module_filename, env, &errors, &module); - if (Failed(result)) { + auto module = ReadModule(module_filename, &errors); + if (!module) { FormatErrorsToFile(errors, Location::Type::Binary, {}, stdout, header, PrintHeader::Once); - return result; + return wabt::Result::Error; + } else { + return wabt::Result::Ok; } - return result; } } WABT_UNREACHABLE; } +Extern::Ptr CommandRunner::GetImport(const std::string& module, + const std::string& name) { + auto mod_iter = registry_.find(module); + if (mod_iter != registry_.end()) { + auto extern_iter = mod_iter->second.find(name); + if (extern_iter != mod_iter->second.end()) { + return extern_iter->second; + } + } + return {}; +} + +void CommandRunner::PopulateImports(const interp::Module::Ptr& module, + RefVec* imports) { + for (auto&& import : module->desc().imports) { + auto extern_ = GetImport(import.type.module, import.type.name); + imports->push_back(extern_ ? extern_.ref() : Ref::Null); + } +} + +void CommandRunner::PopulateExports(const Instance::Ptr& instance, + ExportMap* map) { + map->clear(); + interp::Module::Ptr module{store_, instance->module()}; + for (size_t i = 0; i < module->export_types().size(); ++i) { + const ExportType& export_type = module->export_types()[i]; + (*map)[export_type.name] = store_.UnsafeGet<Extern>(instance->exports()[i]); + } +} + wabt::Result CommandRunner::OnModuleCommand(const ModuleCommand* command) { - Environment::MarkPoint mark = env_.Mark(); Errors errors; - wabt::Result result = ReadModule(command->filename, &env_, - &errors, &last_module_); + auto module = ReadModule(command->filename, &errors); FormatErrorsToFile(errors, Location::Type::Binary); - if (Failed(result)) { - env_.ResetToMarkPoint(mark); + if (!module) { PrintError(command->line, "error reading module: \"%s\"", command->filename.c_str()); return wabt::Result::Error; } - ExecResult exec_result = executor_.Initialize(last_module_); - if (!exec_result.ok()) { - env_.ResetToMarkPoint(mark); - WriteResult(s_stdout_stream.get(), "error initializing module", - exec_result.result); + RefVec imports; + PopulateImports(module, &imports); + + Trap::Ptr trap; + auto instance = Instance::Instantiate(store_, module.ref(), imports, &trap); + if (trap) { + assert(!instance); + PrintError(command->line, "error instantiating module: \"%s\"", + trap->message().c_str()); return wabt::Result::Error; } + PopulateExports(instance, &last_instance_); if (!command->name.empty()) { - last_module_->name = command->name; - env_.EmplaceModuleBinding(command->name, - Binding(env_.GetModuleCount() - 1)); + instances_[command->name] = last_instance_; } return wabt::Result::Ok; } wabt::Result CommandRunner::OnActionCommand(const ActionCommand* command) { - ExecResult exec_result = + ActionResult result = RunAction(command->line, &command->action, RunVerbosity::Verbose); - if (!exec_result.ok()) { + if (result.trap) { PrintError(command->line, "unexpected trap: %s", - ResultToString(exec_result.result).c_str()); + result.trap->message().c_str()); return wabt::Result::Error; } @@ -1181,12 +1226,8 @@ wabt::Result CommandRunner::OnActionCommand(const ActionCommand* command) { wabt::Result CommandRunner::OnAssertMalformedCommand( const AssertMalformedCommand* command) { - Environment env(s_features); - InitEnvironment(&env); - - wabt::Result result = - ReadInvalidModule(command->line, command->filename, &env, command->type, - "assert_malformed"); + wabt::Result result = ReadInvalidModule(command->line, command->filename, + command->type, "assert_malformed"); if (Succeeded(result)) { PrintError(command->line, "expected module to be malformed: \"%s\"", command->filename.c_str()); @@ -1197,55 +1238,52 @@ wabt::Result CommandRunner::OnAssertMalformedCommand( } wabt::Result CommandRunner::OnRegisterCommand(const RegisterCommand* command) { - Index module_index; if (!command->name.empty()) { - module_index = env_.FindModuleIndex(command->name); + auto instance_iter = instances_.find(command->name); + if (instance_iter == instances_.end()) { + PrintError(command->line, "unknown module in register"); + return wabt::Result::Error; + } + registry_[command->as] = instance_iter->second; } else { - module_index = env_.GetLastModuleIndex(); - } - - if (module_index == kInvalidIndex) { - PrintError(command->line, "unknown module in register"); - return wabt::Result::Error; + registry_[command->as] = last_instance_; } - env_.EmplaceRegisteredModuleBinding(command->as, Binding(module_index)); return wabt::Result::Ok; } wabt::Result CommandRunner::OnAssertUnlinkableCommand( const AssertUnlinkableCommand* command) { Errors errors; - wabt::Result result = - ReadModule(command->filename, &env_, &errors, &last_module_); - - if (Failed(result)) { - std::string header = StringPrintf("%s:%d: assert_unlinkable passed", - source_filename_.c_str(), command->line); - FormatErrorsToFile(errors, Location::Type::Binary, {}, stdout, header, - PrintHeader::Once); - return wabt::Result::Ok; + auto module = ReadModule(command->filename, &errors); + + if (!module) { + PrintError(command->line, "unable to compile unlinkable module: \"%s\"", + command->filename.c_str()); + return wabt::Result::Error; } - ExecResult exec_result = executor_.Initialize(last_module_); - if (exec_result.ok()) { + RefVec imports; + PopulateImports(module, &imports); + + Trap::Ptr trap; + auto instance = Instance::Instantiate(store_, module.ref(), imports, &trap); + if (!trap) { PrintError(command->line, "expected module to be unlinkable: \"%s\"", command->filename.c_str()); return wabt::Result::Error; } - WriteResult(s_stdout_stream.get(), "assert_unlinkable passed", - exec_result.result); + // TODO: Change to one-line error. + PrintError(command->line, "assert_unlinkable passed:\n error: %s", + trap->message().c_str()); return wabt::Result::Ok; } wabt::Result CommandRunner::OnAssertInvalidCommand( const AssertInvalidCommand* command) { - Environment env(s_features); - InitEnvironment(&env); - - wabt::Result result = ReadInvalidModule( - command->line, command->filename, &env, command->type, "assert_invalid"); + wabt::Result result = ReadInvalidModule(command->line, command->filename, + command->type, "assert_invalid"); if (Succeeded(result)) { PrintError(command->line, "expected module to be invalid: \"%s\"", command->filename.c_str()); @@ -1258,94 +1296,127 @@ wabt::Result CommandRunner::OnAssertInvalidCommand( wabt::Result CommandRunner::OnAssertUninstantiableCommand( const AssertUninstantiableCommand* command) { Errors errors; - DefinedModule* module; - wabt::Result result = ReadModule(command->filename, &env_, &errors, &module); - FormatErrorsToFile(errors, Location::Type::Binary); + auto module = ReadModule(command->filename, &errors); - if (Succeeded(result)) { - ExecResult exec_result = executor_.Initialize(module); - if (exec_result.ok()) { - PrintError(command->line, "expected instantiation error: \"%s\"", - command->filename.c_str()); - result = wabt::Result::Error; - } else { - result = wabt::Result::Ok; - } - } else { - PrintError(command->line, "error reading module: \"%s\"", + if (!module) { + PrintError(command->line, "unable to compile uninstantiable module: \"%s\"", command->filename.c_str()); - result = wabt::Result::Error; + return wabt::Result::Error; } - // Don't reset env_ here; if the start function fails, the environment is - // still modified. For example, a table may have been populated with a - // function from this module. - return result; -} + RefVec imports; + PopulateImports(module, &imports); -static bool TypedValuesAreEqual(const TypedValue& tv1, const TypedValue& tv2) { - if (tv1.type != tv2.type) { - return false; + Trap::Ptr trap; + auto instance = Instance::Instantiate(store_, module.ref(), imports, &trap); + if (!trap) { + PrintError(command->line, "expected module to be uninstantiable: \"%s\"", + command->filename.c_str()); + return wabt::Result::Error; } - switch (tv1.type) { + // TODO: print error when assertion passes. +#if 0 + PrintError(command->line, "assert_uninstantiable passed: %s", + trap->message().c_str()); +#endif + return wabt::Result::Ok; +} + +static bool TypedValuesAreEqual(const TypedValue& expected, + const TypedValue& actual) { + assert(expected.type == actual.type || IsReference(expected.type)); + switch (expected.type) { case Type::I32: - return tv1.value.i32 == tv2.value.i32; + return expected.value.Get<u32>() == actual.value.Get<u32>(); + case Type::F32: - return tv1.value.f32_bits == tv2.value.f32_bits; + return Bitcast<u32>(expected.value.Get<f32>()) == + Bitcast<u32>(actual.value.Get<f32>()); + case Type::I64: - return tv1.value.i64 == tv2.value.i64; + return expected.value.Get<u64>() == actual.value.Get<u64>(); + case Type::F64: - return tv1.value.f64_bits == tv2.value.f64_bits; + return Bitcast<u64>(expected.value.Get<f64>()) == + Bitcast<u64>(actual.value.Get<f64>()); + case Type::V128: - return tv1.value.vec128 == tv2.value.vec128; + return expected.value.Get<v128>() == actual.value.Get<v128>(); + case Type::Nullref: - return true; + return actual.value.Get<Ref>() == Ref::Null; + case Type::Funcref: - return tv1.value.ref.index == tv2.value.ref.index; case Type::Hostref: - return tv1.value.ref.index == tv2.value.ref.index; + return expected.value.Get<Ref>() == actual.value.Get<Ref>(); + default: WABT_UNREACHABLE; } } +static bool WABT_VECTORCALL IsCanonicalNan(f32 val) { + const u32 kQuietNan = 0x7fc00000U; + const u32 kQuietNegNan = 0xffc00000U; + u32 bits = Bitcast<u32>(val); + return bits == kQuietNan || bits == kQuietNegNan; +} + +static bool WABT_VECTORCALL IsCanonicalNan(f64 val) { + const u64 kQuietNan = 0x7ff8000000000000ULL; + const u64 kQuietNegNan = 0xfff8000000000000ULL; + u64 bits = Bitcast<u64>(val); + return bits == kQuietNan || bits == kQuietNegNan; +} + +static bool WABT_VECTORCALL IsArithmeticNan(f32 val) { + const u32 kQuietNan = 0x7fc00000U; + return (Bitcast<u32>(val) & kQuietNan) == kQuietNan; +} + +static bool WABT_VECTORCALL IsArithmeticNan(f64 val) { + const u64 kQuietNan = 0x7ff8000000000000ULL; + return (Bitcast<u64>(val) & kQuietNan) == kQuietNan; +} + wabt::Result CommandRunner::OnAssertReturnCommand( const AssertReturnCommand* command) { - ExecResult exec_result = + ActionResult action_result = RunAction(command->line, &command->action, RunVerbosity::Quiet); - if (!exec_result.ok()) { + if (action_result.trap) { PrintError(command->line, "unexpected trap: %s", - ResultToString(exec_result.result).c_str()); + action_result.trap->message().c_str()); return wabt::Result::Error; } - if (exec_result.values.size() != command->expected.size()) { + if (action_result.values.size() != command->expected.size()) { PrintError(command->line, "result length mismatch in assert_return: expected %" PRIzd ", got %" PRIzd, - command->expected.size(), exec_result.values.size()); + command->expected.size(), action_result.values.size()); return wabt::Result::Error; } wabt::Result result = wabt::Result::Ok; - for (size_t i = 0; i < exec_result.values.size(); ++i) { + for (size_t i = 0; i < action_result.values.size(); ++i) { const ExpectedValue& expected = command->expected[i]; - const TypedValue& actual = exec_result.values[i]; + TypedValue actual{action_result.types[i], action_result.values[i]}; + if (expected.is_expected_nan) { bool is_nan; if (expected.expectedNan == ExpectedNan::Arithmetic) { if (expected.value.type == Type::F64) { - is_nan = IsArithmeticNan(actual.value.f64_bits); + is_nan = IsArithmeticNan(actual.value.Get<f64>()); } else { - is_nan = IsArithmeticNan(actual.value.f32_bits); + is_nan = IsArithmeticNan(actual.value.Get<f32>()); } } else if (expected.expectedNan == ExpectedNan::Canonical) { if (expected.value.type == Type::F64) { - is_nan = IsCanonicalNan(actual.value.f64_bits); + is_nan = IsCanonicalNan(actual.value.Get<f64>()); } else { - is_nan = IsCanonicalNan(actual.value.f32_bits); + is_nan = IsCanonicalNan(actual.value.Get<f32>()); } } else { WABT_UNREACHABLE; @@ -1356,7 +1427,7 @@ wabt::Result CommandRunner::OnAssertReturnCommand( result = wabt::Result::Error; } } else if (expected.value.type == Type::Funcref) { - if (actual.type != Type::Funcref) { + if (!store_.HasValueType(actual.value.Get<Ref>(), Type::Funcref)) { PrintError(command->line, "mismatch in result %" PRIzd " of assert_return: expected funcref, got %s", @@ -1379,28 +1450,32 @@ wabt::Result CommandRunner::OnAssertReturnCommand( wabt::Result CommandRunner::OnAssertTrapCommand( const AssertTrapCommand* command) { - ExecResult exec_result = + ActionResult result = RunAction(command->line, &command->action, RunVerbosity::Quiet); - if (exec_result.ok()) { + if (!result.trap) { PrintError(command->line, "expected trap: \"%s\"", command->text.c_str()); return wabt::Result::Error; } PrintError(command->line, "assert_trap passed: %s", - ResultToString(exec_result.result).c_str()); + result.trap->message().c_str()); return wabt::Result::Ok; } wabt::Result CommandRunner::OnAssertExhaustionCommand( const AssertExhaustionCommand* command) { - ExecResult exec_result = + ActionResult result = RunAction(command->line, &command->action, RunVerbosity::Quiet); - if (exec_result.result.type != interp::ResultType::TrapCallStackExhausted && - exec_result.result.type != interp::ResultType::TrapValueStackExhausted) { - PrintError(command->line, "expected call stack exhaustion"); + if (!result.trap || result.trap->message() != "call stack exhausted") { + PrintError(command->line, "expected trap: \"%s\"", command->text.c_str()); return wabt::Result::Error; } + // TODO: print message when assertion passes. +#if 0 + PrintError(command->line, "assert_exhaustion passed: %s", + result.trap->message().c_str()); +#endif return wabt::Result::Ok; } diff --git a/src/tools/wasm-interp.cc b/src/tools/wasm-interp.cc index 6126331b..733c54cd 100644 --- a/src/tools/wasm-interp.cc +++ b/src/tools/wasm-interp.cc @@ -16,7 +16,6 @@ #include <algorithm> #include <cassert> -#include <cinttypes> #include <cstdio> #include <cstdlib> #include <memory> @@ -24,18 +23,13 @@ #include <vector> #include "src/binary-reader.h" -#include "src/cast.h" #include "src/error-formatter.h" #include "src/feature.h" #include "src/interp/binary-reader-interp.h" +#include "src/interp/interp-util.h" #include "src/interp/interp.h" -#include "src/literal.h" #include "src/option-parser.h" -#include "src/resolve-names.h" #include "src/stream.h" -#include "src/validator.h" -#include "src/wast-lexer.h" -#include "src/wast-parser.h" using namespace wabt; using namespace wabt::interp; @@ -52,10 +46,7 @@ static Features s_features; static std::unique_ptr<FileStream> s_log_stream; static std::unique_ptr<FileStream> s_stdout_stream; -enum class RunVerbosity { - Quiet = 0, - Verbose = 1, -}; +static Store s_store; static const char s_description[] = R"( read a file in the wasm binary format, and run in it a stack-based @@ -117,112 +108,104 @@ static void ParseOptions(int argc, char** argv) { parser.Parse(argc, argv); } -static void RunAllExports(interp::Module* module, - Executor* executor, - RunVerbosity verbose) { - TypedValues args; - TypedValues results; - for (const interp::Export& export_ : module->exports) { - if (export_.kind != ExternalKind::Func) { - continue; - } - ExecResult exec_result = executor->RunExport(&export_, args); - if (verbose == RunVerbosity::Verbose) { - WriteCall(s_stdout_stream.get(), string_view(), export_.name, args, - exec_result.values, exec_result.result); - } - } -} +Result RunAllExports(const Instance::Ptr& instance, Errors* errors) { + Result result = Result::Ok; -static wabt::Result ReadModule(const char* module_filename, - Environment* env, - Errors* errors, - DefinedModule** out_module) { - wabt::Result result; - std::vector<uint8_t> file_data; + auto module = s_store.UnsafeGet<Module>(instance->module()); + auto&& module_desc = module->desc(); - *out_module = nullptr; - - result = ReadFile(module_filename, &file_data); - if (Succeeded(result)) { - const bool kReadDebugNames = true; - const bool kStopOnFirstError = true; - const bool kFailOnCustomSectionError = true; - ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, - kStopOnFirstError, kFailOnCustomSectionError); - result = ReadBinaryInterp(env, file_data.data(), file_data.size(), options, - errors, out_module); - - if (Succeeded(result)) { - if (s_verbose) { - env->DisassembleModule(s_stdout_stream.get(), *out_module); + for (auto&& export_ : module_desc.exports) { + if (export_.type.type->kind != ExternalKind::Func) { + continue; + } + auto* func_type = cast<FuncType>(export_.type.type.get()); + if (func_type->params.empty()) { + if (s_trace_stream) { + s_trace_stream->Writef(">>> running export \"%s\":\n", + export_.type.name.c_str()); } + auto func = s_store.UnsafeGet<Func>(instance->funcs()[export_.index]); + Values params; + Values results; + Trap::Ptr trap; + result |= func->Call(s_store, params, results, &trap, s_trace_stream); + WriteCall(s_stdout_stream.get(), export_.type.name, *func_type, params, + results, trap); } } + return result; } -static interp::Result PrintCallback(const HostFunc* func, - const interp::FuncSignature* sig, - const TypedValues& args, - TypedValues& results) { - printf("called host "); - WriteCall(s_stdout_stream.get(), func->module_name, func->field_name, args, - results, interp::ResultType::Ok); - return interp::ResultType::Ok; -} +Result ReadAndInstantiateModule(const char* module_filename, + Errors* errors, + Instance::Ptr* out_instance) { + auto* stream = s_stdout_stream.get(); + std::vector<uint8_t> file_data; + CHECK_RESULT(ReadFile(module_filename, &file_data)); + + ModuleDesc module_desc; + const bool kReadDebugNames = true; + const bool kStopOnFirstError = true; + const bool kFailOnCustomSectionError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, + kStopOnFirstError, kFailOnCustomSectionError); + CHECK_RESULT(ReadBinaryInterp(file_data.data(), file_data.size(), options, + errors, &module_desc)); + + if (s_verbose) { + module_desc.istream.Disassemble(stream); + } -static void InitEnvironment(Environment* env) { - if (s_host_print) { - auto* host_module = env->AppendHostModule("host"); - host_module->on_unknown_func_export = - [](Environment* env, HostModule* host_module, string_view name, - Index sig_index) -> Index { - if (name != "print") { - return kInvalidIndex; - } + auto module = Module::New(s_store, module_desc); + + RefVec imports; + for (auto&& import : module_desc.imports) { + if (import.type.type->kind == ExternKind::Func && + ((s_host_print && import.type.module == "host" && + import.type.name == "print") || + s_dummy_import_func)) { + auto func_type = *cast<FuncType>(import.type.type.get()); + auto import_name = StringPrintf("%s.%s", import.type.module.c_str(), + import.type.name.c_str()); + + auto host_func = + HostFunc::New(s_store, func_type, + [=](const Values& params, Values& results, + Trap::Ptr* trap) -> Result { + printf("called host "); + WriteCall(stream, import_name, func_type, params, + results, *trap); + return Result::Ok; + }); + imports.push_back(host_func.ref()); + continue; + } - return host_module->AppendFuncExport(name, sig_index, PrintCallback) - .second; - }; + // By default, just push an null reference. This won't resolve, and + // instantiation will fail. + imports.push_back(Ref::Null); } - if (s_dummy_import_func) { - env->on_unknown_module = [](Environment* env, string_view name) { - auto* host_module = env->AppendHostModule(name); - host_module->on_unknown_func_export = - [](Environment* env, HostModule* host_module, string_view name, - Index sig_index) -> Index { - return host_module->AppendFuncExport(name, sig_index, PrintCallback) - .second; - }; - return true; - }; + RefPtr<Trap> trap; + *out_instance = Instance::Instantiate(s_store, module.ref(), imports, &trap); + if (!*out_instance) { + // TODO: change to "initializing" + WriteTrap(stream, "error initialiazing module", trap); + return Result::Error; } -} -static wabt::Result ReadAndRunModule(const char* module_filename) { - wabt::Result result; - Environment env(s_features); - InitEnvironment(&env); + return Result::Ok; +} +static Result ReadAndRunModule(const char* module_filename) { Errors errors; - DefinedModule* module = nullptr; - result = ReadModule(module_filename, &env, &errors, &module); - FormatErrorsToFile(errors, Location::Type::Binary); - if (Succeeded(result)) { - Executor executor(&env, s_trace_stream, s_thread_options); - ExecResult exec_result = executor.Initialize(module); - if (exec_result.ok()) { - if (s_run_all_exports) { - RunAllExports(module, &executor, RunVerbosity::Verbose); - } - } else { - WriteResult(s_stdout_stream.get(), "error initialiazing module", - exec_result.result); - return wabt::Result::Error; - } + Instance::Ptr instance; + Result result = ReadAndInstantiateModule(module_filename, &errors, &instance); + if (Succeeded(result) && s_run_all_exports) { + RunAllExports(instance, &errors); } + FormatErrorsToFile(errors, Location::Type::Binary); return result; } |