diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/tools/spectest-interp.cc | 1470 | ||||
-rw-r--r-- | src/tools/wasm-interp.cc | 1230 |
2 files changed, 1507 insertions, 1193 deletions
diff --git a/src/tools/spectest-interp.cc b/src/tools/spectest-interp.cc new file mode 100644 index 00000000..24253c9d --- /dev/null +++ b/src/tools/spectest-interp.cc @@ -0,0 +1,1470 @@ +/* + * Copyright 2016 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. + */ + +#include <algorithm> +#include <cassert> +#include <cinttypes> +#include <cstdio> +#include <cstdlib> +#include <memory> +#include <string> +#include <vector> + +#include "src/binary-reader-interpreter.h" +#include "src/binary-reader.h" +#include "src/cast.h" +#include "src/error-handler.h" +#include "src/feature.h" +#include "src/interpreter.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::interpreter; + +#define V(name, str) str, +static const char* s_trap_strings[] = {FOREACH_INTERPRETER_RESULT(V)}; +#undef V + +static int s_verbose; +static const char* s_infile; +static Thread::Options s_thread_options; +static bool s_trace; +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 const char s_description[] = +R"( read a Spectest JSON file, and run its tests in the interpreter. + +examples: + # parse test.json and run the spec tests + $ spectest-interp test.json +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("spectest-interp", s_description); + + parser.AddOption('v', "verbose", "Use multiple times for more info", []() { + s_verbose++; + s_log_stream = FileStream::CreateStdout(); + }); + parser.AddHelpOption(); + s_features.AddOptions(&parser); + parser.AddOption('V', "value-stack-size", "SIZE", + "Size in elements of the value stack", + [](const std::string& argument) { + // TODO(binji): validate. + s_thread_options.value_stack_size = atoi(argument.c_str()); + }); + parser.AddOption('C', "call-stack-size", "SIZE", + "Size in elements of the call stack", + [](const std::string& argument) { + // TODO(binji): validate. + s_thread_options.call_stack_size = atoi(argument.c_str()); + }); + parser.AddOption('t', "trace", "Trace execution", []() { s_trace = true; }); + + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { s_infile = argument; }); + parser.Parse(argc, argv); +} + +enum class ModuleType { + Text, + Binary, +}; + +static string_view GetDirname(string_view path) { + // Strip everything after and including the last slash (or backslash), e.g.: + // + // s = "foo/bar/baz", => "foo/bar" + // s = "/usr/local/include/stdio.h", => "/usr/local/include" + // s = "foo.bar", => "" + // s = "some\windows\directory", => "some\windows" + size_t last_slash = path.find_last_of('/'); + size_t last_backslash = path.find_last_of('\\'); + if (last_slash == string_view::npos) + last_slash = 0; + if (last_backslash == string_view::npos) + last_backslash = 0; + + return path.substr(0, std::max(last_slash, last_backslash)); +} + +/* Not sure, but 100 chars is probably safe */ +#define MAX_TYPED_VALUE_CHARS 100 + +static void SPrintTypedValue(char* buffer, size_t size, const TypedValue* tv) { + switch (tv->type) { + case Type::I32: + snprintf(buffer, size, "i32:%u", tv->value.i32); + break; + + case Type::I64: + snprintf(buffer, size, "i64:%" PRIu64, tv->value.i64); + break; + + case Type::F32: { + float value; + memcpy(&value, &tv->value.f32_bits, sizeof(float)); + snprintf(buffer, size, "f32:%f", value); + break; + } + + case Type::F64: { + double value; + memcpy(&value, &tv->value.f64_bits, sizeof(double)); + snprintf(buffer, size, "f64:%f", value); + break; + } + + default: + WABT_UNREACHABLE; + } +} + +static void PrintTypedValue(const TypedValue* tv) { + char buffer[MAX_TYPED_VALUE_CHARS]; + SPrintTypedValue(buffer, sizeof(buffer), tv); + printf("%s", buffer); +} + +static void PrintTypedValueVector(const std::vector<TypedValue>& values) { + for (size_t i = 0; i < values.size(); ++i) { + PrintTypedValue(&values[i]); + if (i != values.size() - 1) + printf(", "); + } +} + +static void PrintInterpreterResult(const char* desc, + interpreter::Result iresult) { + printf("%s: %s\n", desc, s_trap_strings[static_cast<size_t>(iresult)]); +} + +static void PrintCall(string_view module_name, + string_view func_name, + const std::vector<TypedValue>& args, + const std::vector<TypedValue>& results, + interpreter::Result iresult) { + if (!module_name.empty()) + printf(PRIstringview ".", WABT_PRINTF_STRING_VIEW_ARG(module_name)); + printf(PRIstringview "(", WABT_PRINTF_STRING_VIEW_ARG(func_name)); + PrintTypedValueVector(args); + printf(") =>"); + if (iresult == interpreter::Result::Ok) { + if (results.size() > 0) { + printf(" "); + PrintTypedValueVector(results); + } + printf("\n"); + } else { + PrintInterpreterResult(" error", iresult); + } +} + +static interpreter::Result RunFunction(Thread* thread, + Index func_index, + const std::vector<TypedValue>& args, + std::vector<TypedValue>* out_results) { + return s_trace ? thread->TraceFunction(func_index, s_stdout_stream.get(), + args, out_results) + : thread->RunFunction(func_index, args, out_results); +} + +static interpreter::Result RunStartFunction(Thread* thread, + DefinedModule* module) { + if (module->start_func_index == kInvalidIndex) + return interpreter::Result::Ok; + + if (s_trace) + printf(">>> running start function:\n"); + std::vector<TypedValue> args; + std::vector<TypedValue> results; + interpreter::Result iresult = + RunFunction(thread, module->start_func_index, args, &results); + assert(results.size() == 0); + return iresult; +} + +static interpreter::Result RunExport(Thread* thread, + const interpreter::Export* export_, + const std::vector<TypedValue>& args, + std::vector<TypedValue>* out_results) { + if (s_trace) { + printf(">>> running export \"" PRIstringview "\":\n", + WABT_PRINTF_STRING_VIEW_ARG(export_->name)); + } + + assert(export_->kind == ExternalKind::Func); + return RunFunction(thread, export_->index, args, out_results); +} + +static interpreter::Result RunExportByName(Thread* thread, + interpreter::Module* module, + string_view name, + const std::vector<TypedValue>& args, + std::vector<TypedValue>* out_results, + RunVerbosity verbose) { + interpreter::Export* export_ = module->GetExport(name); + if (!export_) + return interpreter::Result::UnknownExport; + if (export_->kind != ExternalKind::Func) + return interpreter::Result::ExportKindMismatch; + return RunExport(thread, export_, args, out_results); +} + +static interpreter::Result GetGlobalExportByName( + Thread* thread, + interpreter::Module* module, + string_view name, + std::vector<TypedValue>* out_results) { + interpreter::Export* export_ = module->GetExport(name); + if (!export_) + return interpreter::Result::UnknownExport; + if (export_->kind != ExternalKind::Global) + return interpreter::Result::ExportKindMismatch; + + interpreter::Global* global = thread->env()->GetGlobal(export_->index); + out_results->clear(); + out_results->push_back(global->typed_value); + return interpreter::Result::Ok; +} + +static wabt::Result ReadModule(const char* module_filename, + Environment* env, + ErrorHandler* error_handler, + DefinedModule** out_module) { + wabt::Result result; + std::vector<uint8_t> file_data; + + *out_module = nullptr; + + result = ReadFile(module_filename, &file_data); + if (Succeeded(result)) { + const bool kReadDebugNames = true; + const bool kStopOnFirstError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, + kStopOnFirstError); + result = ReadBinaryInterpreter(env, DataOrNull(file_data), file_data.size(), + &options, error_handler, out_module); + + if (Succeeded(result)) { + if (s_verbose) + env->DisassembleModule(s_stdout_stream.get(), *out_module); + } + } + return result; +} + +static interpreter::Result DefaultHostCallback( + const HostFunc* func, + const interpreter::FuncSignature* sig, + Index num_args, + TypedValue* args, + Index num_results, + TypedValue* out_results, + void* user_data) { + memset(out_results, 0, sizeof(TypedValue) * num_results); + for (Index i = 0; i < num_results; ++i) + out_results[i].type = sig->result_types[i]; + + std::vector<TypedValue> vec_args(args, args + num_args); + std::vector<TypedValue> vec_results(out_results, out_results + num_results); + + printf("called host "); + PrintCall(func->module_name, func->field_name, vec_args, vec_results, + interpreter::Result::Ok); + return interpreter::Result::Ok; +} + +#define PRIimport "\"" PRIstringview "." PRIstringview "\"" +#define PRINTF_IMPORT_ARG(x) \ + WABT_PRINTF_STRING_VIEW_ARG((x).module_name) \ + , WABT_PRINTF_STRING_VIEW_ARG((x).field_name) + +class SpectestHostImportDelegate : public HostImportDelegate { + public: + wabt::Result ImportFunc(interpreter::FuncImport* import, + interpreter::Func* func, + interpreter::FuncSignature* func_sig, + const ErrorCallback& callback) override { + if (import->field_name == "print") { + cast<HostFunc>(func)->callback = DefaultHostCallback; + return wabt::Result::Ok; + } else { + PrintError(callback, "unknown host function import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return wabt::Result::Error; + } + } + + wabt::Result ImportTable(interpreter::TableImport* import, + interpreter::Table* table, + const ErrorCallback& callback) override { + if (import->field_name == "table") { + table->limits.has_max = true; + table->limits.initial = 10; + table->limits.max = 20; + return wabt::Result::Ok; + } else { + PrintError(callback, "unknown host table import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return wabt::Result::Error; + } + } + + wabt::Result ImportMemory(interpreter::MemoryImport* import, + interpreter::Memory* memory, + const ErrorCallback& callback) override { + if (import->field_name == "memory") { + memory->page_limits.has_max = true; + memory->page_limits.initial = 1; + memory->page_limits.max = 2; + memory->data.resize(memory->page_limits.initial * WABT_MAX_PAGES); + return wabt::Result::Ok; + } else { + PrintError(callback, "unknown host memory import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return wabt::Result::Error; + } + } + + wabt::Result ImportGlobal(interpreter::GlobalImport* import, + interpreter::Global* global, + const ErrorCallback& callback) override { + if (import->field_name == "global") { + switch (global->typed_value.type) { + case Type::I32: + global->typed_value.value.i32 = 666; + break; + + case Type::F32: { + float value = 666.6f; + memcpy(&global->typed_value.value.f32_bits, &value, sizeof(value)); + break; + } + + case Type::I64: + global->typed_value.value.i64 = 666; + break; + + case Type::F64: { + double value = 666.6; + memcpy(&global->typed_value.value.f64_bits, &value, sizeof(value)); + break; + } + + default: + PrintError(callback, "bad type for host global import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return wabt::Result::Error; + } + + return wabt::Result::Ok; + } else { + PrintError(callback, "unknown host global import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return wabt::Result::Error; + } + } + + private: + void PrintError(const ErrorCallback& callback, const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + callback(buffer); + } +}; + +static void InitEnvironment(Environment* env) { + HostModule* host_module = env->AppendHostModule("spectest"); + host_module->import_delegate.reset(new SpectestHostImportDelegate()); +} + +enum class ActionType { + Invoke, + Get, +}; + +struct Action { + ::ActionType type = ::ActionType::Invoke; + std::string module_name; + std::string field_name; + std::vector<TypedValue> args; +}; + +// An extremely simple JSON parser that only knows how to parse the expected +// format from wat2wasm. +class SpecJSONParser { + public: + SpecJSONParser() : thread_(&env_, s_thread_options) {} + + wabt::Result ReadFile(const char* spec_json_filename); + wabt::Result ParseCommands(); + + int passed() const { return passed_; } + int total() const { return total_; } + + private: + void WABT_PRINTF_FORMAT(2, 3) PrintParseError(const char* format, ...); + void WABT_PRINTF_FORMAT(2, 3) PrintCommandError(const char* format, ...); + + void PutbackChar(); + int ReadChar(); + void SkipWhitespace(); + bool Match(const char* s); + wabt::Result Expect(const char* s); + wabt::Result ExpectKey(const char* key); + wabt::Result ParseUint32(uint32_t* out_int); + wabt::Result ParseString(std::string* out_string); + wabt::Result ParseKeyStringValue(const char* key, std::string* out_string); + wabt::Result ParseOptNameStringValue(std::string* out_string); + wabt::Result ParseLine(); + wabt::Result ParseTypeObject(Type* out_type); + wabt::Result ParseTypeVector(TypeVector* out_types); + wabt::Result ParseConst(TypedValue* out_value); + wabt::Result ParseConstVector(std::vector<TypedValue>* out_values); + wabt::Result ParseAction(::Action* out_action); + wabt::Result ParseModuleType(ModuleType* out_type); + + std::string CreateModulePath(string_view filename); + + wabt::Result OnModuleCommand(string_view filename, string_view name); + wabt::Result RunAction(::Action* action, + interpreter::Result* out_iresult, + std::vector<TypedValue>* out_results, + RunVerbosity verbose); + wabt::Result OnActionCommand(::Action* action); + wabt::Result ReadInvalidTextModule(const char* module_filename, + Environment* env, + ErrorHandler* error_handler); + wabt::Result ReadInvalidModule(const char* module_filename, + Environment* env, + ModuleType module_type, + const char* desc); + wabt::Result OnAssertMalformedCommand(string_view filename, + string_view text, + ModuleType module_type); + wabt::Result OnRegisterCommand(string_view name, string_view as); + wabt::Result OnAssertUnlinkableCommand(string_view filename, + string_view text, + ModuleType module_type); + wabt::Result OnAssertInvalidCommand(string_view filename, + string_view text, + ModuleType module_type); + wabt::Result OnAssertUninstantiableCommand(string_view filename, + string_view text, + ModuleType module_type); + wabt::Result OnAssertReturnCommand(::Action* action, + const std::vector<TypedValue>& expected); + wabt::Result OnAssertReturnNanCommand(::Action* action, bool canonical); + wabt::Result OnAssertTrapCommand(::Action* action, string_view text); + wabt::Result OnAssertExhaustionCommand(::Action* action); + wabt::Result ParseCommand(); + + Environment env_; + Thread thread_; + DefinedModule* last_module_ = nullptr; + + // Parsing info. + std::vector<uint8_t> json_data_; + std::string source_filename_; + size_t json_offset_ = 0; + Location loc_; + Location prev_loc_; + bool has_prev_loc_ = false; + uint32_t command_line_number_ = 0; + + // Test info. + int passed_ = 0; + int total_ = 0; +}; + +#define EXPECT(x) CHECK_RESULT(Expect(x)) +#define EXPECT_KEY(x) CHECK_RESULT(ExpectKey(x)) +#define PARSE_KEY_STRING_VALUE(key, value) \ + CHECK_RESULT(ParseKeyStringValue(key, value)) + +wabt::Result SpecJSONParser::ReadFile(const char* spec_json_filename) { + loc_.filename = spec_json_filename; + loc_.line = 1; + loc_.first_column = 1; + InitEnvironment(&env_); + + return wabt::ReadFile(spec_json_filename, &json_data_); +} + +void SpecJSONParser::PrintParseError(const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + fprintf(stderr, "%s:%d:%d: %s\n", loc_.filename, loc_.line, loc_.first_column, + buffer); +} + +void SpecJSONParser::PrintCommandError(const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + printf(PRIstringview ":%u: %s\n", + WABT_PRINTF_STRING_VIEW_ARG(source_filename_), command_line_number_, + buffer); +} + +void SpecJSONParser::PutbackChar() { + assert(has_prev_loc_); + json_offset_--; + loc_ = prev_loc_; + has_prev_loc_ = false; +} + +int SpecJSONParser::ReadChar() { + if (json_offset_ >= json_data_.size()) + return -1; + prev_loc_ = loc_; + char c = json_data_[json_offset_++]; + if (c == '\n') { + loc_.line++; + loc_.first_column = 1; + } else { + loc_.first_column++; + } + has_prev_loc_ = true; + return c; +} + +void SpecJSONParser::SkipWhitespace() { + while (1) { + switch (ReadChar()) { + case -1: + return; + + case ' ': + case '\t': + case '\n': + case '\r': + break; + + default: + PutbackChar(); + return; + } + } +} + +bool SpecJSONParser::Match(const char* s) { + SkipWhitespace(); + Location start_loc = loc_; + size_t start_offset = json_offset_; + while (*s && *s == ReadChar()) + s++; + + if (*s == 0) { + return true; + } else { + json_offset_ = start_offset; + loc_ = start_loc; + return false; + } +} + +wabt::Result SpecJSONParser::Expect(const char* s) { + if (Match(s)) { + return wabt::Result::Ok; + } else { + PrintParseError("expected %s", s); + return wabt::Result::Error; + } +} + +wabt::Result SpecJSONParser::ExpectKey(const char* key) { + size_t keylen = strlen(key); + size_t quoted_len = keylen + 2 + 1; + char* quoted = static_cast<char*>(alloca(quoted_len)); + snprintf(quoted, quoted_len, "\"%s\"", key); + EXPECT(quoted); + EXPECT(":"); + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseUint32(uint32_t* out_int) { + uint32_t result = 0; + SkipWhitespace(); + while (1) { + int c = ReadChar(); + if (c >= '0' && c <= '9') { + uint32_t last_result = result; + result = result * 10 + static_cast<uint32_t>(c - '0'); + if (result < last_result) { + PrintParseError("uint32 overflow"); + return wabt::Result::Error; + } + } else { + PutbackChar(); + break; + } + } + *out_int = result; + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseString(std::string* out_string) { + out_string->clear(); + + SkipWhitespace(); + if (ReadChar() != '"') { + PrintParseError("expected string"); + return wabt::Result::Error; + } + + while (1) { + int c = ReadChar(); + if (c == '"') { + break; + } else if (c == '\\') { + /* The only escape supported is \uxxxx. */ + c = ReadChar(); + if (c != 'u') { + PrintParseError("expected escape: \\uxxxx"); + return wabt::Result::Error; + } + uint16_t code = 0; + for (int i = 0; i < 4; ++i) { + c = ReadChar(); + int cval; + if (c >= '0' && c <= '9') { + cval = c - '0'; + } else if (c >= 'a' && c <= 'f') { + cval = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + cval = c - 'A' + 10; + } else { + PrintParseError("expected hex char"); + return wabt::Result::Error; + } + code = (code << 4) + cval; + } + + if (code < 256) { + *out_string += code; + } else { + PrintParseError("only escape codes < 256 allowed, got %u\n", code); + } + } else { + *out_string += c; + } + } + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseKeyStringValue(const char* key, + std::string* out_string) { + out_string->clear(); + EXPECT_KEY(key); + return ParseString(out_string); +} + +wabt::Result SpecJSONParser::ParseOptNameStringValue(std::string* out_string) { + out_string->clear(); + if (Match("\"name\"")) { + EXPECT(":"); + CHECK_RESULT(ParseString(out_string)); + EXPECT(","); + } + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseLine() { + EXPECT_KEY("line"); + CHECK_RESULT(ParseUint32(&command_line_number_)); + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseTypeObject(Type* out_type) { + std::string type_str; + EXPECT("{"); + PARSE_KEY_STRING_VALUE("type", &type_str); + EXPECT("}"); + + if (type_str == "i32") { + *out_type = Type::I32; + return wabt::Result::Ok; + } else if (type_str == "f32") { + *out_type = Type::F32; + return wabt::Result::Ok; + } else if (type_str == "i64") { + *out_type = Type::I64; + return wabt::Result::Ok; + } else if (type_str == "f64") { + *out_type = Type::F64; + return wabt::Result::Ok; + } else { + PrintParseError("unknown type: \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(type_str)); + return wabt::Result::Error; + } +} + +wabt::Result SpecJSONParser::ParseTypeVector(TypeVector* out_types) { + out_types->clear(); + EXPECT("["); + bool first = true; + while (!Match("]")) { + if (!first) + EXPECT(","); + Type type; + CHECK_RESULT(ParseTypeObject(&type)); + first = false; + out_types->push_back(type); + } + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseConst(TypedValue* out_value) { + std::string type_str; + std::string value_str; + EXPECT("{"); + PARSE_KEY_STRING_VALUE("type", &type_str); + EXPECT(","); + PARSE_KEY_STRING_VALUE("value", &value_str); + EXPECT("}"); + + const char* value_start = value_str.data(); + const char* value_end = value_str.data() + value_str.size(); + + if (type_str == "i32") { + uint32_t value; + CHECK_RESULT( + ParseInt32(value_start, value_end, &value, ParseIntType::UnsignedOnly)); + out_value->type = Type::I32; + out_value->value.i32 = value; + return wabt::Result::Ok; + } else if (type_str == "f32") { + uint32_t value_bits; + CHECK_RESULT(ParseInt32(value_start, value_end, &value_bits, + ParseIntType::UnsignedOnly)); + out_value->type = Type::F32; + out_value->value.f32_bits = value_bits; + return wabt::Result::Ok; + } else if (type_str == "i64") { + uint64_t value; + CHECK_RESULT( + ParseInt64(value_start, value_end, &value, ParseIntType::UnsignedOnly)); + out_value->type = Type::I64; + out_value->value.i64 = value; + return wabt::Result::Ok; + } else if (type_str == "f64") { + uint64_t value_bits; + CHECK_RESULT(ParseInt64(value_start, value_end, &value_bits, + ParseIntType::UnsignedOnly)); + out_value->type = Type::F64; + out_value->value.f64_bits = value_bits; + return wabt::Result::Ok; + } else { + PrintParseError("unknown type: \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(type_str)); + return wabt::Result::Error; + } +} + +wabt::Result SpecJSONParser::ParseConstVector( + std::vector<TypedValue>* out_values) { + out_values->clear(); + EXPECT("["); + bool first = true; + while (!Match("]")) { + if (!first) + EXPECT(","); + TypedValue value; + CHECK_RESULT(ParseConst(&value)); + out_values->push_back(value); + first = false; + } + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseAction(::Action* out_action) { + EXPECT_KEY("action"); + EXPECT("{"); + EXPECT_KEY("type"); + if (Match("\"invoke\"")) { + out_action->type = ::ActionType::Invoke; + } else { + EXPECT("\"get\""); + out_action->type = ::ActionType::Get; + } + EXPECT(","); + if (Match("\"module\"")) { + EXPECT(":"); + CHECK_RESULT(ParseString(&out_action->module_name)); + EXPECT(","); + } + PARSE_KEY_STRING_VALUE("field", &out_action->field_name); + if (out_action->type == ::ActionType::Invoke) { + EXPECT(","); + EXPECT_KEY("args"); + CHECK_RESULT(ParseConstVector(&out_action->args)); + } + EXPECT("}"); + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseModuleType(ModuleType* out_type) { + std::string module_type_str; + + PARSE_KEY_STRING_VALUE("module_type", &module_type_str); + if (module_type_str == "text") { + *out_type = ModuleType::Text; + return wabt::Result::Ok; + } else if (module_type_str == "binary") { + *out_type = ModuleType::Binary; + return wabt::Result::Ok; + } else { + PrintParseError("unknown module type: \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(module_type_str)); + return wabt::Result::Error; + } +} + +std::string SpecJSONParser::CreateModulePath(string_view filename) { + const char* spec_json_filename = loc_.filename; + string_view dirname = GetDirname(spec_json_filename); + std::string path; + + if (dirname.size() == 0) { + path = filename.to_string(); + } else { + path = dirname.to_string(); + path += '/'; + path += filename.to_string(); + } + + ConvertBackslashToSlash(&path); + return path; +} + +wabt::Result SpecJSONParser::OnModuleCommand(string_view filename, + string_view name) { + std::string path = CreateModulePath(filename); + Environment::MarkPoint mark = env_.Mark(); + ErrorHandlerFile error_handler(Location::Type::Binary); + wabt::Result result = + ReadModule(path.c_str(), &env_, &error_handler, &last_module_); + + if (Failed(result)) { + env_.ResetToMarkPoint(mark); + PrintCommandError("error reading module: \"%s\"", path.c_str()); + return wabt::Result::Error; + } + + interpreter::Result iresult = RunStartFunction(&thread_, last_module_); + if (iresult != interpreter::Result::Ok) { + env_.ResetToMarkPoint(mark); + PrintInterpreterResult("error running start function", iresult); + return wabt::Result::Error; + } + + if (!name.empty()) { + last_module_->name = name.to_string(); + env_.EmplaceModuleBinding(name.to_string(), + Binding(env_.GetModuleCount() - 1)); + } + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::RunAction(::Action* action, + interpreter::Result* out_iresult, + std::vector<TypedValue>* out_results, + RunVerbosity verbose) { + out_results->clear(); + + interpreter::Module* module; + if (!action->module_name.empty()) { + module = env_.FindModule(action->module_name); + } else { + module = env_.GetLastModule(); + } + assert(module); + + switch (action->type) { + case ::ActionType::Invoke: + *out_iresult = RunExportByName(&thread_, module, action->field_name, + action->args, out_results, verbose); + if (verbose == RunVerbosity::Verbose) { + PrintCall(string_view(), action->field_name, action->args, *out_results, + *out_iresult); + } + return wabt::Result::Ok; + + case ::ActionType::Get: { + *out_iresult = GetGlobalExportByName(&thread_, module, action->field_name, + out_results); + return wabt::Result::Ok; + } + + default: + PrintCommandError("invalid action type %d", + static_cast<int>(action->type)); + return wabt::Result::Error; + } +} + +wabt::Result SpecJSONParser::OnActionCommand(::Action* action) { + std::vector<TypedValue> results; + interpreter::Result iresult; + + total_++; + wabt::Result result = + RunAction(action, &iresult, &results, RunVerbosity::Verbose); + if (Succeeded(result)) { + if (iresult == interpreter::Result::Ok) { + passed_++; + } else { + PrintCommandError("unexpected trap: %s", + s_trap_strings[static_cast<size_t>(iresult)]); + result = wabt::Result::Error; + } + } + + return result; +} + +wabt::Result SpecJSONParser::ReadInvalidTextModule( + const char* module_filename, + Environment* env, + ErrorHandler* error_handler) { + std::unique_ptr<WastLexer> lexer = + WastLexer::CreateFileLexer(module_filename); + std::unique_ptr<Script> script; + wabt::Result result = ParseWastScript(lexer.get(), &script, error_handler); + if (Succeeded(result)) { + wabt::Module* module = script->GetFirstModule(); + result = ResolveNamesModule(lexer.get(), module, error_handler); + if (Succeeded(result)) { + // Don't do a full validation, just validate the function signatures. + result = ValidateFuncSignatures(lexer.get(), module, error_handler); + } + } + return result; +} + +wabt::Result SpecJSONParser::ReadInvalidModule(const char* module_filename, + Environment* env, + ModuleType module_type, + const char* desc) { + std::string header = + StringPrintf(PRIstringview ":%d: %s passed", + WABT_PRINTF_STRING_VIEW_ARG(source_filename_), + command_line_number_, desc); + + switch (module_type) { + case ModuleType::Text: { + ErrorHandlerFile error_handler(Location::Type::Text, stdout, header, + ErrorHandlerFile::PrintHeader::Once); + return ReadInvalidTextModule(module_filename, env, &error_handler); + } + + case ModuleType::Binary: { + DefinedModule* module; + ErrorHandlerFile error_handler(Location::Type::Binary, stdout, header, + ErrorHandlerFile::PrintHeader::Once); + return ReadModule(module_filename, env, &error_handler, &module); + } + } + + WABT_UNREACHABLE; +} + +wabt::Result SpecJSONParser::OnAssertMalformedCommand(string_view filename, + string_view text, + ModuleType module_type) { + Environment env; + InitEnvironment(&env); + + total_++; + std::string path = CreateModulePath(filename); + wabt::Result result = + ReadInvalidModule(path.c_str(), &env, module_type, "assert_malformed"); + if (Failed(result)) { + passed_++; + result = wabt::Result::Ok; + } else { + PrintCommandError("expected module to be malformed: \"%s\"", path.c_str()); + result = wabt::Result::Error; + } + + return result; +} + +wabt::Result SpecJSONParser::OnRegisterCommand(string_view name, + string_view as) { + Index module_index; + if (!name.empty()) { + module_index = env_.FindModuleIndex(name); + } else { + module_index = env_.GetLastModuleIndex(); + } + + if (module_index == kInvalidIndex) { + PrintCommandError("unknown module in register"); + return wabt::Result::Error; + } + + env_.EmplaceRegisteredModuleBinding(as.to_string(), Binding(module_index)); + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::OnAssertUnlinkableCommand(string_view filename, + string_view text, + ModuleType module_type) { + total_++; + std::string path = CreateModulePath(filename); + Environment::MarkPoint mark = env_.Mark(); + wabt::Result result = + ReadInvalidModule(path.c_str(), &env_, module_type, "assert_unlinkable"); + env_.ResetToMarkPoint(mark); + + if (Failed(result)) { + passed_++; + result = wabt::Result::Ok; + } else { + PrintCommandError("expected module to be unlinkable: \"%s\"", path.c_str()); + result = wabt::Result::Error; + } + + return result; +} + +wabt::Result SpecJSONParser::OnAssertInvalidCommand(string_view filename, + string_view text, + ModuleType module_type) { + Environment env; + InitEnvironment(&env); + + total_++; + std::string path = CreateModulePath(filename); + wabt::Result result = + ReadInvalidModule(path.c_str(), &env, module_type, "assert_invalid"); + if (Failed(result)) { + passed_++; + result = wabt::Result::Ok; + } else { + PrintCommandError("expected module to be invalid: \"%s\"", path.c_str()); + result = wabt::Result::Error; + } + + return result; +} + +wabt::Result SpecJSONParser::OnAssertUninstantiableCommand( + string_view filename, + string_view text, + ModuleType module_type) { + ErrorHandlerFile error_handler(Location::Type::Binary); + total_++; + std::string path = CreateModulePath(filename); + DefinedModule* module; + Environment::MarkPoint mark = env_.Mark(); + wabt::Result result = + ReadModule(path.c_str(), &env_, &error_handler, &module); + + if (Succeeded(result)) { + interpreter::Result iresult = RunStartFunction(&thread_, module); + if (iresult == interpreter::Result::Ok) { + PrintCommandError("expected error running start function: \"%s\"", + path.c_str()); + result = wabt::Result::Error; + } else { + passed_++; + result = wabt::Result::Ok; + } + } else { + PrintCommandError("error reading module: \"%s\"", path.c_str()); + result = wabt::Result::Error; + } + + env_.ResetToMarkPoint(mark); + return result; +} + +static bool TypedValuesAreEqual(const TypedValue* tv1, const TypedValue* tv2) { + if (tv1->type != tv2->type) + return false; + + switch (tv1->type) { + case Type::I32: + return tv1->value.i32 == tv2->value.i32; + case Type::F32: + return tv1->value.f32_bits == tv2->value.f32_bits; + case Type::I64: + return tv1->value.i64 == tv2->value.i64; + case Type::F64: + return tv1->value.f64_bits == tv2->value.f64_bits; + default: + WABT_UNREACHABLE; + } +} + +wabt::Result SpecJSONParser::OnAssertReturnCommand( + ::Action* action, + const std::vector<TypedValue>& expected) { + std::vector<TypedValue> results; + interpreter::Result iresult; + + total_++; + wabt::Result result = + RunAction(action, &iresult, &results, RunVerbosity::Quiet); + + if (Succeeded(result)) { + if (iresult == interpreter::Result::Ok) { + if (results.size() == expected.size()) { + for (size_t i = 0; i < results.size(); ++i) { + const TypedValue* expected_tv = &expected[i]; + const TypedValue* actual_tv = &results[i]; + if (!TypedValuesAreEqual(expected_tv, actual_tv)) { + char expected_str[MAX_TYPED_VALUE_CHARS]; + char actual_str[MAX_TYPED_VALUE_CHARS]; + SPrintTypedValue(expected_str, sizeof(expected_str), expected_tv); + SPrintTypedValue(actual_str, sizeof(actual_str), actual_tv); + PrintCommandError("mismatch in result %" PRIzd + " of assert_return: expected %s, got %s", + i, expected_str, actual_str); + result = wabt::Result::Error; + } + } + } else { + PrintCommandError( + "result length mismatch in assert_return: expected %" PRIzd + ", got %" PRIzd, + expected.size(), results.size()); + result = wabt::Result::Error; + } + } else { + PrintCommandError("unexpected trap: %s", + s_trap_strings[static_cast<size_t>(iresult)]); + result = wabt::Result::Error; + } + } + + if (Succeeded(result)) + passed_++; + + return result; +} + +wabt::Result SpecJSONParser::OnAssertReturnNanCommand(::Action* action, + bool canonical) { + std::vector<TypedValue> results; + interpreter::Result iresult; + + total_++; + wabt::Result result = + RunAction(action, &iresult, &results, RunVerbosity::Quiet); + if (Succeeded(result)) { + if (iresult == interpreter::Result::Ok) { + if (results.size() != 1) { + PrintCommandError("expected one result, got %" PRIzd, results.size()); + result = wabt::Result::Error; + } + + const TypedValue& actual = results[0]; + switch (actual.type) { + case Type::F32: { + bool is_nan = canonical ? IsCanonicalNan(actual.value.f32_bits) + : IsArithmeticNan(actual.value.f32_bits); + if (!is_nan) { + char actual_str[MAX_TYPED_VALUE_CHARS]; + SPrintTypedValue(actual_str, sizeof(actual_str), &actual); + PrintCommandError("expected result to be nan, got %s", actual_str); + result = wabt::Result::Error; + } + break; + } + + case Type::F64: { + bool is_nan = canonical ? IsCanonicalNan(actual.value.f64_bits) + : IsArithmeticNan(actual.value.f64_bits); + if (!is_nan) { + char actual_str[MAX_TYPED_VALUE_CHARS]; + SPrintTypedValue(actual_str, sizeof(actual_str), &actual); + PrintCommandError("expected result to be nan, got %s", actual_str); + result = wabt::Result::Error; + } + break; + } + + default: + PrintCommandError("expected result type to be f32 or f64, got %s", + GetTypeName(actual.type)); + result = wabt::Result::Error; + break; + } + } else { + PrintCommandError("unexpected trap: %s", + s_trap_strings[static_cast<int>(iresult)]); + result = wabt::Result::Error; + } + } + + if (Succeeded(result)) + passed_++; + + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::OnAssertTrapCommand(::Action* action, + string_view text) { + std::vector<TypedValue> results; + interpreter::Result iresult; + + total_++; + wabt::Result result = + RunAction(action, &iresult, &results, RunVerbosity::Quiet); + if (Succeeded(result)) { + if (iresult != interpreter::Result::Ok) { + passed_++; + } else { + PrintCommandError("expected trap: \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(text)); + result = wabt::Result::Error; + } + } + + return result; +} + +wabt::Result SpecJSONParser::OnAssertExhaustionCommand(::Action* action) { + std::vector<TypedValue> results; + interpreter::Result iresult; + + total_++; + wabt::Result result = + RunAction(action, &iresult, &results, RunVerbosity::Quiet); + if (Succeeded(result)) { + if (iresult == interpreter::Result::TrapCallStackExhausted || + iresult == interpreter::Result::TrapValueStackExhausted) { + passed_++; + } else { + PrintCommandError("expected call stack exhaustion"); + result = wabt::Result::Error; + } + } + + return result; +} + +wabt::Result SpecJSONParser::ParseCommand() { + EXPECT("{"); + EXPECT_KEY("type"); + if (Match("\"module\"")) { + std::string name; + std::string filename; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseOptNameStringValue(&name)); + PARSE_KEY_STRING_VALUE("filename", &filename); + OnModuleCommand(filename, name); + } else if (Match("\"action\"")) { + ::Action action; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseAction(&action)); + OnActionCommand(&action); + } else if (Match("\"register\"")) { + std::string as; + std::string name; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseOptNameStringValue(&name)); + PARSE_KEY_STRING_VALUE("as", &as); + OnRegisterCommand(name, as); + } else if (Match("\"assert_malformed\"")) { + std::string filename; + std::string text; + ModuleType module_type; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + EXPECT(","); + CHECK_RESULT(ParseModuleType(&module_type)); + OnAssertMalformedCommand(filename, text, module_type); + } else if (Match("\"assert_invalid\"")) { + std::string filename; + std::string text; + ModuleType module_type; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + EXPECT(","); + CHECK_RESULT(ParseModuleType(&module_type)); + OnAssertInvalidCommand(filename, text, module_type); + } else if (Match("\"assert_unlinkable\"")) { + std::string filename; + std::string text; + ModuleType module_type; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + EXPECT(","); + CHECK_RESULT(ParseModuleType(&module_type)); + OnAssertUnlinkableCommand(filename, text, module_type); + } else if (Match("\"assert_uninstantiable\"")) { + std::string filename; + std::string text; + ModuleType module_type; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + EXPECT(","); + CHECK_RESULT(ParseModuleType(&module_type)); + OnAssertUninstantiableCommand(filename, text, module_type); + } else if (Match("\"assert_return\"")) { + ::Action action; + std::vector<TypedValue> expected; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseAction(&action)); + EXPECT(","); + EXPECT_KEY("expected"); + CHECK_RESULT(ParseConstVector(&expected)); + OnAssertReturnCommand(&action, expected); + } else if (Match("\"assert_return_canonical_nan\"")) { + ::Action action; + TypeVector expected; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseAction(&action)); + EXPECT(","); + /* Not needed for wabt-interp, but useful for other parsers. */ + EXPECT_KEY("expected"); + CHECK_RESULT(ParseTypeVector(&expected)); + OnAssertReturnNanCommand(&action, true); + } else if (Match("\"assert_return_arithmetic_nan\"")) { + ::Action action; + TypeVector expected; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseAction(&action)); + EXPECT(","); + /* Not needed for wabt-interp, but useful for other parsers. */ + EXPECT_KEY("expected"); + CHECK_RESULT(ParseTypeVector(&expected)); + OnAssertReturnNanCommand(&action, false); + } else if (Match("\"assert_trap\"")) { + ::Action action; + std::string text; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseAction(&action)); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + OnAssertTrapCommand(&action, text); + } else if (Match("\"assert_exhaustion\"")) { + ::Action action; + std::string text; + + EXPECT(","); + CHECK_RESULT(ParseLine()); + EXPECT(","); + CHECK_RESULT(ParseAction(&action)); + OnAssertExhaustionCommand(&action); + } else { + PrintCommandError("unknown command type"); + return wabt::Result::Error; + } + EXPECT("}"); + return wabt::Result::Ok; +} + +wabt::Result SpecJSONParser::ParseCommands() { + EXPECT("{"); + PARSE_KEY_STRING_VALUE("source_filename", &source_filename_); + EXPECT(","); + EXPECT_KEY("commands"); + EXPECT("["); + bool first = true; + while (!Match("]")) { + if (!first) + EXPECT(","); + CHECK_RESULT(ParseCommand()); + first = false; + } + EXPECT("}"); + return wabt::Result::Ok; +} + +static wabt::Result ReadAndRunSpecJSON(const char* spec_json_filename) { + SpecJSONParser parser; + CHECK_RESULT(parser.ReadFile(spec_json_filename)); + wabt::Result result = parser.ParseCommands(); + printf("%d/%d tests passed.\n", parser.passed(), parser.total()); + return result; +} + +int ProgramMain(int argc, char** argv) { + InitStdio(); + ParseOptions(argc, argv); + + s_stdout_stream = FileStream::CreateStdout(); + + wabt::Result result; + result = ReadAndRunSpecJSON(s_infile); + return result != wabt::Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/src/tools/wasm-interp.cc b/src/tools/wasm-interp.cc index 1c4cf958..00ec38ae 100644 --- a/src/tools/wasm-interp.cc +++ b/src/tools/wasm-interp.cc @@ -48,8 +48,8 @@ static int s_verbose; static const char* s_infile; static Thread::Options s_thread_options; static bool s_trace; -static bool s_spec; static bool s_run_all_exports; +static bool s_host_print; static Features s_features; static std::unique_ptr<FileStream> s_log_stream; @@ -74,9 +74,6 @@ examples: # parse test.wasm, run the exported functions and trace the output $ wasm-interp test.wasm --run-all-exports --trace - # parse test.json and run the spec tests - $ wasm-interp test.json --spec - # parse test.wasm and run all its exported functions, setting the # value stack size to 100 elements $ wasm-interp test.wasm -V 100 --run-all-exports @@ -104,42 +101,21 @@ static void ParseOptions(int argc, char** argv) { s_thread_options.call_stack_size = atoi(argument.c_str()); }); parser.AddOption('t', "trace", "Trace execution", []() { s_trace = true; }); - parser.AddOption("spec", "Run spec tests (input file should be .json)", - []() { s_spec = true; }); parser.AddOption( "run-all-exports", "Run all the exported functions, in order. Useful for testing", []() { s_run_all_exports = true; }); + parser.AddOption("host-print", + "Include an importable function named \"host.print\" for " + "printing to stdout", + []() { s_host_print = true; }); parser.AddArgument("filename", OptionParser::ArgumentCount::One, [](const char* argument) { s_infile = argument; }); parser.Parse(argc, argv); - - if (s_spec && s_run_all_exports) - WABT_FATAL("--spec and --run-all-exports are incompatible.\n"); } -enum class ModuleType { - Text, - Binary, -}; - -static string_view GetDirname(string_view path) { - // Strip everything after and including the last slash (or backslash), e.g.: - // - // s = "foo/bar/baz", => "foo/bar" - // s = "/usr/local/include/stdio.h", => "/usr/local/include" - // s = "foo.bar", => "" - // s = "some\windows\directory", => "some\windows" - size_t last_slash = path.find_last_of('/'); - size_t last_backslash = path.find_last_of('\\'); - if (last_slash == string_view::npos) - last_slash = 0; - if (last_backslash == string_view::npos) - last_backslash = 0; - - return path.substr(0, std::max(last_slash, last_backslash)); -} +// TODO(binji): Share these helper functions w/ spectest-interp as well. /* Not sure, but 100 chars is probably safe */ #define MAX_TYPED_VALUE_CHARS 100 @@ -250,37 +226,6 @@ static interpreter::Result RunExport(Thread* thread, return RunFunction(thread, export_->index, args, out_results); } -static interpreter::Result RunExportByName(Thread* thread, - interpreter::Module* module, - string_view name, - const std::vector<TypedValue>& args, - std::vector<TypedValue>* out_results, - RunVerbosity verbose) { - interpreter::Export* export_ = module->GetExport(name); - if (!export_) - return interpreter::Result::UnknownExport; - if (export_->kind != ExternalKind::Func) - return interpreter::Result::ExportKindMismatch; - return RunExport(thread, export_, args, out_results); -} - -static interpreter::Result GetGlobalExportByName( - Thread* thread, - interpreter::Module* module, - string_view name, - std::vector<TypedValue>* out_results) { - interpreter::Export* export_ = module->GetExport(name); - if (!export_) - return interpreter::Result::UnknownExport; - if (export_->kind != ExternalKind::Global) - return interpreter::Result::ExportKindMismatch; - - interpreter::Global* global = thread->env()->GetGlobal(export_->index); - out_results->clear(); - out_results->push_back(global->typed_value); - return interpreter::Result::Ok; -} - static void RunAllExports(interpreter::Module* module, Thread* thread, RunVerbosity verbose) { @@ -320,40 +265,19 @@ static wabt::Result ReadModule(const char* module_filename, return result; } -static interpreter::Result DefaultHostCallback( - const HostFunc* func, - const interpreter::FuncSignature* sig, - Index num_args, - TypedValue* args, - Index num_results, - TypedValue* out_results, - void* user_data) { - memset(out_results, 0, sizeof(TypedValue) * num_results); - for (Index i = 0; i < num_results; ++i) - out_results[i].type = sig->result_types[i]; - - std::vector<TypedValue> vec_args(args, args + num_args); - std::vector<TypedValue> vec_results(out_results, out_results + num_results); - - printf("called host "); - PrintCall(func->module_name, func->field_name, vec_args, vec_results, - interpreter::Result::Ok); - return interpreter::Result::Ok; -} - #define PRIimport "\"" PRIstringview "." PRIstringview "\"" #define PRINTF_IMPORT_ARG(x) \ WABT_PRINTF_STRING_VIEW_ARG((x).module_name) \ , WABT_PRINTF_STRING_VIEW_ARG((x).field_name) -class SpectestHostImportDelegate : public HostImportDelegate { +class WasmInterpHostImportDelegate : public HostImportDelegate { public: wabt::Result ImportFunc(interpreter::FuncImport* import, interpreter::Func* func, interpreter::FuncSignature* func_sig, const ErrorCallback& callback) override { if (import->field_name == "print") { - cast<HostFunc>(func)->callback = DefaultHostCallback; + cast<HostFunc>(func)->callback = PrintCallback; return wabt::Result::Ok; } else { PrintError(callback, "unknown host function import " PRIimport, @@ -365,74 +289,43 @@ class SpectestHostImportDelegate : public HostImportDelegate { wabt::Result ImportTable(interpreter::TableImport* import, interpreter::Table* table, const ErrorCallback& callback) override { - if (import->field_name == "table") { - table->limits.has_max = true; - table->limits.initial = 10; - table->limits.max = 20; - return wabt::Result::Ok; - } else { - PrintError(callback, "unknown host table import " PRIimport, - PRINTF_IMPORT_ARG(*import)); - return wabt::Result::Error; - } + return wabt::Result::Error; } wabt::Result ImportMemory(interpreter::MemoryImport* import, interpreter::Memory* memory, const ErrorCallback& callback) override { - if (import->field_name == "memory") { - memory->page_limits.has_max = true; - memory->page_limits.initial = 1; - memory->page_limits.max = 2; - memory->data.resize(memory->page_limits.initial * WABT_MAX_PAGES); - return wabt::Result::Ok; - } else { - PrintError(callback, "unknown host memory import " PRIimport, - PRINTF_IMPORT_ARG(*import)); - return wabt::Result::Error; - } + return wabt::Result::Error; } wabt::Result ImportGlobal(interpreter::GlobalImport* import, interpreter::Global* global, const ErrorCallback& callback) override { - if (import->field_name == "global") { - switch (global->typed_value.type) { - case Type::I32: - global->typed_value.value.i32 = 666; - break; - - case Type::F32: { - float value = 666.6f; - memcpy(&global->typed_value.value.f32_bits, &value, sizeof(value)); - break; - } - - case Type::I64: - global->typed_value.value.i64 = 666; - break; - - case Type::F64: { - double value = 666.6; - memcpy(&global->typed_value.value.f64_bits, &value, sizeof(value)); - break; - } - - default: - PrintError(callback, "bad type for host global import " PRIimport, - PRINTF_IMPORT_ARG(*import)); - return wabt::Result::Error; - } - - return wabt::Result::Ok; - } else { - PrintError(callback, "unknown host global import " PRIimport, - PRINTF_IMPORT_ARG(*import)); - return wabt::Result::Error; - } + return wabt::Result::Error; } private: + static interpreter::Result PrintCallback( + const HostFunc* func, + const interpreter::FuncSignature* sig, + Index num_args, + TypedValue* args, + Index num_results, + TypedValue* out_results, + void* user_data) { + memset(out_results, 0, sizeof(TypedValue) * num_results); + for (Index i = 0; i < num_results; ++i) + out_results[i].type = sig->result_types[i]; + + std::vector<TypedValue> vec_args(args, args + num_args); + std::vector<TypedValue> vec_results(out_results, out_results + num_results); + + printf("called host "); + PrintCall(func->module_name, func->field_name, vec_args, vec_results, + interpreter::Result::Ok); + return interpreter::Result::Ok; + } + void PrintError(const ErrorCallback& callback, const char* format, ...) { WABT_SNPRINTF_ALLOCA(buffer, length, format); callback(buffer); @@ -440,8 +333,10 @@ class SpectestHostImportDelegate : public HostImportDelegate { }; static void InitEnvironment(Environment* env) { - HostModule* host_module = env->AppendHostModule("spectest"); - host_module->import_delegate.reset(new SpectestHostImportDelegate()); + if (s_host_print) { + HostModule* host_module = env->AppendHostModule("host"); + host_module->import_delegate.reset(new WasmInterpHostImportDelegate()); + } } static wabt::Result ReadAndRunModule(const char* module_filename) { @@ -465,1064 +360,13 @@ static wabt::Result ReadAndRunModule(const char* module_filename) { return result; } -enum class ActionType { - Invoke, - Get, -}; - -struct Action { - ::ActionType type = ::ActionType::Invoke; - std::string module_name; - std::string field_name; - std::vector<TypedValue> args; -}; - -// An extremely simple JSON parser that only knows how to parse the expected -// format from wat2wasm. -class SpecJSONParser { - public: - SpecJSONParser() : thread_(&env_, s_thread_options) {} - - wabt::Result ReadFile(const char* spec_json_filename); - wabt::Result ParseCommands(); - - int passed() const { return passed_; } - int total() const { return total_; } - - private: - void WABT_PRINTF_FORMAT(2, 3) PrintParseError(const char* format, ...); - void WABT_PRINTF_FORMAT(2, 3) PrintCommandError(const char* format, ...); - - void PutbackChar(); - int ReadChar(); - void SkipWhitespace(); - bool Match(const char* s); - wabt::Result Expect(const char* s); - wabt::Result ExpectKey(const char* key); - wabt::Result ParseUint32(uint32_t* out_int); - wabt::Result ParseString(std::string* out_string); - wabt::Result ParseKeyStringValue(const char* key, std::string* out_string); - wabt::Result ParseOptNameStringValue(std::string* out_string); - wabt::Result ParseLine(); - wabt::Result ParseTypeObject(Type* out_type); - wabt::Result ParseTypeVector(TypeVector* out_types); - wabt::Result ParseConst(TypedValue* out_value); - wabt::Result ParseConstVector(std::vector<TypedValue>* out_values); - wabt::Result ParseAction(::Action* out_action); - wabt::Result ParseModuleType(ModuleType* out_type); - - std::string CreateModulePath(string_view filename); - - wabt::Result OnModuleCommand(string_view filename, string_view name); - wabt::Result RunAction(::Action* action, - interpreter::Result* out_iresult, - std::vector<TypedValue>* out_results, - RunVerbosity verbose); - wabt::Result OnActionCommand(::Action* action); - wabt::Result ReadInvalidTextModule(const char* module_filename, - Environment* env, - ErrorHandler* error_handler); - wabt::Result ReadInvalidModule(const char* module_filename, - Environment* env, - ModuleType module_type, - const char* desc); - wabt::Result OnAssertMalformedCommand(string_view filename, - string_view text, - ModuleType module_type); - wabt::Result OnRegisterCommand(string_view name, string_view as); - wabt::Result OnAssertUnlinkableCommand(string_view filename, - string_view text, - ModuleType module_type); - wabt::Result OnAssertInvalidCommand(string_view filename, - string_view text, - ModuleType module_type); - wabt::Result OnAssertUninstantiableCommand(string_view filename, - string_view text, - ModuleType module_type); - wabt::Result OnAssertReturnCommand(::Action* action, - const std::vector<TypedValue>& expected); - wabt::Result OnAssertReturnNanCommand(::Action* action, bool canonical); - wabt::Result OnAssertTrapCommand(::Action* action, string_view text); - wabt::Result OnAssertExhaustionCommand(::Action* action); - wabt::Result ParseCommand(); - - Environment env_; - Thread thread_; - DefinedModule* last_module_ = nullptr; - - // Parsing info. - std::vector<uint8_t> json_data_; - std::string source_filename_; - size_t json_offset_ = 0; - Location loc_; - Location prev_loc_; - bool has_prev_loc_ = false; - uint32_t command_line_number_ = 0; - - // Test info. - int passed_ = 0; - int total_ = 0; -}; - -#define EXPECT(x) CHECK_RESULT(Expect(x)) -#define EXPECT_KEY(x) CHECK_RESULT(ExpectKey(x)) -#define PARSE_KEY_STRING_VALUE(key, value) \ - CHECK_RESULT(ParseKeyStringValue(key, value)) - -wabt::Result SpecJSONParser::ReadFile(const char* spec_json_filename) { - loc_.filename = spec_json_filename; - loc_.line = 1; - loc_.first_column = 1; - InitEnvironment(&env_); - - return wabt::ReadFile(spec_json_filename, &json_data_); -} - -void SpecJSONParser::PrintParseError(const char* format, ...) { - WABT_SNPRINTF_ALLOCA(buffer, length, format); - fprintf(stderr, "%s:%d:%d: %s\n", loc_.filename, loc_.line, loc_.first_column, - buffer); -} - -void SpecJSONParser::PrintCommandError(const char* format, ...) { - WABT_SNPRINTF_ALLOCA(buffer, length, format); - printf(PRIstringview ":%u: %s\n", - WABT_PRINTF_STRING_VIEW_ARG(source_filename_), command_line_number_, - buffer); -} - -void SpecJSONParser::PutbackChar() { - assert(has_prev_loc_); - json_offset_--; - loc_ = prev_loc_; - has_prev_loc_ = false; -} - -int SpecJSONParser::ReadChar() { - if (json_offset_ >= json_data_.size()) - return -1; - prev_loc_ = loc_; - char c = json_data_[json_offset_++]; - if (c == '\n') { - loc_.line++; - loc_.first_column = 1; - } else { - loc_.first_column++; - } - has_prev_loc_ = true; - return c; -} - -void SpecJSONParser::SkipWhitespace() { - while (1) { - switch (ReadChar()) { - case -1: - return; - - case ' ': - case '\t': - case '\n': - case '\r': - break; - - default: - PutbackChar(); - return; - } - } -} - -bool SpecJSONParser::Match(const char* s) { - SkipWhitespace(); - Location start_loc = loc_; - size_t start_offset = json_offset_; - while (*s && *s == ReadChar()) - s++; - - if (*s == 0) { - return true; - } else { - json_offset_ = start_offset; - loc_ = start_loc; - return false; - } -} - -wabt::Result SpecJSONParser::Expect(const char* s) { - if (Match(s)) { - return wabt::Result::Ok; - } else { - PrintParseError("expected %s", s); - return wabt::Result::Error; - } -} - -wabt::Result SpecJSONParser::ExpectKey(const char* key) { - size_t keylen = strlen(key); - size_t quoted_len = keylen + 2 + 1; - char* quoted = static_cast<char*>(alloca(quoted_len)); - snprintf(quoted, quoted_len, "\"%s\"", key); - EXPECT(quoted); - EXPECT(":"); - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseUint32(uint32_t* out_int) { - uint32_t result = 0; - SkipWhitespace(); - while (1) { - int c = ReadChar(); - if (c >= '0' && c <= '9') { - uint32_t last_result = result; - result = result * 10 + static_cast<uint32_t>(c - '0'); - if (result < last_result) { - PrintParseError("uint32 overflow"); - return wabt::Result::Error; - } - } else { - PutbackChar(); - break; - } - } - *out_int = result; - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseString(std::string* out_string) { - out_string->clear(); - - SkipWhitespace(); - if (ReadChar() != '"') { - PrintParseError("expected string"); - return wabt::Result::Error; - } - - while (1) { - int c = ReadChar(); - if (c == '"') { - break; - } else if (c == '\\') { - /* The only escape supported is \uxxxx. */ - c = ReadChar(); - if (c != 'u') { - PrintParseError("expected escape: \\uxxxx"); - return wabt::Result::Error; - } - uint16_t code = 0; - for (int i = 0; i < 4; ++i) { - c = ReadChar(); - int cval; - if (c >= '0' && c <= '9') { - cval = c - '0'; - } else if (c >= 'a' && c <= 'f') { - cval = c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - cval = c - 'A' + 10; - } else { - PrintParseError("expected hex char"); - return wabt::Result::Error; - } - code = (code << 4) + cval; - } - - if (code < 256) { - *out_string += code; - } else { - PrintParseError("only escape codes < 256 allowed, got %u\n", code); - } - } else { - *out_string += c; - } - } - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseKeyStringValue(const char* key, - std::string* out_string) { - out_string->clear(); - EXPECT_KEY(key); - return ParseString(out_string); -} - -wabt::Result SpecJSONParser::ParseOptNameStringValue(std::string* out_string) { - out_string->clear(); - if (Match("\"name\"")) { - EXPECT(":"); - CHECK_RESULT(ParseString(out_string)); - EXPECT(","); - } - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseLine() { - EXPECT_KEY("line"); - CHECK_RESULT(ParseUint32(&command_line_number_)); - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseTypeObject(Type* out_type) { - std::string type_str; - EXPECT("{"); - PARSE_KEY_STRING_VALUE("type", &type_str); - EXPECT("}"); - - if (type_str == "i32") { - *out_type = Type::I32; - return wabt::Result::Ok; - } else if (type_str == "f32") { - *out_type = Type::F32; - return wabt::Result::Ok; - } else if (type_str == "i64") { - *out_type = Type::I64; - return wabt::Result::Ok; - } else if (type_str == "f64") { - *out_type = Type::F64; - return wabt::Result::Ok; - } else { - PrintParseError("unknown type: \"" PRIstringview "\"", - WABT_PRINTF_STRING_VIEW_ARG(type_str)); - return wabt::Result::Error; - } -} - -wabt::Result SpecJSONParser::ParseTypeVector(TypeVector* out_types) { - out_types->clear(); - EXPECT("["); - bool first = true; - while (!Match("]")) { - if (!first) - EXPECT(","); - Type type; - CHECK_RESULT(ParseTypeObject(&type)); - first = false; - out_types->push_back(type); - } - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseConst(TypedValue* out_value) { - std::string type_str; - std::string value_str; - EXPECT("{"); - PARSE_KEY_STRING_VALUE("type", &type_str); - EXPECT(","); - PARSE_KEY_STRING_VALUE("value", &value_str); - EXPECT("}"); - - const char* value_start = value_str.data(); - const char* value_end = value_str.data() + value_str.size(); - - if (type_str == "i32") { - uint32_t value; - CHECK_RESULT( - ParseInt32(value_start, value_end, &value, ParseIntType::UnsignedOnly)); - out_value->type = Type::I32; - out_value->value.i32 = value; - return wabt::Result::Ok; - } else if (type_str == "f32") { - uint32_t value_bits; - CHECK_RESULT(ParseInt32(value_start, value_end, &value_bits, - ParseIntType::UnsignedOnly)); - out_value->type = Type::F32; - out_value->value.f32_bits = value_bits; - return wabt::Result::Ok; - } else if (type_str == "i64") { - uint64_t value; - CHECK_RESULT( - ParseInt64(value_start, value_end, &value, ParseIntType::UnsignedOnly)); - out_value->type = Type::I64; - out_value->value.i64 = value; - return wabt::Result::Ok; - } else if (type_str == "f64") { - uint64_t value_bits; - CHECK_RESULT(ParseInt64(value_start, value_end, &value_bits, - ParseIntType::UnsignedOnly)); - out_value->type = Type::F64; - out_value->value.f64_bits = value_bits; - return wabt::Result::Ok; - } else { - PrintParseError("unknown type: \"" PRIstringview "\"", - WABT_PRINTF_STRING_VIEW_ARG(type_str)); - return wabt::Result::Error; - } -} - -wabt::Result SpecJSONParser::ParseConstVector( - std::vector<TypedValue>* out_values) { - out_values->clear(); - EXPECT("["); - bool first = true; - while (!Match("]")) { - if (!first) - EXPECT(","); - TypedValue value; - CHECK_RESULT(ParseConst(&value)); - out_values->push_back(value); - first = false; - } - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseAction(::Action* out_action) { - EXPECT_KEY("action"); - EXPECT("{"); - EXPECT_KEY("type"); - if (Match("\"invoke\"")) { - out_action->type = ::ActionType::Invoke; - } else { - EXPECT("\"get\""); - out_action->type = ::ActionType::Get; - } - EXPECT(","); - if (Match("\"module\"")) { - EXPECT(":"); - CHECK_RESULT(ParseString(&out_action->module_name)); - EXPECT(","); - } - PARSE_KEY_STRING_VALUE("field", &out_action->field_name); - if (out_action->type == ::ActionType::Invoke) { - EXPECT(","); - EXPECT_KEY("args"); - CHECK_RESULT(ParseConstVector(&out_action->args)); - } - EXPECT("}"); - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseModuleType(ModuleType* out_type) { - std::string module_type_str; - - PARSE_KEY_STRING_VALUE("module_type", &module_type_str); - if (module_type_str == "text") { - *out_type = ModuleType::Text; - return wabt::Result::Ok; - } else if (module_type_str == "binary") { - *out_type = ModuleType::Binary; - return wabt::Result::Ok; - } else { - PrintParseError("unknown module type: \"" PRIstringview "\"", - WABT_PRINTF_STRING_VIEW_ARG(module_type_str)); - return wabt::Result::Error; - } -} - -std::string SpecJSONParser::CreateModulePath(string_view filename) { - const char* spec_json_filename = loc_.filename; - string_view dirname = GetDirname(spec_json_filename); - std::string path; - - if (dirname.size() == 0) { - path = filename.to_string(); - } else { - path = dirname.to_string(); - path += '/'; - path += filename.to_string(); - } - - ConvertBackslashToSlash(&path); - return path; -} - -wabt::Result SpecJSONParser::OnModuleCommand(string_view filename, - string_view name) { - std::string path = CreateModulePath(filename); - Environment::MarkPoint mark = env_.Mark(); - ErrorHandlerFile error_handler(Location::Type::Binary); - wabt::Result result = - ReadModule(path.c_str(), &env_, &error_handler, &last_module_); - - if (Failed(result)) { - env_.ResetToMarkPoint(mark); - PrintCommandError("error reading module: \"%s\"", path.c_str()); - return wabt::Result::Error; - } - - interpreter::Result iresult = RunStartFunction(&thread_, last_module_); - if (iresult != interpreter::Result::Ok) { - env_.ResetToMarkPoint(mark); - PrintInterpreterResult("error running start function", iresult); - return wabt::Result::Error; - } - - if (!name.empty()) { - last_module_->name = name.to_string(); - env_.EmplaceModuleBinding(name.to_string(), - Binding(env_.GetModuleCount() - 1)); - } - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::RunAction(::Action* action, - interpreter::Result* out_iresult, - std::vector<TypedValue>* out_results, - RunVerbosity verbose) { - out_results->clear(); - - interpreter::Module* module; - if (!action->module_name.empty()) { - module = env_.FindModule(action->module_name); - } else { - module = env_.GetLastModule(); - } - assert(module); - - switch (action->type) { - case ::ActionType::Invoke: - *out_iresult = RunExportByName(&thread_, module, action->field_name, - action->args, out_results, verbose); - if (verbose == RunVerbosity::Verbose) { - PrintCall(string_view(), action->field_name, action->args, *out_results, - *out_iresult); - } - return wabt::Result::Ok; - - case ::ActionType::Get: { - *out_iresult = GetGlobalExportByName(&thread_, module, action->field_name, - out_results); - return wabt::Result::Ok; - } - - default: - PrintCommandError("invalid action type %d", - static_cast<int>(action->type)); - return wabt::Result::Error; - } -} - -wabt::Result SpecJSONParser::OnActionCommand(::Action* action) { - std::vector<TypedValue> results; - interpreter::Result iresult; - - total_++; - wabt::Result result = - RunAction(action, &iresult, &results, RunVerbosity::Verbose); - if (Succeeded(result)) { - if (iresult == interpreter::Result::Ok) { - passed_++; - } else { - PrintCommandError("unexpected trap: %s", - s_trap_strings[static_cast<size_t>(iresult)]); - result = wabt::Result::Error; - } - } - - return result; -} - -wabt::Result SpecJSONParser::ReadInvalidTextModule( - const char* module_filename, - Environment* env, - ErrorHandler* error_handler) { - std::unique_ptr<WastLexer> lexer = - WastLexer::CreateFileLexer(module_filename); - std::unique_ptr<Script> script; - wabt::Result result = ParseWastScript(lexer.get(), &script, error_handler); - if (Succeeded(result)) { - wabt::Module* module = script->GetFirstModule(); - result = ResolveNamesModule(lexer.get(), module, error_handler); - if (Succeeded(result)) { - // Don't do a full validation, just validate the function signatures. - result = ValidateFuncSignatures(lexer.get(), module, error_handler); - } - } - return result; -} - -wabt::Result SpecJSONParser::ReadInvalidModule(const char* module_filename, - Environment* env, - ModuleType module_type, - const char* desc) { - std::string header = - StringPrintf(PRIstringview ":%d: %s passed", - WABT_PRINTF_STRING_VIEW_ARG(source_filename_), - command_line_number_, desc); - - switch (module_type) { - case ModuleType::Text: { - ErrorHandlerFile error_handler(Location::Type::Text, stdout, header, - ErrorHandlerFile::PrintHeader::Once); - return ReadInvalidTextModule(module_filename, env, &error_handler); - } - - case ModuleType::Binary: { - DefinedModule* module; - ErrorHandlerFile error_handler(Location::Type::Binary, stdout, header, - ErrorHandlerFile::PrintHeader::Once); - return ReadModule(module_filename, env, &error_handler, &module); - } - } - - WABT_UNREACHABLE; -} - -wabt::Result SpecJSONParser::OnAssertMalformedCommand(string_view filename, - string_view text, - ModuleType module_type) { - Environment env; - InitEnvironment(&env); - - total_++; - std::string path = CreateModulePath(filename); - wabt::Result result = - ReadInvalidModule(path.c_str(), &env, module_type, "assert_malformed"); - if (Failed(result)) { - passed_++; - result = wabt::Result::Ok; - } else { - PrintCommandError("expected module to be malformed: \"%s\"", path.c_str()); - result = wabt::Result::Error; - } - - return result; -} - -wabt::Result SpecJSONParser::OnRegisterCommand(string_view name, - string_view as) { - Index module_index; - if (!name.empty()) { - module_index = env_.FindModuleIndex(name); - } else { - module_index = env_.GetLastModuleIndex(); - } - - if (module_index == kInvalidIndex) { - PrintCommandError("unknown module in register"); - return wabt::Result::Error; - } - - env_.EmplaceRegisteredModuleBinding(as.to_string(), Binding(module_index)); - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::OnAssertUnlinkableCommand(string_view filename, - string_view text, - ModuleType module_type) { - total_++; - std::string path = CreateModulePath(filename); - Environment::MarkPoint mark = env_.Mark(); - wabt::Result result = - ReadInvalidModule(path.c_str(), &env_, module_type, "assert_unlinkable"); - env_.ResetToMarkPoint(mark); - - if (Failed(result)) { - passed_++; - result = wabt::Result::Ok; - } else { - PrintCommandError("expected module to be unlinkable: \"%s\"", path.c_str()); - result = wabt::Result::Error; - } - - return result; -} - -wabt::Result SpecJSONParser::OnAssertInvalidCommand(string_view filename, - string_view text, - ModuleType module_type) { - Environment env; - InitEnvironment(&env); - - total_++; - std::string path = CreateModulePath(filename); - wabt::Result result = - ReadInvalidModule(path.c_str(), &env, module_type, "assert_invalid"); - if (Failed(result)) { - passed_++; - result = wabt::Result::Ok; - } else { - PrintCommandError("expected module to be invalid: \"%s\"", path.c_str()); - result = wabt::Result::Error; - } - - return result; -} - -wabt::Result SpecJSONParser::OnAssertUninstantiableCommand( - string_view filename, - string_view text, - ModuleType module_type) { - ErrorHandlerFile error_handler(Location::Type::Binary); - total_++; - std::string path = CreateModulePath(filename); - DefinedModule* module; - Environment::MarkPoint mark = env_.Mark(); - wabt::Result result = - ReadModule(path.c_str(), &env_, &error_handler, &module); - - if (Succeeded(result)) { - interpreter::Result iresult = RunStartFunction(&thread_, module); - if (iresult == interpreter::Result::Ok) { - PrintCommandError("expected error running start function: \"%s\"", - path.c_str()); - result = wabt::Result::Error; - } else { - passed_++; - result = wabt::Result::Ok; - } - } else { - PrintCommandError("error reading module: \"%s\"", path.c_str()); - result = wabt::Result::Error; - } - - env_.ResetToMarkPoint(mark); - return result; -} - -static bool TypedValuesAreEqual(const TypedValue* tv1, const TypedValue* tv2) { - if (tv1->type != tv2->type) - return false; - - switch (tv1->type) { - case Type::I32: - return tv1->value.i32 == tv2->value.i32; - case Type::F32: - return tv1->value.f32_bits == tv2->value.f32_bits; - case Type::I64: - return tv1->value.i64 == tv2->value.i64; - case Type::F64: - return tv1->value.f64_bits == tv2->value.f64_bits; - default: - WABT_UNREACHABLE; - } -} - -wabt::Result SpecJSONParser::OnAssertReturnCommand( - ::Action* action, - const std::vector<TypedValue>& expected) { - std::vector<TypedValue> results; - interpreter::Result iresult; - - total_++; - wabt::Result result = - RunAction(action, &iresult, &results, RunVerbosity::Quiet); - - if (Succeeded(result)) { - if (iresult == interpreter::Result::Ok) { - if (results.size() == expected.size()) { - for (size_t i = 0; i < results.size(); ++i) { - const TypedValue* expected_tv = &expected[i]; - const TypedValue* actual_tv = &results[i]; - if (!TypedValuesAreEqual(expected_tv, actual_tv)) { - char expected_str[MAX_TYPED_VALUE_CHARS]; - char actual_str[MAX_TYPED_VALUE_CHARS]; - SPrintTypedValue(expected_str, sizeof(expected_str), expected_tv); - SPrintTypedValue(actual_str, sizeof(actual_str), actual_tv); - PrintCommandError("mismatch in result %" PRIzd - " of assert_return: expected %s, got %s", - i, expected_str, actual_str); - result = wabt::Result::Error; - } - } - } else { - PrintCommandError( - "result length mismatch in assert_return: expected %" PRIzd - ", got %" PRIzd, - expected.size(), results.size()); - result = wabt::Result::Error; - } - } else { - PrintCommandError("unexpected trap: %s", - s_trap_strings[static_cast<size_t>(iresult)]); - result = wabt::Result::Error; - } - } - - if (Succeeded(result)) - passed_++; - - return result; -} - -wabt::Result SpecJSONParser::OnAssertReturnNanCommand(::Action* action, - bool canonical) { - std::vector<TypedValue> results; - interpreter::Result iresult; - - total_++; - wabt::Result result = - RunAction(action, &iresult, &results, RunVerbosity::Quiet); - if (Succeeded(result)) { - if (iresult == interpreter::Result::Ok) { - if (results.size() != 1) { - PrintCommandError("expected one result, got %" PRIzd, results.size()); - result = wabt::Result::Error; - } - - const TypedValue& actual = results[0]; - switch (actual.type) { - case Type::F32: { - bool is_nan = canonical ? IsCanonicalNan(actual.value.f32_bits) - : IsArithmeticNan(actual.value.f32_bits); - if (!is_nan) { - char actual_str[MAX_TYPED_VALUE_CHARS]; - SPrintTypedValue(actual_str, sizeof(actual_str), &actual); - PrintCommandError("expected result to be nan, got %s", actual_str); - result = wabt::Result::Error; - } - break; - } - - case Type::F64: { - bool is_nan = canonical ? IsCanonicalNan(actual.value.f64_bits) - : IsArithmeticNan(actual.value.f64_bits); - if (!is_nan) { - char actual_str[MAX_TYPED_VALUE_CHARS]; - SPrintTypedValue(actual_str, sizeof(actual_str), &actual); - PrintCommandError("expected result to be nan, got %s", actual_str); - result = wabt::Result::Error; - } - break; - } - - default: - PrintCommandError("expected result type to be f32 or f64, got %s", - GetTypeName(actual.type)); - result = wabt::Result::Error; - break; - } - } else { - PrintCommandError("unexpected trap: %s", - s_trap_strings[static_cast<int>(iresult)]); - result = wabt::Result::Error; - } - } - - if (Succeeded(result)) - passed_++; - - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::OnAssertTrapCommand(::Action* action, - string_view text) { - std::vector<TypedValue> results; - interpreter::Result iresult; - - total_++; - wabt::Result result = - RunAction(action, &iresult, &results, RunVerbosity::Quiet); - if (Succeeded(result)) { - if (iresult != interpreter::Result::Ok) { - passed_++; - } else { - PrintCommandError("expected trap: \"" PRIstringview "\"", - WABT_PRINTF_STRING_VIEW_ARG(text)); - result = wabt::Result::Error; - } - } - - return result; -} - -wabt::Result SpecJSONParser::OnAssertExhaustionCommand(::Action* action) { - std::vector<TypedValue> results; - interpreter::Result iresult; - - total_++; - wabt::Result result = - RunAction(action, &iresult, &results, RunVerbosity::Quiet); - if (Succeeded(result)) { - if (iresult == interpreter::Result::TrapCallStackExhausted || - iresult == interpreter::Result::TrapValueStackExhausted) { - passed_++; - } else { - PrintCommandError("expected call stack exhaustion"); - result = wabt::Result::Error; - } - } - - return result; -} - -wabt::Result SpecJSONParser::ParseCommand() { - EXPECT("{"); - EXPECT_KEY("type"); - if (Match("\"module\"")) { - std::string name; - std::string filename; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseOptNameStringValue(&name)); - PARSE_KEY_STRING_VALUE("filename", &filename); - OnModuleCommand(filename, name); - } else if (Match("\"action\"")) { - ::Action action; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseAction(&action)); - OnActionCommand(&action); - } else if (Match("\"register\"")) { - std::string as; - std::string name; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseOptNameStringValue(&name)); - PARSE_KEY_STRING_VALUE("as", &as); - OnRegisterCommand(name, as); - } else if (Match("\"assert_malformed\"")) { - std::string filename; - std::string text; - ModuleType module_type; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - PARSE_KEY_STRING_VALUE("filename", &filename); - EXPECT(","); - PARSE_KEY_STRING_VALUE("text", &text); - EXPECT(","); - CHECK_RESULT(ParseModuleType(&module_type)); - OnAssertMalformedCommand(filename, text, module_type); - } else if (Match("\"assert_invalid\"")) { - std::string filename; - std::string text; - ModuleType module_type; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - PARSE_KEY_STRING_VALUE("filename", &filename); - EXPECT(","); - PARSE_KEY_STRING_VALUE("text", &text); - EXPECT(","); - CHECK_RESULT(ParseModuleType(&module_type)); - OnAssertInvalidCommand(filename, text, module_type); - } else if (Match("\"assert_unlinkable\"")) { - std::string filename; - std::string text; - ModuleType module_type; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - PARSE_KEY_STRING_VALUE("filename", &filename); - EXPECT(","); - PARSE_KEY_STRING_VALUE("text", &text); - EXPECT(","); - CHECK_RESULT(ParseModuleType(&module_type)); - OnAssertUnlinkableCommand(filename, text, module_type); - } else if (Match("\"assert_uninstantiable\"")) { - std::string filename; - std::string text; - ModuleType module_type; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - PARSE_KEY_STRING_VALUE("filename", &filename); - EXPECT(","); - PARSE_KEY_STRING_VALUE("text", &text); - EXPECT(","); - CHECK_RESULT(ParseModuleType(&module_type)); - OnAssertUninstantiableCommand(filename, text, module_type); - } else if (Match("\"assert_return\"")) { - ::Action action; - std::vector<TypedValue> expected; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseAction(&action)); - EXPECT(","); - EXPECT_KEY("expected"); - CHECK_RESULT(ParseConstVector(&expected)); - OnAssertReturnCommand(&action, expected); - } else if (Match("\"assert_return_canonical_nan\"")) { - ::Action action; - TypeVector expected; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseAction(&action)); - EXPECT(","); - /* Not needed for wabt-interp, but useful for other parsers. */ - EXPECT_KEY("expected"); - CHECK_RESULT(ParseTypeVector(&expected)); - OnAssertReturnNanCommand(&action, true); - } else if (Match("\"assert_return_arithmetic_nan\"")) { - ::Action action; - TypeVector expected; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseAction(&action)); - EXPECT(","); - /* Not needed for wabt-interp, but useful for other parsers. */ - EXPECT_KEY("expected"); - CHECK_RESULT(ParseTypeVector(&expected)); - OnAssertReturnNanCommand(&action, false); - } else if (Match("\"assert_trap\"")) { - ::Action action; - std::string text; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseAction(&action)); - EXPECT(","); - PARSE_KEY_STRING_VALUE("text", &text); - OnAssertTrapCommand(&action, text); - } else if (Match("\"assert_exhaustion\"")) { - ::Action action; - std::string text; - - EXPECT(","); - CHECK_RESULT(ParseLine()); - EXPECT(","); - CHECK_RESULT(ParseAction(&action)); - OnAssertExhaustionCommand(&action); - } else { - PrintCommandError("unknown command type"); - return wabt::Result::Error; - } - EXPECT("}"); - return wabt::Result::Ok; -} - -wabt::Result SpecJSONParser::ParseCommands() { - EXPECT("{"); - PARSE_KEY_STRING_VALUE("source_filename", &source_filename_); - EXPECT(","); - EXPECT_KEY("commands"); - EXPECT("["); - bool first = true; - while (!Match("]")) { - if (!first) - EXPECT(","); - CHECK_RESULT(ParseCommand()); - first = false; - } - EXPECT("}"); - return wabt::Result::Ok; -} - -static wabt::Result ReadAndRunSpecJSON(const char* spec_json_filename) { - SpecJSONParser parser; - CHECK_RESULT(parser.ReadFile(spec_json_filename)); - wabt::Result result = parser.ParseCommands(); - printf("%d/%d tests passed.\n", parser.passed(), parser.total()); - return result; -} - int ProgramMain(int argc, char** argv) { InitStdio(); ParseOptions(argc, argv); s_stdout_stream = FileStream::CreateStdout(); - wabt::Result result; - if (s_spec) { - result = ReadAndRunSpecJSON(s_infile); - } else { - result = ReadAndRunModule(s_infile); - } + wabt::Result result = ReadAndRunModule(s_infile); return result != wabt::Result::Ok; } |