diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/wasm2c/src/tools | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/wasm2c/src/tools')
-rw-r--r-- | third_party/wasm2c/src/tools/spectest-interp.cc | 1852 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm-decompile.cc | 119 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm-interp.cc | 337 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm-objdump.cc | 149 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm-opcodecnt.cc | 188 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm-strip.cc | 114 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm-validate.cc | 99 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm2c.cc | 169 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm2wat-fuzz.cc | 30 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wasm2wat.cc | 149 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wast2json.cc | 162 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wat-desugar.cc | 130 | ||||
-rw-r--r-- | third_party/wasm2c/src/tools/wat2wasm.cc | 170 |
13 files changed, 3668 insertions, 0 deletions
diff --git a/third_party/wasm2c/src/tools/spectest-interp.cc b/third_party/wasm2c/src/tools/spectest-interp.cc new file mode 100644 index 0000000000..c27e7a9f69 --- /dev/null +++ b/third_party/wasm2c/src/tools/spectest-interp.cc @@ -0,0 +1,1852 @@ +/* + * 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 "src/binary-reader.h" +#include "src/cast.h" +#include "src/common.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/interp/binary-reader-interp.h" +#include "src/interp/interp-util.h" +#include "src/interp/interp.h" +#include "src/literal.h" +#include "src/option-parser.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-lexer.h" +#include "src/wast-parser.h" + +using namespace wabt; +using namespace wabt::interp; + +static int s_verbose; +static const char* 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; }); + parser.Parse(argc, argv); +} + +namespace spectest { + +class Command; +typedef std::unique_ptr<Command> CommandPtr; +typedef std::vector<CommandPtr> CommandPtrVector; + +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; +}; + +typedef ActionCommandBase<CommandType::Action> ActionCommand; + +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) { + int lane_count = LaneCountFromType(ev.lane_type); + assert(ev.value.type == Type::V128); + assert(lane < lane_count); + + ExpectedValue result; + result.value.type = ev.lane_type; + + v128 vec = ev.value.value.Get<v128>(); + + for (int lane = 0; lane < lane_count; ++lane) { + 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) { + int lane_count = LaneCountFromType(lane_type); + assert(tv.type == Type::V128); + assert(lane < lane_count); + + TypedValue result; + result.type = lane_type; + + v128 vec = tv.value.Get<v128>(); + + for (int lane = 0; lane < lane_count; ++lane) { + 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; +} + +class AssertReturnCommand : public CommandMixin<CommandType::AssertReturn> { + public: + Action action; + std::vector<ExpectedValue> expected; +}; + +template <CommandType TypeEnum> +class AssertTrapCommandBase : public CommandMixin<TypeEnum> { + public: + Action action; + std::string text; +}; + +typedef AssertTrapCommandBase<CommandType::AssertTrap> AssertTrapCommand; +typedef AssertTrapCommandBase<CommandType::AssertExhaustion> + AssertExhaustionCommand; + +template <CommandType TypeEnum> +class AssertModuleCommand : public CommandMixin<TypeEnum> { + public: + ModuleType type = ModuleType::Binary; + std::string filename; + std::string text; +}; + +typedef AssertModuleCommand<CommandType::AssertMalformed> + AssertMalformedCommand; +typedef AssertModuleCommand<CommandType::AssertInvalid> AssertInvalidCommand; +typedef AssertModuleCommand<CommandType::AssertUnlinkable> + AssertUnlinkableCommand; +typedef AssertModuleCommand<CommandType::AssertUninstantiable> + AssertUninstantiableCommand; + +// An extremely simple JSON parser that only knows how to parse the expected +// format from wat2wasm. +class JSONParser { + public: + JSONParser() {} + + wabt::Result ReadFile(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, string_view value_str); + wabt::Result ParseI64Value(uint64_t* out_value, string_view value_str); + wabt::Result ParseF32Value(uint32_t* out_value, + ExpectedNan* out_nan, + string_view value_str, + AllowExpected); + wabt::Result ParseF64Value(uint64_t* out_value, + ExpectedNan* out_nan, + string_view value_str, + AllowExpected); + wabt::Result ParseLaneConstValue(Type lane_type, + int lane, + ExpectedValue* out_value, + string_view value_str, + AllowExpected); + wabt::Result ParseConstValue(Type type, + Value* out_value, + ExpectedNan* out_nan, + 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(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(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", loc_.filename.to_string().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 { + 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, + string_view value_str) { + if (Failed(ParseInt32(value_str.begin(), value_str.end(), out_value, + ParseIntType::UnsignedOnly))) { + PrintError("invalid i32 literal"); + return wabt::Result::Error; + } + return wabt::Result::Ok; +} + +wabt::Result JSONParser::ParseI64Value(uint64_t* out_value, + string_view value_str) { + if (Failed(ParseInt64(value_str.begin(), value_str.end(), 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, + 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.begin(), value_str.end(), 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, + 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.begin(), value_str.end(), 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, + 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()); + 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, + 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; + + default: + PrintError("unknown concrete type: \"%s\"", type.GetName()); + 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 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)); +} + +std::string JSONParser::CreateModulePath(string_view filename) { + string_view 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 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 = MakeUnique<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 = MakeUnique<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 = MakeUnique<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 = MakeUnique<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 = MakeUnique<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 = MakeUnique<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 = MakeUnique<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 = MakeUnique<AssertReturnCommand>(); + EXPECT(","); + CHECK_RESULT(ParseLine(&command->line)); + EXPECT(","); + CHECK_RESULT(ParseAction(&command->action)); + EXPECT(","); + EXPECT_KEY("expected"); + CHECK_RESULT(ParseExpectedValues(&command->expected)); + *out_command = std::move(command); + } else if (Match("\"assert_trap\"")) { + auto command = MakeUnique<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 = MakeUnique<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 { + 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(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 CheckAssertReturnResult(const AssertReturnCommand* command, + int index, + ExpectedValue expected, + TypedValue actual, + bool print_error); + + void TallyCommand(wabt::Result); + + wabt::Result ReadInvalidTextModule(string_view module_filename, + const std::string& header); + wabt::Result ReadInvalidModule(int line_number, + string_view module_filename, + ModuleType module_type, + const char* desc); + wabt::Result ReadUnlinkableModule(int line_number, + 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_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["memory"] = interp::Memory::New(store_, MemoryType{Limits{1, 2}}); + + spectest["global_i32"] = interp::Global::New( + store_, GlobalType{ValueType::I32, Mutability::Const}, Value::Make(u32{666})); + spectest["global_i64"] = interp::Global::New( + store_, GlobalType{ValueType::I64, Mutability::Const}, Value::Make(u64{666})); + spectest["global_f32"] = interp::Global::New( + store_, GlobalType{ValueType::F32, Mutability::Const}, Value::Make(f32{666})); + spectest["global_f64"] = interp::Global::New( + store_, GlobalType{ValueType::F64, Mutability::Const}, Value::Make(f64{666})); +} + +wabt::Result CommandRunner::Run(const Script& script) { + source_filename_ = script.filename; + + for (const CommandPtr& command : script.commands) { + switch (command->type) { + case CommandType::Module: + OnModuleCommand(cast<ModuleCommand>(command.get())); + break; + + case CommandType::Action: + TallyCommand(OnActionCommand(cast<ActionCommand>(command.get()))); + break; + + case CommandType::Register: + OnRegisterCommand(cast<RegisterCommand>(command.get())); + 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; + } + } + + 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()); + func->Call(store_, action->args, result.values, &result.trap, + s_trace_stream); + result.types = func->type().results; + if (verbose == RunVerbosity::Verbose) { + WriteCall(s_stdout_stream.get(), 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::ReadInvalidTextModule(string_view module_filename, + const std::string& header) { + std::vector<uint8_t> file_data; + wabt::Result result = ReadFile(module_filename, &file_data); + std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer( + module_filename, file_data.data(), file_data.size()); + Errors errors; + if (Succeeded(result)) { + std::unique_ptr<wabt::Module> module; + WastParseOptions options(s_features); + result = ParseWatModule(lexer.get(), &module, &errors, &options); + } + + auto line_finder = lexer->MakeLineFinder(); + FormatErrorsToFile(errors, Location::Type::Text, line_finder.get(), stdout, + header, PrintHeader::Once); + return result; +} + +interp::Module::Ptr CommandRunner::ReadModule(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(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, + string_view module_filename, + ModuleType module_type, + const char* desc) { + std::string header = StringPrintf( + "%s:%d: %s passed", source_filename_.c_str(), line_number, desc); + + switch (module_type) { + case ModuleType::Text: { + return ReadInvalidTextModule(module_filename, header); + } + + 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; +} + +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; + } + + 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 = ReadInvalidModule(command->line, command->filename, + command->type, "assert_malformed"); + if (Succeeded(result)) { + PrintError(command->line, "expected module to be malformed: \"%s\"", + command->filename.c_str()); + 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; + } + + 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; + } + + 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; + } + + 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()); + + case ExpectedNan::Canonical: + return StringPrintf("%s:nan:canonical", ev.value.type.GetName()); + } + 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))) { + 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; + + 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 (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; +} + +void CommandRunner::TallyCommand(wabt::Result result) { + if (Succeeded(result)) { + passed_++; + } + total_++; +} + +static int ReadAndRunSpecJSON(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 +} diff --git a/third_party/wasm2c/src/tools/wasm-decompile.cc b/third_party/wasm2c/src/tools/wasm-decompile.cc new file mode 100644 index 0000000000..74491e55c3 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm-decompile.cc @@ -0,0 +1,119 @@ +/* + * Copyright 2019 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 <cassert> +#include <cinttypes> +#include <cstdio> +#include <cstdlib> + +#include "src/apply-names.h" +#include "src/binary-reader.h" +#include "src/binary-reader-ir.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/generate-names.h" +#include "src/ir.h" +#include "src/option-parser.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-lexer.h" +#include "src/decompiler.h" + +using namespace wabt; + +int ProgramMain(int argc, char** argv) { + InitStdio(); + + std::string infile; + std::string outfile; + Features features; + DecompileOptions decompile_options; + bool fail_on_custom_section_error = true; + + { + const char s_description[] = + " Read a file in the WebAssembly binary format, and convert it to\n" + " a decompiled text file.\n" + "\n" + "examples:\n" + " # parse binary file test.wasm and write text file test.dcmp\n" + " $ wasm-decompile test.wasm -o test.dcmp\n"; + OptionParser parser("wasm-decompile", s_description); + parser.AddOption( + 'o', "output", "FILENAME", + "Output file for the decompiled file, by default use stdout", + [&](const char* argument) { + outfile = argument; + ConvertBackslashToSlash(&outfile); + }); + features.AddOptions(&parser); + parser.AddOption("ignore-custom-section-errors", + "Ignore errors in custom sections", + [&]() { fail_on_custom_section_error = false; }); + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [&](const char* argument) { + infile = argument; + ConvertBackslashToSlash(&infile); + }); + parser.Parse(argc, argv); + } + + std::vector<uint8_t> file_data; + Result result = ReadFile(infile.c_str(), &file_data); + if (Succeeded(result)) { + Errors errors; + Module module; + const bool kStopOnFirstError = true; + ReadBinaryOptions options(features, nullptr, + true, kStopOnFirstError, + fail_on_custom_section_error); + result = ReadBinaryIr(infile.c_str(), file_data.data(), file_data.size(), + options, &errors, &module); + if (Succeeded(result)) { + ValidateOptions options(features); + result = ValidateModule(&module, &errors, options); + if (Succeeded(result)) { + result = GenerateNames(&module, + static_cast<NameOpts>(NameOpts::AlphaNames)); + } + if (Succeeded(result)) { + // Must be called after ReadBinaryIr & GenerateNames, and before + // ApplyNames, see comments at definition. + RenameAll(module); + } + if (Succeeded(result)) { + /* TODO(binji): This shouldn't fail; if a name can't be applied + * (because the index is invalid, say) it should just be skipped. */ + Result dummy_result = ApplyNames(&module); + WABT_USE(dummy_result); + } + if (Succeeded(result)) { + auto s = Decompile(module, decompile_options); + FileStream stream(!outfile.empty() ? FileStream(outfile) + : FileStream(stdout)); + stream.WriteData(s.data(), s.size()); + } + } + FormatErrorsToFile(errors, Location::Type::Binary); + } + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wasm-interp.cc b/third_party/wasm2c/src/tools/wasm-interp.cc new file mode 100644 index 0000000000..e349a9e323 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm-interp.cc @@ -0,0 +1,337 @@ +/* + * 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 <cstdio> +#include <cstdlib> +#include <memory> +#include <string> +#include <vector> + +#include "src/binary-reader.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/interp/binary-reader-interp.h" +#include "src/interp/interp-util.h" +#include "src/interp/interp-wasi.h" +#include "src/interp/interp.h" +#include "src/option-parser.h" +#include "src/stream.h" + +#ifdef WITH_WASI +#include "uvwasi.h" +#endif + +using namespace wabt; +using namespace wabt::interp; + +static int s_verbose; +static const char* s_infile; +static Thread::Options s_thread_options; +static Stream* s_trace_stream; +static bool s_run_all_exports; +static bool s_host_print; +static bool s_dummy_import_func; +static Features s_features; +static bool s_wasi; +static std::vector<std::string> s_wasi_env; +static std::vector<std::string> s_wasi_argv; +static std::vector<std::string> s_wasi_dirs; + +static std::unique_ptr<FileStream> s_log_stream; +static std::unique_ptr<FileStream> s_stdout_stream; +static std::unique_ptr<FileStream> s_stderr_stream; + +static Store s_store; + +static const char s_description[] = + R"( read a file in the wasm binary format, and run in it a stack-based + interpreter. + +examples: + # parse binary file test.wasm, and type-check it + $ wasm-interp test.wasm + + # parse test.wasm and run all its exported functions + $ wasm-interp test.wasm --run-all-exports + + # parse test.wasm, run the exported functions and trace the output + $ wasm-interp test.wasm --run-all-exports --trace + + # 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 +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm-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.AddOption("wasi", + "Assume input module is WASI compliant (Export " + " WASI API the the module and invoke _start function)", + []() { s_wasi = true; }); + parser.AddOption( + 'e', "env", "ENV", + "Pass the given environment string in the WASI runtime", + [](const std::string& argument) { s_wasi_env.push_back(argument); }); + parser.AddOption( + 'd', "dir", "DIR", "Pass the given directory the the WASI runtime", + [](const std::string& argument) { s_wasi_dirs.push_back(argument); }); + 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.AddOption( + "dummy-import-func", + "Provide a dummy implementation of all imported functions. The function " + "will log the call and return an appropriate zero value.", + []() { s_dummy_import_func = true; }); + + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { s_infile = argument; }); + parser.AddArgument( + "arg", OptionParser::ArgumentCount::ZeroOrMore, + [](const char* argument) { s_wasi_argv.push_back(argument); }); + parser.Parse(argc, argv); +} + +Result RunAllExports(const Instance::Ptr& instance, Errors* errors) { + Result result = Result::Ok; + + auto module = s_store.UnsafeGet<Module>(instance->module()); + auto&& module_desc = module->desc(); + + for (auto&& export_ : module_desc.exports) { + if (export_.type.type->kind != ExternalKind::Func) { + continue; + } + auto* func_type = cast<FuncType>(export_.type.type.get()); + if (func_type->params.empty()) { + if (s_trace_stream) { + s_trace_stream->Writef(">>> running export \"%s\":\n", + export_.type.name.c_str()); + } + auto func = s_store.UnsafeGet<Func>(instance->funcs()[export_.index]); + Values params; + Values results; + Trap::Ptr trap; + result |= func->Call(s_store, params, results, &trap, s_trace_stream); + WriteCall(s_stdout_stream.get(), export_.type.name, *func_type, params, + results, trap); + } + } + + return result; +} + +static void BindImports(const Module::Ptr& module, RefVec& imports) { + auto* stream = s_stdout_stream.get(); + + for (auto&& import : module->desc().imports) { + if (import.type.type->kind == ExternKind::Func && + ((s_host_print && import.type.module == "host" && + import.type.name == "print") || + s_dummy_import_func)) { + auto func_type = *cast<FuncType>(import.type.type.get()); + auto import_name = StringPrintf("%s.%s", import.type.module.c_str(), + import.type.name.c_str()); + + auto host_func = + HostFunc::New(s_store, func_type, + [=](Thread& thread, const Values& params, + Values& results, Trap::Ptr* trap) -> Result { + printf("called host "); + WriteCall(stream, import_name, func_type, params, + results, *trap); + return Result::Ok; + }); + imports.push_back(host_func.ref()); + continue; + } + + // By default, just push an null reference. This won't resolve, and + // instantiation will fail. + imports.push_back(Ref::Null); + } +} + +static Result ReadModule(const char* module_filename, + Errors* errors, + Module::Ptr* out_module) { + auto* stream = s_stdout_stream.get(); + std::vector<uint8_t> file_data; + CHECK_RESULT(ReadFile(module_filename, &file_data)); + + ModuleDesc module_desc; + const bool kReadDebugNames = true; + const bool kStopOnFirstError = true; + const bool kFailOnCustomSectionError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, + kStopOnFirstError, kFailOnCustomSectionError); + CHECK_RESULT(ReadBinaryInterp(file_data.data(), file_data.size(), options, + errors, &module_desc)); + + if (s_verbose) { + module_desc.istream.Disassemble(stream); + } + + *out_module = Module::New(s_store, module_desc); + return Result::Ok; +} + +static Result InstantiateModule(RefVec& imports, + const Module::Ptr& module, + Instance::Ptr* out_instance) { + RefPtr<Trap> trap; + *out_instance = Instance::Instantiate(s_store, module.ref(), imports, &trap); + if (!*out_instance) { + WriteTrap(s_stderr_stream.get(), "error initializing module", trap); + return Result::Error; + } + return Result::Ok; +} + +static Result ReadAndRunModule(const char* module_filename) { + Errors errors; + Module::Ptr module; + Result result = ReadModule(module_filename, &errors, &module); + if (!Succeeded(result)) { + FormatErrorsToFile(errors, Location::Type::Binary); + return result; + } + + RefVec imports; + +#if WITH_WASI + uvwasi_t uvwasi; +#endif + + if (s_wasi) { +#if WITH_WASI + uvwasi_errno_t err; + uvwasi_options_t init_options; + + std::vector<const char*> argv; + argv.push_back(module_filename); + for (auto& s : s_wasi_argv) { + if (s_trace_stream) { + s_trace_stream->Writef("wasi: arg: \"%s\"\n", s.c_str()); + } + argv.push_back(s.c_str()); + } + argv.push_back(nullptr); + + std::vector<const char*> envp; + for (auto& s : s_wasi_env) { + if (s_trace_stream) { + s_trace_stream->Writef("wasi: env: \"%s\"\n", s.c_str()); + } + envp.push_back(s.c_str()); + } + envp.push_back(nullptr); + + std::vector<uvwasi_preopen_t> dirs; + for (auto& dir : s_wasi_dirs) { + if (s_trace_stream) { + s_trace_stream->Writef("wasi: dir: \"%s\"\n", dir.c_str()); + } + dirs.push_back({dir.c_str(), dir.c_str()}); + } + + /* Setup the initialization options. */ + init_options.in = 0; + init_options.out = 1; + init_options.err = 2; + init_options.fd_table_size = 3; + init_options.argc = argv.size() - 1; + init_options.argv = argv.data(); + init_options.envp = envp.data(); + init_options.preopenc = dirs.size(); + init_options.preopens = dirs.data(); + init_options.allocator = NULL; + + err = uvwasi_init(&uvwasi, &init_options); + if (err != UVWASI_ESUCCESS) { + s_stderr_stream.get()->Writef("error initialiazing uvwasi: %d\n", err); + return Result::Error; + } + CHECK_RESULT(WasiBindImports(module, imports, s_stderr_stream.get(), + s_trace_stream)); +#else + s_stderr_stream.get()->Writef("wasi support not compiled in\n"); + return Result::Error; +#endif + } else { + BindImports(module, imports); + } + BindImports(module, imports); + + Instance::Ptr instance; + CHECK_RESULT(InstantiateModule(imports, module, &instance)); + + if (s_run_all_exports) { + RunAllExports(instance, &errors); + } +#ifdef WITH_WASI + if (s_wasi) { + CHECK_RESULT( + WasiRunStart(instance, &uvwasi, s_stderr_stream.get(), s_trace_stream)); + } +#endif + + return Result::Ok; +} + +int ProgramMain(int argc, char** argv) { + InitStdio(); + s_stdout_stream = FileStream::CreateStdout(); + s_stderr_stream = FileStream::CreateStderr(); + + ParseOptions(argc, argv); + + wabt::Result result = ReadAndRunModule(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/third_party/wasm2c/src/tools/wasm-objdump.cc b/third_party/wasm2c/src/tools/wasm-objdump.cc new file mode 100644 index 0000000000..fbfed12cf1 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm-objdump.cc @@ -0,0 +1,149 @@ +/* + * 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 <cstdio> +#include <cstdlib> +#include <cstring> + +#include "src/common.h" +#include "src/option-parser.h" +#include "src/stream.h" +#include "src/binary-reader.h" +#include "src/binary-reader-objdump.h" + +using namespace wabt; + +static const char s_description[] = +R"( Print information about the contents of wasm binaries. + +examples: + $ wasm-objdump test.wasm +)"; + +static ObjdumpOptions s_objdump_options; + +static std::vector<const char*> s_infiles; + +static std::unique_ptr<FileStream> s_log_stream; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm-objdump", s_description); + + parser.AddOption('h', "headers", "Print headers", + []() { s_objdump_options.headers = true; }); + parser.AddOption( + 'j', "section", "SECTION", "Select just one section", + [](const char* argument) { s_objdump_options.section_name = argument; }); + parser.AddOption('s', "full-contents", "Print raw section contents", + []() { s_objdump_options.raw = true; }); + parser.AddOption('d', "disassemble", "Disassemble function bodies", + []() { s_objdump_options.disassemble = true; }); + parser.AddOption("debug", "Print extra debug information", []() { + s_objdump_options.debug = true; + s_log_stream = FileStream::CreateStderr(); + s_objdump_options.log_stream = s_log_stream.get(); + }); + parser.AddOption('x', "details", "Show section details", + []() { s_objdump_options.details = true; }); + parser.AddOption('r', "reloc", "Show relocations inline with disassembly", + []() { s_objdump_options.relocs = true; }); + parser.AddOption(0, "section-offsets", + "Print section offsets instead of file offsets " + "in code disassembly", + []() { s_objdump_options.section_offsets = true; }); + parser.AddArgument( + "filename", OptionParser::ArgumentCount::OneOrMore, + [](const char* argument) { s_infiles.push_back(argument); }); + + parser.Parse(argc, argv); +} + +Result dump_file(const char* filename) { + std::vector<uint8_t> file_data; + CHECK_RESULT(ReadFile(filename, &file_data)); + + uint8_t* data = file_data.data(); + size_t size = file_data.size(); + + // Perform serveral passed over the binary in order to print out different + // types of information. + s_objdump_options.filename = filename; + printf("\n"); + + ObjdumpState state; + + Result result = Result::Ok; + + // Pass 0: Prepass + s_objdump_options.mode = ObjdumpMode::Prepass; + result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state); + s_objdump_options.log_stream = nullptr; + + // Pass 1: Print the section headers + if (s_objdump_options.headers) { + s_objdump_options.mode = ObjdumpMode::Headers; + result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state); + } + + // Pass 2: Print extra information based on section type + if (s_objdump_options.details) { + s_objdump_options.mode = ObjdumpMode::Details; + result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state); + } + + // Pass 3: Disassemble code section + if (s_objdump_options.disassemble) { + s_objdump_options.mode = ObjdumpMode::Disassemble; + result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state); + } + + // Pass 4: Dump to raw contents of the sections + if (s_objdump_options.raw) { + s_objdump_options.mode = ObjdumpMode::RawData; + result |= ReadBinaryObjdump(data, size, &s_objdump_options, &state); + } + + return result; +} + +int ProgramMain(int argc, char** argv) { + InitStdio(); + + ParseOptions(argc, argv); + if (!s_objdump_options.headers && !s_objdump_options.details && + !s_objdump_options.disassemble && !s_objdump_options.raw) { + fprintf(stderr, "At least one of the following switches must be given:\n"); + fprintf(stderr, " -d/--disassemble\n"); + fprintf(stderr, " -h/--headers\n"); + fprintf(stderr, " -x/--details\n"); + fprintf(stderr, " -s/--full-contents\n"); + return 1; + } + + for (const char* filename: s_infiles) { + if (Failed(dump_file(filename))) { + return 1; + } + } + + return 0; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wasm-opcodecnt.cc b/third_party/wasm2c/src/tools/wasm-opcodecnt.cc new file mode 100644 index 0000000000..5d6225f098 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm-opcodecnt.cc @@ -0,0 +1,188 @@ +/* + * 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 <cerrno> +#include <cinttypes> +#include <cstdio> +#include <cstdlib> +#include <map> +#include <vector> + +#include "src/binary-reader.h" +#include "src/binary-reader-opcnt.h" +#include "src/option-parser.h" +#include "src/stream.h" + +#define ERROR(fmt, ...) \ + fprintf(stderr, "%s:%d: " fmt, __FILE__, __LINE__, __VA_ARGS__) + +using namespace wabt; + +static int s_verbose; +static const char* s_infile; +static const char* s_outfile; +static size_t s_cutoff = 0; +static const char* s_separator = ": "; + +static ReadBinaryOptions s_read_binary_options; +static std::unique_ptr<FileStream> s_log_stream; +static Features s_features; + +static const char s_description[] = +R"( Read a file in the wasm binary format, and count opcode usage for + instructions. + +examples: + # parse binary file test.wasm and write pcode dist file test.dist + $ wasm-opcodecnt test.wasm -o test.dist +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm-opcodecnt", s_description); + + parser.AddOption('v', "verbose", "Use multiple times for more info", []() { + s_verbose++; + s_log_stream = FileStream::CreateStderr(); + s_read_binary_options.log_stream = s_log_stream.get(); + }); + s_features.AddOptions(&parser); + parser.AddOption('o', "output", "FILENAME", + "Output file for the opcode counts, by default use stdout", + [](const char* argument) { s_outfile = argument; }); + parser.AddOption( + 'c', "cutoff", "N", "Cutoff for reporting counts less than N", + [](const std::string& argument) { s_cutoff = atol(argument.c_str()); }); + parser.AddOption( + 's', "separator", "SEPARATOR", + "Separator text between element and count when reporting counts", + [](const char* argument) { s_separator = argument; }); + parser.AddArgument("filename", OptionParser::ArgumentCount::OneOrMore, + [](const char* argument) { s_infile = argument; }); + parser.Parse(argc, argv); +} + +template <typename T> +struct SortByCountDescending { + bool operator()(const T& lhs, const T& rhs) const { + return lhs.second > rhs.second; + } +}; + +template <typename T> +struct WithinCutoff { + bool operator()(const T& pair) const { + return pair.second >= s_cutoff; + } +}; + +static size_t SumCounts(const OpcodeInfoCounts& info_counts) { + size_t sum = 0; + for (auto& pair : info_counts) { + sum += pair.second; + } + return sum; +} + +void WriteCounts(Stream& stream, const OpcodeInfoCounts& info_counts) { + typedef std::pair<Opcode, size_t> OpcodeCountPair; + + std::map<Opcode, size_t> counts; + for (auto& info_count_pair: info_counts) { + Opcode opcode = info_count_pair.first.opcode(); + size_t count = info_count_pair.second; + counts[opcode] += count; + } + + std::vector<OpcodeCountPair> sorted; + std::copy_if(counts.begin(), counts.end(), std::back_inserter(sorted), + WithinCutoff<OpcodeCountPair>()); + + // Use a stable sort to keep the elements with the same count in opcode + // order (since the Opcode map is sorted). + std::stable_sort(sorted.begin(), sorted.end(), + SortByCountDescending<OpcodeCountPair>()); + + for (auto& pair : sorted) { + Opcode opcode = pair.first; + size_t count = pair.second; + stream.Writef("%s%s%" PRIzd "\n", opcode.GetName(), s_separator, count); + } +} + +void WriteCountsWithImmediates(Stream& stream, + const OpcodeInfoCounts& counts) { + // Remove const from the key type so we can sort below. + typedef std::pair<std::remove_const<OpcodeInfoCounts::key_type>::type, + OpcodeInfoCounts::mapped_type> + OpcodeInfoCountPair; + + std::vector<OpcodeInfoCountPair> sorted; + std::copy_if(counts.begin(), counts.end(), std::back_inserter(sorted), + WithinCutoff<OpcodeInfoCountPair>()); + + // Use a stable sort to keep the elements with the same count in opcode info + // order (since the OpcodeInfoCounts map is sorted). + std::stable_sort(sorted.begin(), sorted.end(), + SortByCountDescending<OpcodeInfoCountPair>()); + + for (auto& pair : sorted) { + auto&& info = pair.first; + size_t count = pair.second; + info.Write(stream); + stream.Writef("%s%" PRIzd "\n", s_separator, count); + } +} + +int ProgramMain(int argc, char** argv) { + InitStdio(); + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + Result result = ReadFile(s_infile, &file_data); + if (Failed(result)) { + const char* input_name = s_infile ? s_infile : "stdin"; + ERROR("Unable to parse: %s", input_name); + return 1; + } + + FileStream stream(s_outfile ? FileStream(s_outfile) : FileStream(stdout)); + + if (Succeeded(result)) { + OpcodeInfoCounts counts; + s_read_binary_options.features = s_features; + result = ReadBinaryOpcnt(file_data.data(), file_data.size(), + s_read_binary_options, &counts); + if (Succeeded(result)) { + stream.Writef("Total opcodes: %" PRIzd "\n\n", SumCounts(counts)); + + stream.Writef("Opcode counts:\n"); + WriteCounts(stream, counts); + + stream.Writef("\nOpcode counts with immediates:\n"); + WriteCountsWithImmediates(stream, counts); + } + } + + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wasm-strip.cc b/third_party/wasm2c/src/tools/wasm-strip.cc new file mode 100644 index 0000000000..f838672064 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm-strip.cc @@ -0,0 +1,114 @@ +/* + * Copyright 2018 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 "src/binary.h" +#include "src/binary-reader.h" +#include "src/binary-reader-nop.h" +#include "src/error-formatter.h" +#include "src/leb128.h" +#include "src/option-parser.h" +#include "src/stream.h" + +using namespace wabt; + +static std::string s_filename; + +static const char s_description[] = +R"( Remove sections of a WebAssembly binary file. + +examples: + # Remove all custom sections from test.wasm + $ wasm-strip test.wasm +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm-strip", s_description); + + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { + s_filename = argument; + ConvertBackslashToSlash(&s_filename); + }); + parser.Parse(argc, argv); +} + +class BinaryReaderStrip : public BinaryReaderNop { + public: + explicit BinaryReaderStrip(Errors* errors) + : errors_(errors) { + stream_.WriteU32(WABT_BINARY_MAGIC, "WASM_BINARY_MAGIC"); + stream_.WriteU32(WABT_BINARY_VERSION, "WASM_BINARY_VERSION"); + } + + bool OnError(const Error& error) override { + errors_->push_back(error); + return true; + } + + Result BeginSection(Index section_index, + BinarySection section_type, + Offset size) override { + if (section_type == BinarySection::Custom) { + return Result::Ok; + } + stream_.WriteU8Enum(section_type, "section code"); + WriteU32Leb128(&stream_, size, "section size"); + stream_.WriteData(state->data + state->offset, size, "section data"); + return Result::Ok; + } + + Result WriteToFile(string_view filename) { + return stream_.WriteToFile(filename); + } + + private: + MemoryStream stream_; + Errors* errors_; +}; + +int ProgramMain(int argc, char** argv) { + Result result; + + InitStdio(); + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + result = ReadFile(s_filename.c_str(), &file_data); + if (Succeeded(result)) { + Errors errors; + Features features; + const bool kReadDebugNames = false; + const bool kStopOnFirstError = true; + const bool kFailOnCustomSectionError = false; + ReadBinaryOptions options(features, nullptr, kReadDebugNames, + kStopOnFirstError, kFailOnCustomSectionError); + + BinaryReaderStrip reader(&errors); + result = ReadBinary(file_data.data(), file_data.size(), &reader, options); + FormatErrorsToFile(errors, Location::Type::Binary); + + if (Succeeded(result)) { + result = reader.WriteToFile(s_filename); + } + } + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wasm-validate.cc b/third_party/wasm2c/src/tools/wasm-validate.cc new file mode 100644 index 0000000000..a55c5ed898 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm-validate.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2017 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 <cassert> +#include <cinttypes> +#include <cstdio> +#include <cstdlib> + +#include "src/binary-reader.h" +#include "src/binary-reader-ir.h" +#include "src/error-formatter.h" +#include "src/ir.h" +#include "src/option-parser.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-lexer.h" + +using namespace wabt; + +static int s_verbose; +static std::string s_infile; +static Features s_features; +static bool s_read_debug_names = true; +static bool s_fail_on_custom_section_error = true; +static std::unique_ptr<FileStream> s_log_stream; + +static const char s_description[] = +R"( Read a file in the WebAssembly binary format, and validate it. + +examples: + # validate binary file test.wasm + $ wasm-validate test.wasm +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm-validate", 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("no-debug-names", "Ignore debug names in the binary file", + []() { s_read_debug_names = false; }); + parser.AddOption("ignore-custom-section-errors", + "Ignore errors in custom sections", + []() { s_fail_on_custom_section_error = false; }); + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { + s_infile = argument; + ConvertBackslashToSlash(&s_infile); + }); + parser.Parse(argc, argv); +} + +int ProgramMain(int argc, char** argv) { + Result result; + + InitStdio(); + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + result = ReadFile(s_infile.c_str(), &file_data); + if (Succeeded(result)) { + Errors errors; + Module module; + const bool kStopOnFirstError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), + s_read_debug_names, kStopOnFirstError, + s_fail_on_custom_section_error); + result = ReadBinaryIr(s_infile.c_str(), file_data.data(), file_data.size(), + options, &errors, &module); + if (Succeeded(result)) { + ValidateOptions options(s_features); + result = ValidateModule(&module, &errors, options); + } + FormatErrorsToFile(errors, Location::Type::Binary); + } + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wasm2c.cc b/third_party/wasm2c/src/tools/wasm2c.cc new file mode 100644 index 0000000000..680be01045 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm2c.cc @@ -0,0 +1,169 @@ +/* + * Copyright 2017 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 <cassert> +#include <cinttypes> +#include <cstdio> +#include <cstdlib> + +#include "src/apply-names.h" +#include "src/binary-reader.h" +#include "src/binary-reader-ir.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/generate-names.h" +#include "src/ir.h" +#include "src/option-parser.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-lexer.h" + +#include "src/c-writer.h" + +using namespace wabt; + +static int s_verbose; +static std::string s_infile; +static std::string s_outfile; +static Features s_features; +static WriteCOptions s_write_c_options; +static bool s_read_debug_names = true; +static std::unique_ptr<FileStream> s_log_stream; + +static const char s_description[] = +R"( Read a file in the WebAssembly binary format, and convert it to + a C source file and header. + +examples: + # parse binary file test.wasm and write test.c and test.h + $ wasm2c test.wasm -o test.c + + # parse test.wasm, write test.c and test.h, but ignore the debug names, if any + $ wasm2c test.wasm --no-debug-names -o test.c +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm2c", s_description); + + parser.AddOption('v', "verbose", "Use multiple times for more info", []() { + s_verbose++; + s_log_stream = FileStream::CreateStderr(); + }); + parser.AddOption( + 'o', "output", "FILENAME", + "Output file for the generated C source file, by default use stdout", + [](const char* argument) { + s_outfile = argument; + ConvertBackslashToSlash(&s_outfile); + }); + parser.AddOption( + 'n', "modname", "MODNAME", + "Unique name for the module being generated. Each wasm sandboxed module in a single application should have a unique name.", + [](const char* argument) { + s_write_c_options.mod_name = argument; + }); + s_features.AddOptions(&parser); + parser.AddOption("no-debug-names", "Ignore debug names in the binary file", + []() { s_read_debug_names = false; }); + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { + s_infile = argument; + ConvertBackslashToSlash(&s_infile); + }); + parser.Parse(argc, argv); + + // TODO(binji): currently wasm2c doesn't support any non-default feature + // flags. + bool any_non_default_feature = false; +#define WABT_FEATURE(variable, flag, default_, help) \ + any_non_default_feature |= (s_features.variable##_enabled() != default_); +#include "src/feature.def" +#undef WABT_FEATURE + + if (any_non_default_feature) { + fprintf(stderr, "wasm2c currently support only default feature flags.\n"); + exit(1); + } +} + +// TODO(binji): copied from binary-writer-spec.cc, probably should share. +static string_view strip_extension(string_view s) { + string_view ext = s.substr(s.find_last_of('.')); + string_view result = s; + + if (ext == ".c") + result.remove_suffix(ext.length()); + return result; +} + +int ProgramMain(int argc, char** argv) { + Result result; + + InitStdio(); + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + result = ReadFile(s_infile.c_str(), &file_data); + if (Succeeded(result)) { + Errors errors; + Module module; + const bool kStopOnFirstError = true; + const bool kFailOnCustomSectionError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), + s_read_debug_names, kStopOnFirstError, + kFailOnCustomSectionError); + result = ReadBinaryIr(s_infile.c_str(), file_data.data(), file_data.size(), + options, &errors, &module); + if (Succeeded(result)) { + if (Succeeded(result)) { + ValidateOptions options(s_features); + result = ValidateModule(&module, &errors, options); + result |= GenerateNames(&module); + } + + if (Succeeded(result)) { + /* TODO(binji): This shouldn't fail; if a name can't be applied + * (because the index is invalid, say) it should just be skipped. */ + Result dummy_result = ApplyNames(&module); + WABT_USE(dummy_result); + } + + if (Succeeded(result)) { + if (!s_outfile.empty()) { + std::string header_name = + strip_extension(s_outfile).to_string() + ".h"; + FileStream c_stream(s_outfile.c_str()); + FileStream h_stream(header_name); + result = WriteC(&c_stream, &h_stream, header_name.c_str(), &module, + s_write_c_options); + } else { + FileStream stream(stdout); + result = + WriteC(&stream, &stream, "wasm.h", &module, s_write_c_options); + } + } + } + FormatErrorsToFile(errors, Location::Type::Binary); + } + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} + diff --git a/third_party/wasm2c/src/tools/wasm2wat-fuzz.cc b/third_party/wasm2c/src/tools/wasm2wat-fuzz.cc new file mode 100644 index 0000000000..1318ef6284 --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm2wat-fuzz.cc @@ -0,0 +1,30 @@ +// Copyright 2019 Google LLC +// +// 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. +// +// This file is copied from the oss-fuzz project: +// +// https://github.com/google/oss-fuzz/blob/master/projects/wabt/wasm2wat_fuzzer.cc + +#include "src/binary-reader-ir.h" +#include "src/binary-reader.h" +#include "src/common.h" +#include "src/ir.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + wabt::ReadBinaryOptions options; + wabt::Errors errors; + wabt::Module module; + wabt::ReadBinaryIr("dummy filename", data, size, options, &errors, &module); + return 0; +} diff --git a/third_party/wasm2c/src/tools/wasm2wat.cc b/third_party/wasm2c/src/tools/wasm2wat.cc new file mode 100644 index 0000000000..58e89849fe --- /dev/null +++ b/third_party/wasm2c/src/tools/wasm2wat.cc @@ -0,0 +1,149 @@ +/* + * 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 <cassert> +#include <cinttypes> +#include <cstdio> +#include <cstdlib> + +#include "src/apply-names.h" +#include "src/binary-reader.h" +#include "src/binary-reader-ir.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/generate-names.h" +#include "src/ir.h" +#include "src/option-parser.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-lexer.h" +#include "src/wat-writer.h" + +using namespace wabt; + +static int s_verbose; +static std::string s_infile; +static std::string s_outfile; +static Features s_features; +static WriteWatOptions s_write_wat_options; +static bool s_generate_names = false; +static bool s_read_debug_names = true; +static bool s_fail_on_custom_section_error = true; +static std::unique_ptr<FileStream> s_log_stream; +static bool s_validate = true; + +static const char s_description[] = +R"( Read a file in the WebAssembly binary format, and convert it to + the WebAssembly text format. + +examples: + # parse binary file test.wasm and write text file test.wast + $ wasm2wat test.wasm -o test.wat + + # parse test.wasm, write test.wat, but ignore the debug names, if any + $ wasm2wat test.wasm --no-debug-names -o test.wat +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wasm2wat", s_description); + + parser.AddOption('v', "verbose", "Use multiple times for more info", []() { + s_verbose++; + s_log_stream = FileStream::CreateStderr(); + }); + parser.AddOption( + 'o', "output", "FILENAME", + "Output file for the generated wast file, by default use stdout", + [](const char* argument) { + s_outfile = argument; + ConvertBackslashToSlash(&s_outfile); + }); + parser.AddOption('f', "fold-exprs", "Write folded expressions where possible", + []() { s_write_wat_options.fold_exprs = true; }); + s_features.AddOptions(&parser); + parser.AddOption("inline-exports", "Write all exports inline", + []() { s_write_wat_options.inline_export = true; }); + parser.AddOption("inline-imports", "Write all imports inline", + []() { s_write_wat_options.inline_import = true; }); + parser.AddOption("no-debug-names", "Ignore debug names in the binary file", + []() { s_read_debug_names = false; }); + parser.AddOption("ignore-custom-section-errors", + "Ignore errors in custom sections", + []() { s_fail_on_custom_section_error = false; }); + parser.AddOption( + "generate-names", + "Give auto-generated names to non-named functions, types, etc.", + []() { s_generate_names = true; }); + parser.AddOption("no-check", "Don't check for invalid modules", + []() { s_validate = false; }); + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { + s_infile = argument; + ConvertBackslashToSlash(&s_infile); + }); + parser.Parse(argc, argv); +} + +int ProgramMain(int argc, char** argv) { + Result result; + + InitStdio(); + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + result = ReadFile(s_infile.c_str(), &file_data); + if (Succeeded(result)) { + Errors errors; + Module module; + const bool kStopOnFirstError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), + s_read_debug_names, kStopOnFirstError, + s_fail_on_custom_section_error); + result = ReadBinaryIr(s_infile.c_str(), file_data.data(), file_data.size(), + options, &errors, &module); + if (Succeeded(result)) { + if (Succeeded(result) && s_validate) { + ValidateOptions options(s_features); + result = ValidateModule(&module, &errors, options); + } + + if (s_generate_names) { + result = GenerateNames(&module); + } + + if (Succeeded(result)) { + /* TODO(binji): This shouldn't fail; if a name can't be applied + * (because the index is invalid, say) it should just be skipped. */ + Result dummy_result = ApplyNames(&module); + WABT_USE(dummy_result); + } + + if (Succeeded(result)) { + FileStream stream(!s_outfile.empty() ? FileStream(s_outfile) + : FileStream(stdout)); + result = WriteWat(&stream, &module, s_write_wat_options); + } + } + FormatErrorsToFile(errors, Location::Type::Binary); + } + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wast2json.cc b/third_party/wasm2c/src/tools/wast2json.cc new file mode 100644 index 0000000000..a5c9a47d1d --- /dev/null +++ b/third_party/wasm2c/src/tools/wast2json.cc @@ -0,0 +1,162 @@ +/* + * 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 <cassert> +#include <cstdarg> +#include <cstdint> +#include <cstdlib> +#include <cstdio> +#include <string> + +#include "config.h" + +#include "src/binary-writer.h" +#include "src/binary-writer-spec.h" +#include "src/common.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/filenames.h" +#include "src/ir.h" +#include "src/option-parser.h" +#include "src/resolve-names.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-parser.h" + +using namespace wabt; + +static const char* s_infile; +static std::string s_outfile; +static int s_verbose; +static WriteBinaryOptions s_write_binary_options; +static bool s_validate = true; +static bool s_debug_parsing; +static Features s_features; + +static std::unique_ptr<FileStream> s_log_stream; + +static const char s_description[] = +R"( read a file in the wasm spec test format, check it for errors, and + convert it to a JSON file and associated wasm binary files. + +examples: + # parse spec-test.wast, and write files to spec-test.json. Modules are + # written to spec-test.0.wasm, spec-test.1.wasm, etc. + $ wast2json spec-test.wast -o spec-test.json +)"; + +static void ParseOptions(int argc, char* argv[]) { + OptionParser parser("wast2json", s_description); + + parser.AddOption('v', "verbose", "Use multiple times for more info", []() { + s_verbose++; + s_log_stream = FileStream::CreateStderr(); + }); + parser.AddOption("debug-parser", "Turn on debugging the parser of wast files", + []() { s_debug_parsing = true; }); + s_features.AddOptions(&parser); + parser.AddOption('o', "output", "FILE", "output JSON file", + [](const char* argument) { s_outfile = argument; }); + parser.AddOption( + 'r', "relocatable", + "Create a relocatable wasm binary (suitable for linking with e.g. lld)", + []() { s_write_binary_options.relocatable = true; }); + parser.AddOption( + "no-canonicalize-leb128s", + "Write all LEB128 sizes as 5-bytes instead of their minimal size", + []() { s_write_binary_options.canonicalize_lebs = false; }); + parser.AddOption("debug-names", + "Write debug names to the generated binary file", + []() { s_write_binary_options.write_debug_names = true; }); + parser.AddOption("no-check", "Don't check for invalid modules", + []() { s_validate = false; }); + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { s_infile = argument; }); + + parser.Parse(argc, argv); +} + +static std::string DefaultOuputName(string_view input_name) { + // Strip existing extension and add .json + std::string result(StripExtension(GetBasename(input_name))); + result += ".json"; + + return result; +} + +int ProgramMain(int argc, char** argv) { + InitStdio(); + + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + Result result = ReadFile(s_infile, &file_data); + std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer( + s_infile, file_data.data(), file_data.size()); + if (Failed(result)) { + WABT_FATAL("unable to read file: %s\n", s_infile); + } + + Errors errors; + std::unique_ptr<Script> script; + WastParseOptions parse_wast_options(s_features); + result = ParseWastScript(lexer.get(), &script, &errors, &parse_wast_options); + + if (Succeeded(result) && s_validate) { + ValidateOptions options(s_features); + result = ValidateScript(script.get(), &errors, options); + } + + if (Succeeded(result)) { + if (s_outfile.empty()) { + s_outfile = DefaultOuputName(s_infile); + } + + std::vector<FilenameMemoryStreamPair> module_streams; + MemoryStream json_stream; + + std::string output_basename = StripExtension(s_outfile).to_string(); + s_write_binary_options.features = s_features; + result = WriteBinarySpecScript(&json_stream, script.get(), s_infile, + output_basename, s_write_binary_options, + &module_streams, s_log_stream.get()); + + if (Succeeded(result)) { + result = json_stream.WriteToFile(s_outfile); + } + + if (Succeeded(result)) { + for (auto iter = module_streams.begin(); iter != module_streams.end(); + ++iter) { + result = iter->stream->WriteToFile(iter->filename); + if (!Succeeded(result)) { + break; + } + } + } + } + + auto line_finder = lexer->MakeLineFinder(); + FormatErrorsToFile(errors, Location::Type::Text, line_finder.get()); + + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wat-desugar.cc b/third_party/wasm2c/src/tools/wat-desugar.cc new file mode 100644 index 0000000000..cc6a2cba8e --- /dev/null +++ b/third_party/wasm2c/src/tools/wat-desugar.cc @@ -0,0 +1,130 @@ +/* + * 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 <cassert> +#include <cstdarg> +#include <cstdint> +#include <cstdio> +#include <cstdlib> + +#include "config.h" + +#include "src/apply-names.h" +#include "src/common.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/generate-names.h" +#include "src/ir.h" +#include "src/option-parser.h" +#include "src/stream.h" +#include "src/wast-parser.h" +#include "src/wat-writer.h" + +using namespace wabt; + +static const char* s_infile; +static const char* s_outfile; +static WriteWatOptions s_write_wat_options; +static bool s_generate_names; +static bool s_debug_parsing; +static Features s_features; + +static const char s_description[] = +R"( read a file in the wasm s-expression format and format it. + +examples: + # write output to stdout + $ wat-desugar test.wat + + # write output to test2.wat + $ wat-desugar test.wat -o test2.wat + + # generate names for indexed variables + $ wat-desugar --generate-names test.wat +)"; + +static void ParseOptions(int argc, char** argv) { + OptionParser parser("wat-desugar", s_description); + + parser.AddOption('o', "output", "FILE", "Output file for the formatted file", + [](const char* argument) { s_outfile = argument; }); + parser.AddOption("debug-parser", "Turn on debugging the parser of wat files", + []() { s_debug_parsing = true; }); + parser.AddOption('f', "fold-exprs", "Write folded expressions where possible", + []() { s_write_wat_options.fold_exprs = true; }); + parser.AddOption("inline-exports", "Write all exports inline", + []() { s_write_wat_options.inline_export = true; }); + parser.AddOption("inline-imports", "Write all imports inline", + []() { s_write_wat_options.inline_import = true; }); + s_features.AddOptions(&parser); + parser.AddOption( + "generate-names", + "Give auto-generated names to non-named functions, types, etc.", + []() { s_generate_names = true; }); + + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { s_infile = argument; }); + parser.Parse(argc, argv); +} + +int ProgramMain(int argc, char** argv) { + InitStdio(); + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + Result result = ReadFile(s_infile, &file_data); + if (Failed(result)) { + WABT_FATAL("unable to read %s\n", s_infile); + } + + std::unique_ptr<WastLexer> lexer(WastLexer::CreateBufferLexer( + s_infile, file_data.data(), file_data.size())); + + Errors errors; + std::unique_ptr<Script> script; + WastParseOptions parse_wast_options(s_features); + result = ParseWastScript(lexer.get(), &script, &errors, &parse_wast_options); + auto line_finder = lexer->MakeLineFinder(); + FormatErrorsToFile(errors, Location::Type::Text); + + if (Succeeded(result)) { + Module* module = script->GetFirstModule(); + if (!module) { + WABT_FATAL("no module in file.\n"); + } + + if (s_generate_names) { + result = GenerateNames(module); + } + + if (Succeeded(result)) { + result = ApplyNames(module); + } + + if (Succeeded(result)) { + FileStream stream(s_outfile ? FileStream(s_outfile) : FileStream(stdout)); + result = WriteWat(&stream, module, s_write_wat_options); + } + } + + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} diff --git a/third_party/wasm2c/src/tools/wat2wasm.cc b/third_party/wasm2c/src/tools/wat2wasm.cc new file mode 100644 index 0000000000..f77dad2778 --- /dev/null +++ b/third_party/wasm2c/src/tools/wat2wasm.cc @@ -0,0 +1,170 @@ +/* + * 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 <cassert> +#include <cstdarg> +#include <cstdint> +#include <cstdlib> +#include <cstdio> +#include <string> + +#include "config.h" + +#include "src/binary-writer.h" +#include "src/common.h" +#include "src/error-formatter.h" +#include "src/feature.h" +#include "src/filenames.h" +#include "src/ir.h" +#include "src/option-parser.h" +#include "src/resolve-names.h" +#include "src/stream.h" +#include "src/validator.h" +#include "src/wast-parser.h" + +using namespace wabt; + +static const char* s_infile; +static std::string s_outfile; +static bool s_dump_module; +static int s_verbose; +static WriteBinaryOptions s_write_binary_options; +static bool s_validate = true; +static bool s_debug_parsing; +static Features s_features; + +static std::unique_ptr<FileStream> s_log_stream; + +static const char s_description[] = +R"( read a file in the wasm text format, check it for errors, and + convert it to the wasm binary format. + +examples: + # parse and typecheck test.wat + $ wat2wasm test.wat + + # parse test.wat and write to binary file test.wasm + $ wat2wasm test.wat -o test.wasm + + # parse spec-test.wast, and write verbose output to stdout (including + # the meaning of every byte) + $ wat2wasm spec-test.wast -v +)"; + +static void ParseOptions(int argc, char* argv[]) { + OptionParser parser("wat2wasm", s_description); + + parser.AddOption('v', "verbose", "Use multiple times for more info", []() { + s_verbose++; + s_log_stream = FileStream::CreateStderr(); + }); + parser.AddOption("debug-parser", "Turn on debugging the parser of wat files", + []() { s_debug_parsing = true; }); + parser.AddOption('d', "dump-module", + "Print a hexdump of the module to stdout", + []() { s_dump_module = true; }); + s_features.AddOptions(&parser); + parser.AddOption('o', "output", "FILE", "output wasm binary file", + [](const char* argument) { s_outfile = argument; }); + parser.AddOption( + 'r', "relocatable", + "Create a relocatable wasm binary (suitable for linking with e.g. lld)", + []() { s_write_binary_options.relocatable = true; }); + parser.AddOption( + "no-canonicalize-leb128s", + "Write all LEB128 sizes as 5-bytes instead of their minimal size", + []() { s_write_binary_options.canonicalize_lebs = false; }); + parser.AddOption("debug-names", + "Write debug names to the generated binary file", + []() { s_write_binary_options.write_debug_names = true; }); + parser.AddOption("no-check", "Don't check for invalid modules", + []() { s_validate = false; }); + parser.AddArgument("filename", OptionParser::ArgumentCount::One, + [](const char* argument) { s_infile = argument; }); + + parser.Parse(argc, argv); +} + +static void WriteBufferToFile(string_view filename, + const OutputBuffer& buffer) { + if (s_dump_module) { + std::unique_ptr<FileStream> stream = FileStream::CreateStdout(); + if (s_verbose) { + stream->Writef(";; dump\n"); + } + if (!buffer.data.empty()) { + stream->WriteMemoryDump(buffer.data.data(), buffer.data.size()); + } + } + + buffer.WriteToFile(filename); +} + +static std::string DefaultOuputName(string_view input_name) { + // Strip existing extension and add .wasm + std::string result(StripExtension(GetBasename(input_name))); + result += kWasmExtension; + + return result; +} + +int ProgramMain(int argc, char** argv) { + InitStdio(); + + ParseOptions(argc, argv); + + std::vector<uint8_t> file_data; + Result result = ReadFile(s_infile, &file_data); + std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer( + s_infile, file_data.data(), file_data.size()); + if (Failed(result)) { + WABT_FATAL("unable to read file: %s\n", s_infile); + } + + Errors errors; + std::unique_ptr<Module> module; + WastParseOptions parse_wast_options(s_features); + result = ParseWatModule(lexer.get(), &module, &errors, &parse_wast_options); + + if (Succeeded(result) && s_validate) { + ValidateOptions options(s_features); + result = ValidateModule(module.get(), &errors, options); + } + + if (Succeeded(result)) { + MemoryStream stream(s_log_stream.get()); + s_write_binary_options.features = s_features; + result = WriteBinaryModule(&stream, module.get(), s_write_binary_options); + + if (Succeeded(result)) { + if (s_outfile.empty()) { + s_outfile = DefaultOuputName(s_infile); + } + WriteBufferToFile(s_outfile.c_str(), stream.output_buffer()); + } + } + + auto line_finder = lexer->MakeLineFinder(); + FormatErrorsToFile(errors, Location::Type::Text, line_finder.get()); + + return result != Result::Ok; +} + +int main(int argc, char** argv) { + WABT_TRY + return ProgramMain(argc, argv); + WABT_CATCH_BAD_ALLOC_AND_EXIT +} |