| /* |
| * 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 <map> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "wabt/binary-reader-ir.h" |
| #include "wabt/binary-reader-nop.h" |
| #include "wabt/binary-reader.h" |
| #include "wabt/cast.h" |
| #include "wabt/common.h" |
| #include "wabt/error-formatter.h" |
| #include "wabt/feature.h" |
| #include "wabt/interp/binary-reader-interp.h" |
| #include "wabt/interp/interp-util.h" |
| #include "wabt/interp/interp.h" |
| #include "wabt/literal.h" |
| #include "wabt/option-parser.h" |
| #include "wabt/stream.h" |
| #include "wabt/string-util.h" |
| #include "wabt/validator.h" |
| #include "wabt/wast-lexer.h" |
| #include "wabt/wast-parser.h" |
| |
| using namespace wabt; |
| using namespace wabt::interp; |
| |
| static int s_verbose; |
| static std::string s_infile; |
| static Thread::Options s_thread_options; |
| static Stream* s_trace_stream; |
| 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::CreateStderr(); |
| }); |
| 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_stream = s_stdout_stream.get(); }); |
| |
| parser.AddArgument("filename", OptionParser::ArgumentCount::One, |
| [](const char* argument) { |
| s_infile = argument; |
| ConvertBackslashToSlash(&s_infile); |
| }); |
| parser.Parse(argc, argv); |
| } |
| |
| namespace spectest { |
| |
| class Command; |
| using CommandPtr = std::unique_ptr<Command>; |
| using CommandPtrVector = std::vector<CommandPtr>; |
| |
| class Script { |
| public: |
| std::string filename; |
| CommandPtrVector commands; |
| }; |
| |
| class Command { |
| public: |
| WABT_DISALLOW_COPY_AND_ASSIGN(Command); |
| Command() = delete; |
| virtual ~Command() = default; |
| |
| CommandType type; |
| uint32_t line = 0; |
| |
| protected: |
| explicit Command(CommandType type) : type(type) {} |
| }; |
| |
| template <CommandType TypeEnum> |
| class CommandMixin : public Command { |
| public: |
| static bool classof(const Command* cmd) { return cmd->type == TypeEnum; } |
| CommandMixin() : Command(TypeEnum) {} |
| }; |
| |
| enum class ModuleType { |
| Text, |
| Binary, |
| }; |
| |
| class ModuleCommand : public CommandMixin<CommandType::Module> { |
| public: |
| ModuleType module = ModuleType::Binary; |
| std::string filename; |
| std::string name; |
| }; |
| |
| class Action { |
| public: |
| ActionType type = ActionType::Invoke; |
| std::string module_name; |
| std::string field_name; |
| ValueTypes types; |
| Values args; |
| }; |
| |
| template <CommandType TypeEnum> |
| class ActionCommandBase : public CommandMixin<TypeEnum> { |
| public: |
| Action action; |
| }; |
| |
| using ActionCommand = ActionCommandBase<CommandType::Action>; |
| |
| class RegisterCommand : public CommandMixin<CommandType::Register> { |
| public: |
| std::string as; |
| std::string name; |
| }; |
| |
| struct ExpectedValue { |
| TypedValue value; |
| Type lane_type; // Only valid if value.type == Type::V128. |
| // Up to 4 NaN values used, depending on |value.type| and |lane_type|: |
| // | type | lane_type | valid | |
| // | f32 | | nan[0] | |
| // | f64 | | nan[0] | |
| // | v128 | f32 | nan[0] through nan[3] | |
| // | v128 | f64 | nan[0],nan[1] | |
| // | * | * | none valid | |
| ExpectedNan nan[4]; |
| }; |
| |
| int LaneCountFromType(Type type) { |
| switch (type) { |
| case Type::I8: return 16; |
| case Type::I16: return 8; |
| case Type::I32: return 4; |
| case Type::I64: return 2; |
| case Type::F32: return 4; |
| case Type::F64: return 2; |
| default: assert(false); return 0; |
| } |
| } |
| |
| ExpectedValue GetLane(const ExpectedValue& ev, int lane) { |
| assert(ev.value.type == Type::V128); |
| assert(lane < LaneCountFromType(ev.lane_type)); |
| |
| ExpectedValue result; |
| result.value.type = ev.lane_type; |
| |
| v128 vec = ev.value.value.Get<v128>(); |
| |
| switch (ev.lane_type) { |
| case Type::I8: |
| result.nan[0] = ExpectedNan::None; |
| result.value.value.Set<u32>(vec.u8(lane)); |
| break; |
| |
| case Type::I16: |
| result.nan[0] = ExpectedNan::None; |
| result.value.value.Set<u32>(vec.u16(lane)); |
| break; |
| |
| case Type::I32: |
| result.nan[0] = ExpectedNan::None; |
| result.value.value.Set<u32>(vec.u32(lane)); |
| break; |
| |
| case Type::I64: |
| result.nan[0] = ExpectedNan::None; |
| result.value.value.Set<u64>(vec.u64(lane)); |
| break; |
| |
| case Type::F32: |
| result.nan[0] = ev.nan[lane]; |
| result.value.value.Set<f32>(Bitcast<f32>(vec.f32_bits(lane))); |
| break; |
| |
| case Type::F64: |
| result.nan[0] = ev.nan[lane]; |
| result.value.value.Set<f64>(Bitcast<f64>(vec.f64_bits(lane))); |
| break; |
| |
| default: |
| WABT_UNREACHABLE; |
| } |
| return result; |
| } |
| |
| TypedValue GetLane(const TypedValue& tv, Type lane_type, int lane) { |
| assert(tv.type == Type::V128); |
| assert(lane < LaneCountFromType(lane_type)); |
| |
| TypedValue result; |
| result.type = lane_type; |
| |
| v128 vec = tv.value.Get<v128>(); |
| |
| switch (lane_type) { |
| case Type::I8: |
| result.value.Set<u32>(vec.u8(lane)); |
| break; |
| |
| case Type::I16: |
| result.value.Set<u32>(vec.u16(lane)); |
| break; |
| |
| case Type::I32: |
| result.value.Set<u32>(vec.u32(lane)); |
| break; |
| |
| case Type::I64: |
| result.value.Set<u64>(vec.u64(lane)); |
| break; |
| |
| case Type::F32: |
| result.value.Set<f32>(Bitcast<f32>(vec.f32_bits(lane))); |
| break; |
| |
| case Type::F64: |
| result.value.Set<f64>(Bitcast<f64>(vec.f64_bits(lane))); |
| break; |
| |
| default: |
| WABT_UNREACHABLE; |
| } |
| return result; |
| } |
| |
| bool CheckIR(const std::string& filename, bool validate) { |
| std::vector<uint8_t> file_data; |
| |
| if (Failed(ReadFile(filename, &file_data))) { |
| return false; |
| } |
| |
| const bool kReadDebugNames = true; |
| const bool kStopOnFirstError = true; |
| const bool kFailOnCustomSectionError = true; |
| ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, |
| kStopOnFirstError, kFailOnCustomSectionError); |
| |
| Errors errors; |
| wabt::Module module; |
| if (Failed(ReadBinaryIr(filename.c_str(), file_data.data(), file_data.size(), |
| options, &errors, &module))) { |
| return false; |
| } |
| |
| if (!validate) { |
| return true; |
| } |
| |
| return Succeeded( |
| ValidateModule(&module, &errors, ValidateOptions{s_features})); |
| } |
| |
| bool WellformedIR(const std::string& filename) { |
| return CheckIR(filename, false); |
| } |
| |
| bool ValidIR(const std::string& filename) { |
| return CheckIR(filename, true); |
| } |
| |
| class AssertReturnCommand : public CommandMixin<CommandType::AssertReturn> { |
| public: |
| Action action; |
| std::vector<ExpectedValue> expected; |
| bool expect_either; |
| }; |
| |
| template <CommandType TypeEnum> |
| class AssertTrapCommandBase : public CommandMixin<TypeEnum> { |
| public: |
| Action action; |
| std::string text; |
| }; |
| |
| using AssertTrapCommand = AssertTrapCommandBase<CommandType::AssertTrap>; |
| using AssertExhaustionCommand = |
| AssertTrapCommandBase<CommandType::AssertExhaustion>; |
| |
| template <CommandType TypeEnum> |
| class AssertModuleCommand : public CommandMixin<TypeEnum> { |
| public: |
| ModuleType type = ModuleType::Binary; |
| std::string filename; |
| std::string text; |
| }; |
| |
| using AssertMalformedCommand = |
| AssertModuleCommand<CommandType::AssertMalformed>; |
| using AssertInvalidCommand = AssertModuleCommand<CommandType::AssertInvalid>; |
| using AssertUnlinkableCommand = |
| AssertModuleCommand<CommandType::AssertUnlinkable>; |
| using AssertUninstantiableCommand = |
| AssertModuleCommand<CommandType::AssertUninstantiable>; |
| |
| class AssertExceptionCommand |
| : public CommandMixin<CommandType::AssertException> { |
| public: |
| Action action; |
| }; |
| |
| // An extremely simple JSON parser that only knows how to parse the expected |
| // format from wat2wasm. |
| class JSONParser { |
| public: |
| JSONParser() {} |
| |
| wabt::Result ReadFile(std::string_view spec_json_filename); |
| wabt::Result ParseScript(Script* out_script); |
| |
| private: |
| void WABT_PRINTF_FORMAT(2, 3) PrintError(const char* format, ...); |
| |
| // Whether to allow parsing of expectation-only forms (e.g. `nan:canonical`, |
| // `nan:arithmetic`, etc.) |
| enum class AllowExpected { No, Yes }; |
| |
| 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(uint32_t* out_line_number); |
| wabt::Result ParseType(Type* out_type); |
| wabt::Result ParseTypeObject(Type* out_type); |
| wabt::Result ParseTypeVector(TypeVector* out_types); |
| wabt::Result ParseConst(TypedValue* out_value); |
| wabt::Result ParseI32Value(uint32_t* out_value, std::string_view value_str); |
| wabt::Result ParseI64Value(uint64_t* out_value, std::string_view value_str); |
| wabt::Result ParseF32Value(uint32_t* out_value, |
| ExpectedNan* out_nan, |
| std::string_view value_str, |
| AllowExpected); |
| wabt::Result ParseF64Value(uint64_t* out_value, |
| ExpectedNan* out_nan, |
| std::string_view value_str, |
| AllowExpected); |
| wabt::Result ParseLaneConstValue(Type lane_type, |
| int lane, |
| ExpectedValue* out_value, |
| std::string_view value_str, |
| AllowExpected); |
| wabt::Result ParseConstValue(Type type, |
| Value* out_value, |
| ExpectedNan* out_nan, |
| std::string_view value_str, |
| AllowExpected); |
| wabt::Result ParseConstVector(ValueTypes* out_types, Values* out_values); |
| wabt::Result ParseExpectedValue(ExpectedValue* out_value, AllowExpected); |
| wabt::Result ParseExpectedValues(std::vector<ExpectedValue>* out_values); |
| wabt::Result ParseAction(Action* out_action); |
| wabt::Result ParseActionResult(); |
| wabt::Result ParseModuleType(ModuleType* out_type); |
| |
| std::string CreateModulePath(std::string_view filename); |
| wabt::Result ParseFilename(std::string* out_filename); |
| wabt::Result ParseCommand(CommandPtr* out_command); |
| |
| // Parsing info. |
| std::vector<uint8_t> json_data_; |
| size_t json_offset_ = 0; |
| Location loc_; |
| Location prev_loc_; |
| bool has_prev_loc_ = false; |
| }; |
| |
| #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 JSONParser::ReadFile(std::string_view spec_json_filename) { |
| loc_.filename = spec_json_filename; |
| loc_.line = 1; |
| loc_.first_column = 1; |
| |
| return wabt::ReadFile(spec_json_filename, &json_data_); |
| } |
| |
| void JSONParser::PrintError(const char* format, ...) { |
| WABT_SNPRINTF_ALLOCA(buffer, length, format); |
| fprintf(stderr, "%s:%d:%d: %s\n", std::string(loc_.filename).c_str(), |
| loc_.line, loc_.first_column, buffer); |
| } |
| |
| void JSONParser::PutbackChar() { |
| assert(has_prev_loc_); |
| json_offset_--; |
| loc_ = prev_loc_; |
| has_prev_loc_ = false; |
| } |
| |
| int JSONParser::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 JSONParser::SkipWhitespace() { |
| while (1) { |
| switch (ReadChar()) { |
| case -1: |
| return; |
| |
| case ' ': |
| case '\t': |
| case '\n': |
| case '\r': |
| break; |
| |
| default: |
| PutbackChar(); |
| return; |
| } |
| } |
| } |
| |
| bool JSONParser::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 JSONParser::Expect(const char* s) { |
| if (Match(s)) { |
| return wabt::Result::Ok; |
| } else { |
| PrintError("expected %s", s); |
| return wabt::Result::Error; |
| } |
| } |
| |
| wabt::Result JSONParser::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 JSONParser::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) { |
| PrintError("uint32 overflow"); |
| return wabt::Result::Error; |
| } |
| } else { |
| PutbackChar(); |
| break; |
| } |
| } |
| *out_int = result; |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseString(std::string* out_string) { |
| out_string->clear(); |
| |
| SkipWhitespace(); |
| if (ReadChar() != '"') { |
| PrintError("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') { |
| PrintError("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 { |
| PrintError("expected hex char"); |
| return wabt::Result::Error; |
| } |
| code = (code << 4) + cval; |
| } |
| |
| if (code < 256) { |
| *out_string += code; |
| } else { |
| PrintError("only escape codes < 256 allowed, got %u\n", code); |
| } |
| } else { |
| *out_string += c; |
| } |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseKeyStringValue(const char* key, |
| std::string* out_string) { |
| out_string->clear(); |
| EXPECT_KEY(key); |
| return ParseString(out_string); |
| } |
| |
| wabt::Result JSONParser::ParseOptNameStringValue(std::string* out_string) { |
| out_string->clear(); |
| if (Match("\"name\"")) { |
| EXPECT(":"); |
| CHECK_RESULT(ParseString(out_string)); |
| EXPECT(","); |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseLine(uint32_t* out_line_number) { |
| EXPECT_KEY("line"); |
| CHECK_RESULT(ParseUint32(out_line_number)); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseType(Type* out_type) { |
| std::string type_str; |
| CHECK_RESULT(ParseString(&type_str)); |
| |
| if (type_str == "i32") { |
| *out_type = Type::I32; |
| } else if (type_str == "f32") { |
| *out_type = Type::F32; |
| } else if (type_str == "i64") { |
| *out_type = Type::I64; |
| } else if (type_str == "f64") { |
| *out_type = Type::F64; |
| } else if (type_str == "v128") { |
| *out_type = Type::V128; |
| } else if (type_str == "i8") { |
| *out_type = Type::I8; |
| } else if (type_str == "i16") { |
| *out_type = Type::I16; |
| } else if (type_str == "funcref") { |
| *out_type = Type::FuncRef; |
| } else if (type_str == "externref") { |
| *out_type = Type::ExternRef; |
| } else if (type_str == "exnref") { |
| *out_type = Type::ExnRef; |
| } else { |
| PrintError("unknown type: \"%s\"", type_str.c_str()); |
| return wabt::Result::Error; |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseTypeObject(Type* out_type) { |
| EXPECT("{"); |
| EXPECT_KEY("type"); |
| CHECK_RESULT(ParseType(out_type)); |
| EXPECT("}"); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::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 JSONParser::ParseConst(TypedValue* out_value) { |
| ExpectedValue expected; |
| CHECK_RESULT(ParseExpectedValue(&expected, AllowExpected::No)); |
| *out_value = expected.value; |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseI32Value(uint32_t* out_value, |
| std::string_view value_str) { |
| if (Failed(ParseInt32(value_str, out_value, ParseIntType::UnsignedOnly))) { |
| PrintError("invalid i32 literal"); |
| return wabt::Result::Error; |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseI64Value(uint64_t* out_value, |
| std::string_view value_str) { |
| if (Failed(ParseInt64(value_str, out_value, ParseIntType::UnsignedOnly))) { |
| PrintError("invalid i64 literal"); |
| return wabt::Result::Error; |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseF32Value(uint32_t* out_value, |
| ExpectedNan* out_nan, |
| std::string_view value_str, |
| AllowExpected allow_expected) { |
| if (allow_expected == AllowExpected::Yes) { |
| *out_value = 0; |
| if (value_str == "nan:canonical") { |
| *out_nan = ExpectedNan::Canonical; |
| return wabt::Result::Ok; |
| } else if (value_str == "nan:arithmetic") { |
| *out_nan = ExpectedNan::Arithmetic; |
| return wabt::Result::Ok; |
| } |
| } |
| |
| *out_nan = ExpectedNan::None; |
| if (Failed(ParseInt32(value_str, out_value, ParseIntType::UnsignedOnly))) { |
| PrintError("invalid f32 literal"); |
| return wabt::Result::Error; |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseF64Value(uint64_t* out_value, |
| ExpectedNan* out_nan, |
| std::string_view value_str, |
| AllowExpected allow_expected) { |
| if (allow_expected == AllowExpected::Yes) { |
| *out_value = 0; |
| if (value_str == "nan:canonical") { |
| *out_nan = ExpectedNan::Canonical; |
| return wabt::Result::Ok; |
| } else if (value_str == "nan:arithmetic") { |
| *out_nan = ExpectedNan::Arithmetic; |
| return wabt::Result::Ok; |
| } |
| } |
| |
| *out_nan = ExpectedNan::None; |
| if (Failed(ParseInt64(value_str, out_value, ParseIntType::UnsignedOnly))) { |
| PrintError("invalid f64 literal"); |
| return wabt::Result::Error; |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseLaneConstValue(Type lane_type, |
| int lane, |
| ExpectedValue* out_value, |
| std::string_view value_str, |
| AllowExpected allow_expected) { |
| v128 v = out_value->value.value.Get<v128>(); |
| |
| switch (lane_type) { |
| case Type::I8: { |
| uint32_t value; |
| CHECK_RESULT(ParseI32Value(&value, value_str)); |
| v.set_u8(lane, value); |
| break; |
| } |
| |
| case Type::I16: { |
| uint32_t value; |
| CHECK_RESULT(ParseI32Value(&value, value_str)); |
| v.set_u16(lane, value); |
| break; |
| } |
| |
| case Type::I32: { |
| uint32_t value; |
| CHECK_RESULT(ParseI32Value(&value, value_str)); |
| v.set_u32(lane, value); |
| break; |
| } |
| |
| case Type::I64: { |
| uint64_t value; |
| CHECK_RESULT(ParseI64Value(&value, value_str)); |
| v.set_u64(lane, value); |
| break; |
| } |
| |
| case Type::F32: { |
| ExpectedNan nan; |
| uint32_t value_bits; |
| CHECK_RESULT(ParseF32Value(&value_bits, &nan, value_str, allow_expected)); |
| v.set_f32_bits(lane, value_bits); |
| assert(lane < 4); |
| out_value->nan[lane] = nan; |
| break; |
| } |
| |
| case Type::F64: { |
| ExpectedNan nan; |
| uint64_t value_bits; |
| CHECK_RESULT(ParseF64Value(&value_bits, &nan, value_str, allow_expected)); |
| v.set_f64_bits(lane, value_bits); |
| assert(lane < 2); |
| out_value->nan[lane] = nan; |
| break; |
| } |
| |
| default: |
| PrintError("unknown concrete type: \"%s\"", lane_type.GetName().c_str()); |
| return wabt::Result::Error; |
| } |
| |
| out_value->value.value.Set<v128>(v); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseConstValue(Type type, |
| Value* out_value, |
| ExpectedNan* out_nan, |
| std::string_view value_str, |
| AllowExpected allow_expected) { |
| *out_nan = ExpectedNan::None; |
| |
| switch (type) { |
| case Type::I32: { |
| uint32_t value; |
| CHECK_RESULT(ParseI32Value(&value, value_str)); |
| out_value->Set(value); |
| break; |
| } |
| |
| case Type::F32: { |
| uint32_t value_bits; |
| CHECK_RESULT( |
| ParseF32Value(&value_bits, out_nan, value_str, allow_expected)); |
| out_value->Set(Bitcast<f32>(value_bits)); |
| break; |
| } |
| |
| case Type::I64: { |
| uint64_t value; |
| CHECK_RESULT(ParseI64Value(&value, value_str)); |
| out_value->Set(value); |
| break; |
| } |
| |
| case Type::F64: { |
| uint64_t value_bits; |
| CHECK_RESULT( |
| ParseF64Value(&value_bits, out_nan, value_str, allow_expected)); |
| out_value->Set(Bitcast<f64>(value_bits)); |
| break; |
| } |
| |
| case Type::V128: |
| assert(false); // Should use ParseLaneConstValue instead. |
| break; |
| |
| case Type::FuncRef: |
| if (value_str == "null") { |
| out_value->Set(Ref::Null); |
| } else { |
| assert(allow_expected == AllowExpected::Yes); |
| out_value->Set(Ref{1}); |
| } |
| break; |
| |
| case Type::ExternRef: |
| if (value_str == "null") { |
| out_value->Set(Ref::Null); |
| } else { |
| uint32_t value; |
| CHECK_RESULT(ParseI32Value(&value, value_str)); |
| // TODO: hack, just whatever ref is at this index; but skip null (which |
| // is always 0). |
| out_value->Set(Ref{value + 1}); |
| } |
| break; |
| |
| case Type::ExnRef: |
| if (value_str == "null") { |
| out_value->Set(Ref::Null); |
| } else { |
| // FIXME? |
| PrintError("NYI"); |
| return wabt::Result::Error; |
| } |
| break; |
| |
| default: |
| PrintError("unknown concrete type: \"%s\"", type.GetName().c_str()); |
| return wabt::Result::Error; |
| } |
| |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseExpectedValue(ExpectedValue* out_value, |
| AllowExpected allow_expected) { |
| Type type; |
| std::string value_str; |
| EXPECT("{"); |
| EXPECT_KEY("type"); |
| CHECK_RESULT(ParseType(&type)); |
| EXPECT(","); |
| if (type == Type::V128) { |
| Type lane_type; |
| EXPECT_KEY("lane_type"); |
| CHECK_RESULT(ParseType(&lane_type)); |
| EXPECT(","); |
| EXPECT_KEY("value"); |
| EXPECT("["); |
| |
| int lane_count = LaneCountFromType(lane_type); |
| for (int lane = 0; lane < lane_count; ++lane) { |
| CHECK_RESULT(ParseString(&value_str)); |
| CHECK_RESULT(ParseLaneConstValue(lane_type, lane, out_value, value_str, |
| allow_expected)); |
| if (lane < lane_count - 1) { |
| EXPECT(","); |
| } |
| } |
| EXPECT("]"); |
| out_value->value.type = type; |
| out_value->lane_type = lane_type; |
| } else { |
| PARSE_KEY_STRING_VALUE("value", &value_str); |
| CHECK_RESULT(ParseConstValue(type, &out_value->value.value, |
| &out_value->nan[0], value_str, |
| allow_expected)); |
| out_value->value.type = type; |
| } |
| EXPECT("}"); |
| |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseExpectedValues( |
| std::vector<ExpectedValue>* out_values) { |
| out_values->clear(); |
| EXPECT("["); |
| bool first = true; |
| while (!Match("]")) { |
| if (!first) { |
| EXPECT(","); |
| } |
| ExpectedValue value; |
| CHECK_RESULT(ParseExpectedValue(&value, AllowExpected::Yes)); |
| out_values->push_back(value); |
| first = false; |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseConstVector(ValueTypes* out_types, |
| Values* out_values) { |
| out_values->clear(); |
| EXPECT("["); |
| bool first = true; |
| while (!Match("]")) { |
| if (!first) { |
| EXPECT(","); |
| } |
| TypedValue tv; |
| CHECK_RESULT(ParseConst(&tv)); |
| out_types->push_back(tv.type); |
| out_values->push_back(tv.value); |
| first = false; |
| } |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::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->types, &out_action->args)); |
| } |
| EXPECT("}"); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseActionResult() { |
| // Not needed for wabt-interp, but useful for other parsers. |
| EXPECT_KEY("expected"); |
| TypeVector expected; |
| CHECK_RESULT(ParseTypeVector(&expected)); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::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 { |
| PrintError("unknown module type: \"%s\"", module_type_str.c_str()); |
| return wabt::Result::Error; |
| } |
| } |
| |
| static std::string_view GetDirname(std::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 == std::string_view::npos) { |
| last_slash = 0; |
| } |
| if (last_backslash == std::string_view::npos) { |
| last_backslash = 0; |
| } |
| |
| return path.substr(0, std::max(last_slash, last_backslash)); |
| } |
| |
| std::string JSONParser::CreateModulePath(std::string_view filename) { |
| std::string_view spec_json_filename = loc_.filename; |
| std::string_view dirname = GetDirname(spec_json_filename); |
| std::string path; |
| |
| if (dirname.size() == 0) { |
| path = std::string(filename); |
| } else { |
| path = dirname + "/" + filename; |
| } |
| |
| ConvertBackslashToSlash(&path); |
| return path; |
| } |
| |
| wabt::Result JSONParser::ParseFilename(std::string* out_filename) { |
| PARSE_KEY_STRING_VALUE("filename", out_filename); |
| *out_filename = CreateModulePath(*out_filename); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseCommand(CommandPtr* out_command) { |
| EXPECT("{"); |
| EXPECT_KEY("type"); |
| if (Match("\"module\"")) { |
| auto command = std::make_unique<ModuleCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseOptNameStringValue(&command->name)); |
| CHECK_RESULT(ParseFilename(&command->filename)); |
| *out_command = std::move(command); |
| } else if (Match("\"action\"")) { |
| auto command = std::make_unique<ActionCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseAction(&command->action)); |
| EXPECT(","); |
| CHECK_RESULT(ParseActionResult()); |
| *out_command = std::move(command); |
| } else if (Match("\"register\"")) { |
| auto command = std::make_unique<RegisterCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseOptNameStringValue(&command->name)); |
| PARSE_KEY_STRING_VALUE("as", &command->as); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_malformed\"")) { |
| auto command = std::make_unique<AssertMalformedCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseFilename(&command->filename)); |
| EXPECT(","); |
| PARSE_KEY_STRING_VALUE("text", &command->text); |
| EXPECT(","); |
| CHECK_RESULT(ParseModuleType(&command->type)); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_invalid\"")) { |
| auto command = std::make_unique<AssertInvalidCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseFilename(&command->filename)); |
| EXPECT(","); |
| PARSE_KEY_STRING_VALUE("text", &command->text); |
| EXPECT(","); |
| CHECK_RESULT(ParseModuleType(&command->type)); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_unlinkable\"")) { |
| auto command = std::make_unique<AssertUnlinkableCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseFilename(&command->filename)); |
| EXPECT(","); |
| PARSE_KEY_STRING_VALUE("text", &command->text); |
| EXPECT(","); |
| CHECK_RESULT(ParseModuleType(&command->type)); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_uninstantiable\"")) { |
| auto command = std::make_unique<AssertUninstantiableCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseFilename(&command->filename)); |
| EXPECT(","); |
| PARSE_KEY_STRING_VALUE("text", &command->text); |
| EXPECT(","); |
| CHECK_RESULT(ParseModuleType(&command->type)); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_return\"")) { |
| auto command = std::make_unique<AssertReturnCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseAction(&command->action)); |
| EXPECT(","); |
| if (Match("\"either\"")) { |
| EXPECT(":"); |
| command->expect_either = true; |
| } else { |
| EXPECT_KEY("expected"); |
| } |
| CHECK_RESULT(ParseExpectedValues(&command->expected)); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_trap\"")) { |
| auto command = std::make_unique<AssertTrapCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseAction(&command->action)); |
| EXPECT(","); |
| PARSE_KEY_STRING_VALUE("text", &command->text); |
| EXPECT(","); |
| CHECK_RESULT(ParseActionResult()); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_exhaustion\"")) { |
| auto command = std::make_unique<AssertExhaustionCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseAction(&command->action)); |
| EXPECT(","); |
| PARSE_KEY_STRING_VALUE("text", &command->text); |
| EXPECT(","); |
| CHECK_RESULT(ParseActionResult()); |
| *out_command = std::move(command); |
| } else if (Match("\"assert_exception\"")) { |
| if (!s_features.exceptions_enabled()) { |
| PrintError("invalid command: exceptions not allowed"); |
| return wabt::Result::Error; |
| } |
| auto command = std::make_unique<AssertExceptionCommand>(); |
| EXPECT(","); |
| CHECK_RESULT(ParseLine(&command->line)); |
| EXPECT(","); |
| CHECK_RESULT(ParseAction(&command->action)); |
| EXPECT(","); |
| CHECK_RESULT(ParseActionResult()); |
| *out_command = std::move(command); |
| } else { |
| PrintError("unknown command type"); |
| return wabt::Result::Error; |
| } |
| EXPECT("}"); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result JSONParser::ParseScript(Script* out_script) { |
| EXPECT("{"); |
| PARSE_KEY_STRING_VALUE("source_filename", &out_script->filename); |
| EXPECT(","); |
| EXPECT_KEY("commands"); |
| EXPECT("["); |
| bool first = true; |
| while (!Match("]")) { |
| CommandPtr command; |
| if (!first) { |
| EXPECT(","); |
| } |
| CHECK_RESULT(ParseCommand(&command)); |
| out_script->commands.push_back(std::move(command)); |
| first = false; |
| } |
| EXPECT("}"); |
| 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, ...); |
| ActionResult RunAction(int line_number, |
| const Action* action, |
| RunVerbosity verbose); |
| |
| interp::Module::Ptr ReadModule(std::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*); |
| wabt::Result OnRegisterCommand(const RegisterCommand*); |
| wabt::Result OnAssertMalformedCommand(const AssertMalformedCommand*); |
| wabt::Result OnAssertUnlinkableCommand(const AssertUnlinkableCommand*); |
| wabt::Result OnAssertInvalidCommand(const AssertInvalidCommand*); |
| wabt::Result OnAssertUninstantiableCommand( |
| const AssertUninstantiableCommand*); |
| wabt::Result OnAssertReturnCommand(const AssertReturnCommand*); |
| wabt::Result OnAssertTrapCommand(const AssertTrapCommand*); |
| wabt::Result OnAssertExhaustionCommand(const AssertExhaustionCommand*); |
| wabt::Result OnAssertExceptionCommand(const AssertExceptionCommand*); |
| |
| wabt::Result CheckAssertReturnResult(const AssertReturnCommand* command, |
| int index, |
| ExpectedValue expected, |
| TypedValue actual, |
| bool print_error); |
| |
| void TallyCommand(wabt::Result); |
| |
| wabt::Result ReadTextModule(std::string_view module_filename, |
| const std::string& header, |
| bool validate); |
| wabt::Result ReadMalformedBinaryModule(std::string_view module_filename, |
| Errors* errors); |
| wabt::Result ReadMalformedModule(int line_number, |
| std::string_view module_filename, |
| ModuleType module_type, |
| const char* desc); |
| wabt::Result ReadInvalidModule(int line_number, |
| std::string_view module_filename, |
| ModuleType module_type, |
| const char* desc); |
| wabt::Result ReadUnlinkableModule(int line_number, |
| std::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_; |
| }; |
| |
| 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_i64", interp::FuncType{{ValueType::I64}, {}}}, |
| {"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, |
| [=](Thread& inst, 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; |
| }); |
| } |
| |
| spectest["table"] = |
| interp::Table::New(store_, TableType{ValueType::FuncRef, Limits{10, 20}}); |
| |
| spectest["table64"] = interp::Table::New( |
| store_, TableType{ValueType::FuncRef, Limits{10, 20, false, true}}); |
| |
| spectest["memory"] = interp::Memory::New( |
| store_, MemoryType{Limits{1, 2}, WABT_DEFAULT_PAGE_SIZE}); |
| |
| 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.6})); |
| spectest["global_f64"] = |
| interp::Global::New(store_, GlobalType{ValueType::F64, Mutability::Const}, |
| Value::Make(f64{666.6})); |
| } |
| |
| wabt::Result CommandRunner::Run(const Script& script) { |
| source_filename_ = script.filename; |
| |
| for (const CommandPtr& command : script.commands) { |
| switch (command->type) { |
| case CommandType::Module: |
| case CommandType::ScriptModule: |
| TallyCommand(OnModuleCommand(cast<ModuleCommand>(command.get()))); |
| break; |
| |
| case CommandType::Action: |
| TallyCommand(OnActionCommand(cast<ActionCommand>(command.get()))); |
| break; |
| |
| case CommandType::Register: |
| if (Failed(OnRegisterCommand(cast<RegisterCommand>(command.get())))) { |
| PrintError(command->line, "invalid register command"); |
| return wabt::Result::Error; |
| } |
| break; |
| |
| case CommandType::AssertMalformed: |
| TallyCommand(OnAssertMalformedCommand( |
| cast<AssertMalformedCommand>(command.get()))); |
| break; |
| |
| case CommandType::AssertInvalid: |
| TallyCommand( |
| OnAssertInvalidCommand(cast<AssertInvalidCommand>(command.get()))); |
| break; |
| |
| case CommandType::AssertUnlinkable: |
| TallyCommand(OnAssertUnlinkableCommand( |
| cast<AssertUnlinkableCommand>(command.get()))); |
| break; |
| |
| case CommandType::AssertUninstantiable: |
| TallyCommand(OnAssertUninstantiableCommand( |
| cast<AssertUninstantiableCommand>(command.get()))); |
| break; |
| |
| case CommandType::AssertReturn: |
| TallyCommand( |
| OnAssertReturnCommand(cast<AssertReturnCommand>(command.get()))); |
| break; |
| |
| case CommandType::AssertTrap: |
| TallyCommand( |
| OnAssertTrapCommand(cast<AssertTrapCommand>(command.get()))); |
| break; |
| |
| case CommandType::AssertExhaustion: |
| TallyCommand(OnAssertExhaustionCommand( |
| cast<AssertExhaustionCommand>(command.get()))); |
| break; |
| |
| case CommandType::AssertException: |
| TallyCommand(OnAssertExceptionCommand( |
| cast<AssertExceptionCommand>(command.get()))); |
| break; |
| } |
| } |
| |
| return wabt::Result::Ok; |
| } |
| |
| void CommandRunner::PrintError(uint32_t line_number, const char* format, ...) { |
| WABT_SNPRINTF_ALLOCA(buffer, length, format); |
| printf("%s:%u: %s\n", source_filename_.c_str(), line_number, buffer); |
| } |
| |
| 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 {}; |
| } |
| |
| ActionResult result; |
| |
| switch (action->type) { |
| case ActionType::Invoke: { |
| auto* func = cast<interp::Func>(extern_.get()); |
| auto ok = func->Call(store_, action->args, result.values, &result.trap, |
| s_trace_stream); |
| assert((ok == Result::Ok) == (!result.trap)); |
| result.types = func->type().results; |
| if (verbose == RunVerbosity::Verbose) { |
| WriteCall(s_stdout_stream.get(), action->field_name, func->type(), |
| action->args, result.values, result.trap); |
| } |
| break; |
| } |
| |
| 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 result; |
| } |
| |
| wabt::Result CommandRunner::ReadTextModule(std::string_view module_filename, |
| const std::string& header, |
| bool validate) { |
| std::vector<uint8_t> file_data; |
| wabt::Result result = ReadFile(module_filename, &file_data); |
| Errors errors; |
| std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer( |
| module_filename, file_data.data(), file_data.size(), &errors); |
| if (Succeeded(result)) { |
| std::unique_ptr<wabt::Module> module; |
| WastParseOptions options(s_features); |
| result = ParseWatModule(lexer.get(), &module, &errors, &options); |
| |
| if (validate && Succeeded(result)) { |
| result = |
| ValidateModule(module.get(), &errors, ValidateOptions{s_features}); |
| } |
| } |
| |
| auto line_finder = lexer->MakeLineFinder(); |
| FormatErrorsToFile(errors, Location::Type::Text, line_finder.get(), stdout, |
| header, PrintHeader::Once); |
| return result; |
| } |
| |
| interp::Module::Ptr CommandRunner::ReadModule(std::string_view module_filename, |
| Errors* errors) { |
| std::vector<uint8_t> file_data; |
| |
| if (Failed(ReadFile(module_filename, &file_data))) { |
| return {}; |
| } |
| |
| 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(module_filename, file_data.data(), |
| file_data.size(), options, errors, |
| &module_desc))) { |
| return {}; |
| } |
| |
| 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, |
| std::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 ReadTextModule(module_filename, header, true); |
| } |
| |
| case ModuleType::Binary: { |
| Errors errors; |
| auto module = ReadModule(module_filename, &errors); |
| if (!module) { |
| FormatErrorsToFile(errors, Location::Type::Binary, {}, stdout, header, |
| PrintHeader::Once); |
| return wabt::Result::Error; |
| } else { |
| return wabt::Result::Ok; |
| } |
| } |
| } |
| |
| WABT_UNREACHABLE; |
| } |
| |
| wabt::Result CommandRunner::ReadMalformedBinaryModule( |
| std::string_view module_filename, |
| Errors* errors) { |
| std::vector<uint8_t> file_data; |
| |
| CHECK_RESULT(ReadFile(module_filename, &file_data)); |
| |
| const bool kReadDebugNames = true; |
| const bool kStopOnFirstError = true; |
| const bool kFailOnCustomSectionError = true; |
| ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, |
| kStopOnFirstError, kFailOnCustomSectionError); |
| |
| class BinaryReaderErrorLogging : public BinaryReaderNop { |
| Errors* errors_; |
| |
| public: |
| BinaryReaderErrorLogging(Errors* errors) : errors_(errors) {} |
| |
| bool OnError(const Error& error) override { |
| errors_->push_back(error); |
| return true; |
| } |
| }; |
| |
| BinaryReaderErrorLogging reader_delegate{errors}; |
| return ReadBinary(file_data.data(), file_data.size(), &reader_delegate, |
| options); |
| } |
| |
| wabt::Result CommandRunner::ReadMalformedModule( |
| int line_number, |
| std::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 ReadTextModule(module_filename, header, false); |
| } |
| |
| case ModuleType::Binary: { |
| Errors errors; |
| wabt::Result result = ReadMalformedBinaryModule(module_filename, &errors); |
| FormatErrorsToFile(errors, Location::Type::Binary, {}, stdout, header, |
| PrintHeader::Once); |
| 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) { |
| Errors errors; |
| auto module = ReadModule(command->filename, &errors); |
| FormatErrorsToFile(errors, Location::Type::Binary); |
| |
| if (!module) { |
| PrintError(command->line, "error reading module: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| if (!ValidIR(command->filename)) { |
| PrintError(command->line, "IR Validator thinks module is invalid: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| 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()) { |
| instances_[command->name] = last_instance_; |
| } |
| |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result CommandRunner::OnActionCommand(const ActionCommand* command) { |
| ActionResult result = |
| RunAction(command->line, &command->action, RunVerbosity::Verbose); |
| |
| if (result.trap) { |
| PrintError(command->line, "unexpected trap: %s", |
| result.trap->message().c_str()); |
| return wabt::Result::Error; |
| } |
| |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result CommandRunner::OnAssertMalformedCommand( |
| const AssertMalformedCommand* command) { |
| wabt::Result result = ReadMalformedModule(command->line, command->filename, |
| command->type, "assert_malformed"); |
| if (Succeeded(result)) { |
| PrintError(command->line, "expected module to be malformed: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| if (WellformedIR(command->filename)) { |
| PrintError(command->line, |
| "BinaryReaderIR thinks module is well-formed: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result CommandRunner::OnRegisterCommand(const RegisterCommand* command) { |
| if (!command->name.empty()) { |
| 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 { |
| registry_[command->as] = last_instance_; |
| } |
| |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result CommandRunner::OnAssertUnlinkableCommand( |
| const AssertUnlinkableCommand* command) { |
| Errors errors; |
| 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; |
| } |
| |
| if (!ValidIR(command->filename)) { |
| PrintError(command->line, "IR Validator thinks module is invalid: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| 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; |
| } |
| |
| // 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) { |
| 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()); |
| return wabt::Result::Error; |
| } |
| |
| if (ValidIR(command->filename)) { |
| PrintError(command->line, "IR Validator thinks module is valid: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result CommandRunner::OnAssertUninstantiableCommand( |
| const AssertUninstantiableCommand* command) { |
| Errors errors; |
| auto module = ReadModule(command->filename, &errors); |
| |
| if (!module) { |
| PrintError(command->line, "unable to compile uninstantiable module: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| if (!ValidIR(command->filename)) { |
| PrintError(command->line, "IR Validator thinks module is invalid: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| 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 uninstantiable: \"%s\"", |
| command->filename.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| // 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 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; |
| } |
| |
| static std::string ExpectedValueToString(const ExpectedValue& ev) { |
| // Extend TypedValueToString to print expected nan values too. |
| switch (ev.value.type) { |
| case Type::F32: |
| case Type::F64: |
| switch (ev.nan[0]) { |
| case ExpectedNan::None: |
| return TypedValueToString(ev.value); |
| |
| case ExpectedNan::Arithmetic: |
| return StringPrintf("%s:nan:arithmetic", |
| ev.value.type.GetName().c_str()); |
| |
| case ExpectedNan::Canonical: |
| return StringPrintf("%s:nan:canonical", |
| ev.value.type.GetName().c_str()); |
| } |
| break; |
| |
| case Type::V128: { |
| int lane_count = LaneCountFromType(ev.lane_type); |
| std::string result = "v128 "; |
| for (int lane = 0; lane < lane_count; ++lane) { |
| result += ExpectedValueToString(GetLane(ev, lane)); |
| } |
| return result; |
| } |
| |
| default: |
| break; |
| } |
| return TypedValueToString(ev.value); |
| } |
| |
| wabt::Result CommandRunner::CheckAssertReturnResult( |
| const AssertReturnCommand* command, |
| int index, |
| ExpectedValue expected, |
| TypedValue actual, |
| bool print_error) { |
| assert(expected.value.type == actual.type || |
| IsReference(expected.value.type)); |
| bool ok = true; |
| switch (expected.value.type) { |
| case Type::I8: |
| case Type::I16: |
| case Type::I32: |
| ok = expected.value.value.Get<u32>() == actual.value.Get<u32>(); |
| break; |
| |
| case Type::I64: |
| ok = expected.value.value.Get<u64>() == actual.value.Get<u64>(); |
| break; |
| |
| case Type::F32: |
| switch (expected.nan[0]) { |
| case ExpectedNan::Arithmetic: |
| ok = IsArithmeticNan(actual.value.Get<f32>()); |
| break; |
| |
| case ExpectedNan::Canonical: |
| ok = IsCanonicalNan(actual.value.Get<f32>()); |
| break; |
| |
| case ExpectedNan::None: |
| ok = Bitcast<u32>(expected.value.value.Get<f32>()) == |
| Bitcast<u32>(actual.value.Get<f32>()); |
| break; |
| } |
| break; |
| |
| case Type::F64: |
| switch (expected.nan[0]) { |
| case ExpectedNan::Arithmetic: |
| ok = IsArithmeticNan(actual.value.Get<f64>()); |
| break; |
| |
| case ExpectedNan::Canonical: |
| ok = IsCanonicalNan(actual.value.Get<f64>()); |
| break; |
| |
| case ExpectedNan::None: |
| ok = Bitcast<u64>(expected.value.value.Get<f64>()) == |
| Bitcast<u64>(actual.value.Get<f64>()); |
| break; |
| } |
| break; |
| |
| case Type::V128: { |
| // Compare each lane as if it were its own value. |
| for (int lane = 0; lane < LaneCountFromType(expected.lane_type); ++lane) { |
| ExpectedValue lane_expected = GetLane(expected, lane); |
| TypedValue lane_actual = GetLane(actual, expected.lane_type, lane); |
| |
| if (Failed(CheckAssertReturnResult(command, index, lane_expected, |
| lane_actual, false))) { |
| if (print_error) { |
| PrintError(command->line, |
| "mismatch in lane %u of result %u of assert_return: " |
| "expected %s, got %s", |
| lane, index, |
| ExpectedValueToString(lane_expected).c_str(), |
| TypedValueToString(lane_actual).c_str()); |
| } |
| ok = false; |
| } |
| } |
| break; |
| } |
| |
| case Type::FuncRef: |
| // A funcref expectation only requires that the reference be a function, |
| // but it doesn't check the actual index. |
| ok = (actual.type == Type::FuncRef); |
| break; |
| |
| case Type::ExternRef: |
| ok = expected.value.value.Get<Ref>() == actual.value.Get<Ref>(); |
| break; |
| |
| case Type::ExnRef: |
| // FIXME is this correct? |
| ok = (actual.type == Type::ExnRef); |
| break; |
| |
| default: |
| WABT_UNREACHABLE; |
| } |
| |
| if (!ok && print_error) { |
| PrintError(command->line, |
| "mismatch in result %u of assert_return: expected %s, got %s", |
| index, ExpectedValueToString(expected).c_str(), |
| TypedValueToString(actual).c_str()); |
| } |
| return ok ? wabt::Result::Ok : wabt::Result::Error; |
| } |
| |
| wabt::Result CommandRunner::OnAssertReturnCommand( |
| const AssertReturnCommand* command) { |
| ActionResult action_result = |
| RunAction(command->line, &command->action, RunVerbosity::Quiet); |
| |
| if (action_result.trap) { |
| PrintError(command->line, "unexpected trap: %s", |
| action_result.trap->message().c_str()); |
| return wabt::Result::Error; |
| } |
| |
| if (command->expect_either) { |
| if (action_result.values.size() != 1) { |
| PrintError(command->line, |
| "\"either\" requires single result but got %" PRIzd, |
| action_result.values.size()); |
| return wabt::Result::Error; |
| } |
| |
| TypedValue actual{action_result.types[0], action_result.values[0]}; |
| for (size_t i = 0; i < command->expected.size(); ++i) { |
| const ExpectedValue& expected = command->expected[i]; |
| if (Succeeded( |
| CheckAssertReturnResult(command, i, expected, actual, false))) { |
| return wabt::Result::Ok; |
| } |
| } |
| PrintError(command->line, |
| "mismatch in result of assert_return: expected %s (%" PRIzd |
| " alternatives), got %s", |
| ExpectedValueToString(command->expected[0]).c_str(), |
| command->expected.size(), TypedValueToString(actual).c_str()); |
| return wabt::Result::Error; |
| } else { |
| if (action_result.values.size() != command->expected.size()) { |
| PrintError(command->line, |
| "result length mismatch in assert_return: expected %" PRIzd |
| ", got %" PRIzd, |
| command->expected.size(), action_result.values.size()); |
| return wabt::Result::Error; |
| } |
| |
| wabt::Result result = wabt::Result::Ok; |
| for (size_t i = 0; i < action_result.values.size(); ++i) { |
| const ExpectedValue& expected = command->expected[i]; |
| TypedValue actual{action_result.types[i], action_result.values[i]}; |
| |
| result |= CheckAssertReturnResult(command, i, expected, actual, true); |
| } |
| return result; |
| } |
| } |
| |
| wabt::Result CommandRunner::OnAssertTrapCommand( |
| const AssertTrapCommand* command) { |
| ActionResult result = |
| RunAction(command->line, &command->action, RunVerbosity::Quiet); |
| if (!result.trap) { |
| PrintError(command->line, "expected trap: \"%s\"", command->text.c_str()); |
| return wabt::Result::Error; |
| } |
| |
| PrintError(command->line, "assert_trap passed: %s", |
| result.trap->message().c_str()); |
| return wabt::Result::Ok; |
| } |
| |
| wabt::Result CommandRunner::OnAssertExhaustionCommand( |
| const AssertExhaustionCommand* command) { |
| ActionResult result = |
| RunAction(command->line, &command->action, RunVerbosity::Quiet); |
| 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; |
| } |
| |
| wabt::Result CommandRunner::OnAssertExceptionCommand( |
| const AssertExceptionCommand* command) { |
| ActionResult result = |
| RunAction(command->line, &command->action, RunVerbosity::Quiet); |
| if (!result.trap || result.trap->message() != "uncaught exception") { |
| PrintError(command->line, "expected an exception to be thrown"); |
| return wabt::Result::Error; |
| } |
| PrintError(command->line, "assert_exception passed"); |
| |
| return wabt::Result::Ok; |
| } |
| |
| void CommandRunner::TallyCommand(wabt::Result result) { |
| if (Succeeded(result)) { |
| passed_++; |
| } |
| total_++; |
| } |
| |
| static int ReadAndRunSpecJSON(std::string_view spec_json_filename) { |
| JSONParser parser; |
| if (parser.ReadFile(spec_json_filename) == wabt::Result::Error) { |
| return 1; |
| } |
| |
| Script script; |
| if (parser.ParseScript(&script) == wabt::Result::Error) { |
| return 1; |
| } |
| |
| CommandRunner runner; |
| if (runner.Run(script) == wabt::Result::Error) { |
| return 1; |
| } |
| |
| printf("%d/%d tests passed.\n", runner.passed(), runner.total()); |
| const int failed = runner.total() - runner.passed(); |
| return failed; |
| } |
| |
| } // namespace spectest |
| |
| int ProgramMain(int argc, char** argv) { |
| InitStdio(); |
| s_stdout_stream = FileStream::CreateStdout(); |
| |
| ParseOptions(argc, argv); |
| return spectest::ReadAndRunSpecJSON(s_infile); |
| } |
| |
| int main(int argc, char** argv) { |
| WABT_TRY |
| return ProgramMain(argc, argv); |
| WABT_CATCH_BAD_ALLOC_AND_EXIT |
| } |