From aa4d8d26b971f18921a1a6a368ef15fd5b8e2457 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 26 Apr 2021 12:48:13 +0430 Subject: [PATCH] LibWasm: Start implementing a basic WebAssembly binary format parser This can currently parse a really simple module. Note that it cannot parse the DataCount section, and it's still missing almost all of the instructions. This commit also adds a 'wasm' test utility that tries to parse a given webassembly binary file. It currently does nothing but exit when the parse fails, but it's a start :^) --- AK/Debug.h.in | 5 + Meta/CMake/all_the_debug_macros.cmake | 1 + Userland/Libraries/CMakeLists.txt | 1 + Userland/Libraries/LibWasm/CMakeLists.txt | 6 + Userland/Libraries/LibWasm/Constants.h | 37 + Userland/Libraries/LibWasm/Opcode.h | 202 ++++ Userland/Libraries/LibWasm/Parser/Parser.cpp | 772 +++++++++++++++ Userland/Libraries/LibWasm/Types.h | 932 +++++++++++++++++++ Userland/Utilities/CMakeLists.txt | 1 + Userland/Utilities/wasm.cpp | 34 + 10 files changed, 1991 insertions(+) create mode 100644 Userland/Libraries/LibWasm/CMakeLists.txt create mode 100644 Userland/Libraries/LibWasm/Constants.h create mode 100644 Userland/Libraries/LibWasm/Opcode.h create mode 100644 Userland/Libraries/LibWasm/Parser/Parser.cpp create mode 100644 Userland/Libraries/LibWasm/Types.h create mode 100644 Userland/Utilities/wasm.cpp diff --git a/AK/Debug.h.in b/AK/Debug.h.in index adb22891f9..89c0fb1b5d 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -394,6 +394,10 @@ #cmakedefine01 UPDATE_COALESCING_DEBUG #endif +#ifndef WASM_BINPARSER_DEBUG +#cmakedefine01 WASM_BINPARSER_DEBUG +#endif + #ifndef WINDOWMANAGER_DEBUG #cmakedefine01 WINDOWMANAGER_DEBUG #endif @@ -409,3 +413,4 @@ #ifndef WSSCREEN_DEBUG #cmakedefine01 WSSCREEN_DEBUG #endif + diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 8d61e0f65d..1f11e383d8 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -175,6 +175,7 @@ set(RSA_PARSE_DEBUG ON) set(LINE_EDITOR_DEBUG ON) set(LANGUAGE_SERVER_DEBUG ON) set(GL_DEBUG ON) +set(WASM_BINPARSER_DEBUG ON) # False positive: DEBUG is a flag but it works differently. # set(DEBUG ON) diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt index 00bab9deca..ae9734ef40 100644 --- a/Userland/Libraries/CMakeLists.txt +++ b/Userland/Libraries/CMakeLists.txt @@ -39,6 +39,7 @@ add_subdirectory(LibThread) add_subdirectory(LibTLS) add_subdirectory(LibTTF) add_subdirectory(LibVT) +add_subdirectory(LibWasm) add_subdirectory(LibWeb) add_subdirectory(LibWebSocket) add_subdirectory(LibX86) diff --git a/Userland/Libraries/LibWasm/CMakeLists.txt b/Userland/Libraries/LibWasm/CMakeLists.txt new file mode 100644 index 0000000000..c16dedeadc --- /dev/null +++ b/Userland/Libraries/LibWasm/CMakeLists.txt @@ -0,0 +1,6 @@ +set(SOURCES + Parser/Parser.cpp +) + +serenity_lib(LibWasm wasm) +target_link_libraries(LibWasm LibC LibCore) diff --git a/Userland/Libraries/LibWasm/Constants.h b/Userland/Libraries/LibWasm/Constants.h new file mode 100644 index 0000000000..9d5f346fc6 --- /dev/null +++ b/Userland/Libraries/LibWasm/Constants.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Wasm::Constants { + +// Value +static constexpr auto i32_tag = 0x7f; +static constexpr auto i64_tag = 0x7e; +static constexpr auto f32_tag = 0x7d; +static constexpr auto f64_tag = 0x7c; +static constexpr auto function_reference_tag = 0x70; +static constexpr auto extern_reference_tag = 0x6f; + +// Function +static constexpr auto function_signature_tag = 0x60; + +// Global +static constexpr auto const_tag = 0x00; +static constexpr auto var_tag = 0x01; + +// Block +static constexpr auto empty_block_tag = 0x40; + +// Import section +static constexpr auto extern_function_tag = 0x00; +static constexpr auto extern_table_tag = 0x01; +static constexpr auto extern_memory_tag = 0x02; +static constexpr auto extern_global_tag = 0x03; + +} diff --git a/Userland/Libraries/LibWasm/Opcode.h b/Userland/Libraries/LibWasm/Opcode.h new file mode 100644 index 0000000000..e77b75739c --- /dev/null +++ b/Userland/Libraries/LibWasm/Opcode.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Wasm { + +TYPEDEF_DISTINCT_ORDERED_ID(u8, OpCode); + +namespace Instructions { + +static constexpr OpCode unreachable = 0x00, + nop = 0x01, + block = 0x02, + loop = 0x03, + if_ = 0x04, + br = 0x0c, + br_if = 0x0d, + br_table = 0x0e, + return_ = 0x0f, + call = 0x10, + call_indirect = 0x11, + drop = 0x1a, + select = 0x1b, + select_typed = 0x1c, + local_get = 0x20, + local_set = 0x21, + local_tee = 0x22, + global_get = 0x23, + global_set = 0x24, + table_get = 0x25, + table_set = 0x26, + i32_load = 0x28, + i64_load = 0x29, + f32_load = 0x2a, + f64_load = 0x2b, + i32_load8_s = 0x2c, + i32_load8_u = 0x2d, + i32_load16_s = 0x2e, + i32_load16_u = 0x2f, + i64_load8_s = 0x30, + i64_load8_u = 0x31, + i64_load16_s = 0x32, + i64_load16_u = 0x33, + i64_load32_s = 0x34, + i64_load32_u = 0x35, + i32_store = 0x36, + i64_store = 0x37, + f32_store = 0x38, + f64_store = 0x39, + i32_store8 = 0x3a, + i32_store16 = 0x3b, + i64_store8 = 0x3c, + i64_store16 = 0x3d, + i64_store32 = 0x3e, + memory_size = 0x3f, + memory_grow = 0x40, + i32_const = 0x41, + i64_const = 0x42, + f32_const = 0x43, + f64_const = 0x44, + i32_eqz = 0x45, + i32_eq = 0x46, + i32_ne = 0x47, + i32_lts = 0x48, + i32_ltu = 0x49, + i32_gts = 0x4a, + i32_gtu = 0x4b, + i32_les = 0x4c, + i32_leu = 0x4d, + i32_ges = 0x4e, + i32_geu = 0x4f, + i64_eqz = 0x50, + i64_eq = 0x51, + i64_ne = 0x52, + i64_lts = 0x53, + i64_ltu = 0x54, + i64_gts = 0x55, + i64_gtu = 0x56, + i64_les = 0x57, + i64_leu = 0x58, + i64_ges = 0x59, + i64_geu = 0x5a, + f32_eq = 0x5b, + f32_ne = 0x5c, + f32_lt = 0x5d, + f32_gt = 0x5e, + f32_le = 0x5f, + f32_ge = 0x60, + f64_eq = 0x61, + f64_ne = 0x62, + f64_lt = 0x63, + f64_gt = 0x64, + f64_le = 0x65, + f64_ge = 0x66, + i32_clz = 0x67, + i32_ctz = 0x68, + i32_popcnt = 0x69, + i32_add = 0x6a, + i32_sub = 0x6b, + i32_mul = 0x6c, + i32_divs = 0x6d, + i32_divu = 0x6e, + i32_rems = 0x6f, + i32_remu = 0x70, + i32_and = 0x71, + i32_or = 0x72, + i32_xor = 0x73, + i32_shl = 0x74, + i32_shrs = 0x75, + i32_shru = 0x76, + i32_rotl = 0x77, + i32_rotr = 0x78, + i64_clz = 0x79, + i64_ctz = 0x7a, + i64_popcnt = 0x7b, + i64_add = 0x7c, + i64_sub = 0x7d, + i64_mul = 0x7e, + i64_divs = 0x7f, + i64_divu = 0x80, + i64_rems = 0x81, + i64_remu = 0x82, + i64_and = 0x83, + i64_or = 0x84, + i64_xor = 0x85, + i64_shl = 0x86, + i64_shrs = 0x87, + i64_shru = 0x88, + i64_rotl = 0x89, + i64_rotr = 0x8a, + f32_abs = 0x8b, + f32_neg = 0x8c, + f32_ceil = 0x8d, + f32_floor = 0x8e, + f32_trunc = 0x8f, + f32_nearest = 0x90, + f32_sqrt = 0x91, + f32_add = 0x92, + f32_sub = 0x93, + f32_mul = 0x94, + f32_div = 0x95, + f32_min = 0x96, + f32_max = 0x97, + f32_copysign = 0x98, + f64_abs = 0x99, + f64_neg = 0x9a, + f64_ceil = 0x9b, + f64_floor = 0x9c, + f64_trunc = 0x9d, + f64_nearest = 0x9e, + f64_sqrt = 0x9f, + f64_add = 0xa0, + f64_sub = 0xa1, + f64_mul = 0xa2, + f64_div = 0xa3, + f64_min = 0xa4, + f64_max = 0xa5, + f64_copysign = 0xa6, + i32_wrap_i64 = 0xa7, + i32_trunc_sf32 = 0xa8, + i32_trunc_uf32 = 0xa9, + i32_trunc_sf64 = 0xaa, + i32_trunc_uf64 = 0xab, + i64_extend_si32 = 0xac, + i64_extend_ui32 = 0xad, + i64_trunc_sf32 = 0xae, + i64_trunc_uf32 = 0xaf, + i64_trunc_sf64 = 0xb0, + i64_trunc_uf64 = 0xb1, + f32_convert_si32 = 0xb2, + f32_convert_ui32 = 0xb3, + f32_convert_si64 = 0xb4, + f32_convert_ui64 = 0xb5, + f32_demote_f64 = 0xb6, + f64_convert_si32 = 0xb7, + f64_convert_ui32 = 0xb8, + f64_convert_si64 = 0xb9, + f64_convert_ui64 = 0xba, + f64_promote_f32 = 0xbb, + i32_reinterpret_f32 = 0xbc, + i64_reinterpret_f64 = 0xbd, + f32_reinterpret_i32 = 0xbe, + f64_reinterpret_i64 = 0xbf, + table_init = 0xfc, + table_drop = 0xfc, + table_copy = 0xfc, + table_grow = 0xfc, + table_size = 0xfc, + table_fill = 0xfc, + memory_init = 0xfc, + data_drop = 0xfc, + memory_copy = 0xfc, + memory_fill = 0xfc; +} + +} diff --git a/Userland/Libraries/LibWasm/Parser/Parser.cpp b/Userland/Libraries/LibWasm/Parser/Parser.cpp new file mode 100644 index 0000000000..46d634b799 --- /dev/null +++ b/Userland/Libraries/LibWasm/Parser/Parser.cpp @@ -0,0 +1,772 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Wasm { + +template +static ParseResult> parse_vector(InputStream& stream) +{ + ScopeLogger logger; + size_t count; + if (!LEB128::read_unsigned(stream, count)) + return ParseError::InvalidInput; + + Vector entries; + for (size_t i = 0; i < count; ++i) { + if constexpr (IsSame) { + size_t value; + if (!LEB128::read_unsigned(stream, value)) + return ParseError::InvalidInput; + entries.append(value); + } else if constexpr (IsSame) { + ssize_t value; + if (!LEB128::read_signed(stream, value)) + return ParseError::InvalidInput; + entries.append(value); + } else if constexpr (IsSame) { + entries.resize(count); + if (!stream.read_or_error({ entries.data(), entries.size() })) + return ParseError::InvalidInput; + break; // Note: We read this all in one go! + } else { + auto result = T::parse(stream); + if (result.is_error()) + return result.error(); + entries.append(result.release_value()); + } + } + + return entries; +} + +static ParseResult parse_name(InputStream& stream) +{ + ScopeLogger logger; + auto data = parse_vector(stream); + if (data.is_error()) + return data.error(); + + return String::copy(data.value()); +} + +template +struct ParseUntilAnyOfResult { + u8 terminator { 0 }; + Vector values; +}; +template +static ParseResult> parse_until_any_of(InputStream& stream, Args... terminators) requires(requires(InputStream& stream) { T::parse(stream); }) +{ + ScopeLogger logger; + ReconsumableStream new_stream { stream }; + ParseUntilAnyOfResult result; + for (;;) { + u8 byte; + new_stream >> byte; + if (new_stream.has_any_error()) + return ParseError::InvalidInput; + + if ((... || (byte == terminators))) { + result.terminator = byte; + return result; + } + + new_stream.unread({ &byte, 1 }); + auto parse_result = T::parse(new_stream); + if (parse_result.is_error()) + return parse_result.error(); + + result.values.append(parse_result.release_value()); + } +} + +ParseResult ValueType::parse(InputStream& stream) +{ + ScopeLogger logger("ValueType"); + u8 tag; + stream >> tag; + if (stream.has_any_error()) + return ParseError::InvalidInput; + switch (tag) { + case Constants::i32_tag: + return ValueType(I32); + case Constants::i64_tag: + return ValueType(I64); + case Constants::f32_tag: + return ValueType(F32); + case Constants::f64_tag: + return ValueType(F64); + case Constants::function_reference_tag: + return ValueType(FunctionReference); + case Constants::extern_reference_tag: + return ValueType(ExternReference); + default: + return ParseError::InvalidInput; + } +} + +ParseResult ResultType::parse(InputStream& stream) +{ + ScopeLogger logger("ResultType"); + auto types = parse_vector(stream); + if (types.is_error()) + return types.error(); + return ResultType { types.release_value() }; +} + +ParseResult FunctionType::parse(InputStream& stream) +{ + ScopeLogger logger("FunctionType"); + u8 tag; + stream >> tag; + if (stream.has_any_error()) + return ParseError::InvalidInput; + + if (tag != Constants::function_signature_tag) { + dbgln("Expected 0x60, but found 0x{:x}", tag); + return ParseError::InvalidInput; + } + + auto parameters_result = parse_vector(stream); + if (parameters_result.is_error()) + return parameters_result.error(); + auto results_result = parse_vector(stream); + if (results_result.is_error()) + return results_result.error(); + + return FunctionType { parameters_result.release_value(), results_result.release_value() }; +} + +ParseResult Limits::parse(InputStream& stream) +{ + ScopeLogger logger("Limits"); + u8 flag; + stream >> flag; + if (stream.has_any_error()) + return ParseError::InvalidInput; + + if (flag > 1) + return ParseError::InvalidInput; + + size_t min; + if (!LEB128::read_unsigned(stream, min)) + return ParseError::InvalidInput; + + Optional max; + if (flag) { + size_t value; + if (LEB128::read_unsigned(stream, value)) + return ParseError::InvalidInput; + max = value; + } + + return Limits { static_cast(min), move(max) }; +} + +ParseResult MemoryType::parse(InputStream& stream) +{ + ScopeLogger logger("MemoryType"); + auto limits_result = Limits::parse(stream); + if (limits_result.is_error()) + return limits_result.error(); + return MemoryType { limits_result.release_value() }; +} + +ParseResult TableType::parse(InputStream& stream) +{ + ScopeLogger logger("TableType"); + auto type_result = ValueType::parse(stream); + if (type_result.is_error()) + return type_result.error(); + if (!type_result.value().is_reference()) + return ParseError::InvalidInput; + auto limits_result = Limits::parse(stream); + if (limits_result.is_error()) + return limits_result.error(); + return TableType { type_result.release_value(), limits_result.release_value() }; +} + +ParseResult GlobalType::parse(InputStream& stream) +{ + ScopeLogger logger("GlobalType"); + auto type_result = ValueType::parse(stream); + if (type_result.is_error()) + return type_result.error(); + u8 mutable_; + stream >> mutable_; + + if (stream.has_any_error()) + return ParseError::InvalidInput; + + if (mutable_ > 1) + return ParseError::InvalidInput; + + return GlobalType { type_result.release_value(), mutable_ == 0x01 }; +} + +ParseResult BlockType::parse(InputStream& stream) +{ + ScopeLogger logger("BlockType"); + u8 kind; + stream >> kind; + if (stream.has_any_error()) + return ParseError::InvalidInput; + if (kind == Constants::empty_block_tag) + return BlockType {}; + + { + InputMemoryStream value_stream { ReadonlyBytes { &kind, 1 } }; + if (auto value_type = ValueType::parse(value_stream); !value_type.is_error()) + return BlockType { value_type.release_value() }; + } + + ReconsumableStream new_stream { stream }; + new_stream.unread({ &kind, 1 }); + + ssize_t index_value; + if (!LEB128::read_signed(new_stream, index_value)) + return ParseError::InvalidInput; + + if (index_value < 0) + return ParseError::InvalidInput; + + return BlockType { TypeIndex(index_value) }; +} + +ParseResult Instruction::parse(InputStream& stream) +{ + ScopeLogger logger("Instruction"); + u8 byte; + stream >> byte; + if (stream.has_any_error()) + return ParseError::InvalidInput; + OpCode opcode { byte }; + + if (opcode == Instructions::block || opcode == Instructions::loop || opcode == Instructions::if_) { + auto block_type = BlockType::parse(stream); + if (block_type.is_error()) + return block_type.error(); + auto result = parse_until_any_of(stream, 0x0b, 0x05); + if (result.is_error()) + return result.error(); + + if (result.value().terminator == 0x0b) { + // block/loop/if without else + NonnullOwnPtrVector instructions; + for (auto& entry : result.value().values) + instructions.append(make(move(entry))); + + return Instruction { opcode, BlockAndInstructionSet { block_type.release_value(), move(instructions) } }; + } + + VERIFY(result.value().terminator == 0x05); + NonnullOwnPtrVector left_instructions, right_instructions; + for (auto& entry : result.value().values) + left_instructions.append(make(move(entry))); + // if with else + { + auto result = parse_until_any_of(stream, 0x0b); + if (result.is_error()) + return result.error(); + + for (auto& entry : result.value().values) + right_instructions.append(make(move(entry))); + + return Instruction { opcode, BlockAndTwoInstructionSets { block_type.release_value(), move(left_instructions), move(right_instructions) } }; + } + } + + // FIXME: Parse all the other instructions + TODO(); +} + +ParseResult CustomSection::parse(InputStream& stream) +{ + ScopeLogger logger("CustomSection"); + auto name = parse_name(stream); + if (name.is_error()) + return name.error(); + + auto data_buffer = ByteBuffer::create_uninitialized(64); + while (!stream.has_any_error() && !stream.unreliable_eof()) { + char buf[16]; + auto size = stream.read({ buf, 16 }); + if (size == 0) + break; + data_buffer.append(buf, size); + } + + return CustomSection(name.release_value(), move(data_buffer)); +} + +ParseResult TypeSection::parse(InputStream& stream) +{ + ScopeLogger logger("TypeSection"); + auto types = parse_vector(stream); + if (types.is_error()) + return types.error(); + return TypeSection { types.release_value() }; +} + +ParseResult ImportSection::Import::parse(InputStream& stream) +{ + ScopeLogger logger("Import"); + auto module = parse_name(stream); + if (module.is_error()) + return module.error(); + auto name = parse_name(stream); + if (name.is_error()) + return name.error(); + u8 tag; + stream >> tag; + if (stream.has_any_error()) + return ParseError::InvalidInput; + + switch (tag) { + case Constants::extern_function_tag: { + size_t index; + if (!LEB128::read_unsigned(stream, index)) + return ParseError::InvalidInput; + return Import { module.release_value(), name.release_value(), TypeIndex { index } }; + } + case Constants::extern_table_tag: + return parse_with_type(stream, module, name); + case Constants::extern_memory_tag: + return parse_with_type(stream, module, name); + case Constants::extern_global_tag: + return parse_with_type(stream, module, name); + default: + return ParseError::InvalidInput; + } +} + +ParseResult ImportSection::parse(InputStream& stream) +{ + ScopeLogger logger("ImportSection"); + auto imports = parse_vector(stream); + if (imports.is_error()) + return imports.error(); + return ImportSection { imports.release_value() }; +} + +ParseResult FunctionSection::parse(InputStream& stream) +{ + ScopeLogger logger("FunctionSection"); + auto indices = parse_vector(stream); + if (indices.is_error()) + return indices.error(); + + Vector typed_indices; + typed_indices.resize(indices.value().size()); + for (auto entry : indices.value()) + typed_indices.append(entry); + + return FunctionSection { move(typed_indices) }; +} + +ParseResult TableSection::Table::parse(InputStream& stream) +{ + ScopeLogger logger("Table"); + auto type = TableType::parse(stream); + if (type.is_error()) + return type.error(); + return Table { type.release_value() }; +} + +ParseResult TableSection::parse(InputStream& stream) +{ + ScopeLogger logger("TableSection"); + auto tables = parse_vector(stream); + if (tables.is_error()) + return tables.error(); + return TableSection { tables.release_value() }; +} + +ParseResult MemorySection::Memory::parse(InputStream& stream) +{ + ScopeLogger logger("Memory"); + auto type = MemoryType::parse(stream); + if (type.is_error()) + return type.error(); + return Memory { type.release_value() }; +} + +ParseResult MemorySection::parse(InputStream& stream) +{ + ScopeLogger logger("MemorySection"); + auto memorys = parse_vector(stream); + if (memorys.is_error()) + return memorys.error(); + return MemorySection { memorys.release_value() }; +} + +ParseResult Expression::parse(InputStream& stream) +{ + ScopeLogger logger("Expression"); + auto instructions = parse_until_any_of(stream, 0x0b); + if (instructions.is_error()) + return instructions.error(); + + return Expression { move(instructions.value().values) }; +} + +ParseResult GlobalSection::Global::parse(InputStream& stream) +{ + ScopeLogger logger("Global"); + auto type = GlobalType::parse(stream); + if (type.is_error()) + return type.error(); + auto exprs = Expression::parse(stream); + if (exprs.is_error()) + return exprs.error(); + return Global { type.release_value(), exprs.release_value() }; +} + +ParseResult GlobalSection::parse(InputStream& stream) +{ + ScopeLogger logger("GlobalSection"); + auto result = parse_vector(stream); + if (result.is_error()) + return result.error(); + return GlobalSection { result.release_value() }; +} + +ParseResult ExportSection::Export::parse(InputStream& stream) +{ + ScopeLogger logger("Export"); + auto name = parse_name(stream); + if (name.is_error()) + return name.error(); + u8 tag; + stream >> tag; + if (stream.has_any_error()) + return ParseError::InvalidInput; + + size_t index; + if (!LEB128::read_unsigned(stream, index)) + return ParseError::InvalidInput; + + switch (tag) { + case Constants::extern_function_tag: + return Export { name.release_value(), ExportDesc { FunctionIndex { index } } }; + case Constants::extern_table_tag: + return Export { name.release_value(), ExportDesc { TableIndex { index } } }; + case Constants::extern_memory_tag: + return Export { name.release_value(), ExportDesc { MemoryIndex { index } } }; + case Constants::extern_global_tag: + return Export { name.release_value(), ExportDesc { GlobalIndex { index } } }; + default: + return ParseError::InvalidInput; + } +} + +ParseResult ExportSection::parse(InputStream& stream) +{ + ScopeLogger logger("ExportSection"); + auto result = parse_vector(stream); + if (result.is_error()) + return result.error(); + return ExportSection { result.release_value() }; +} + +ParseResult StartSection::StartFunction::parse(InputStream& stream) +{ + ScopeLogger logger("StartFunction"); + size_t index; + if (!LEB128::read_unsigned(stream, index)) + return ParseError::InvalidInput; + return StartFunction { FunctionIndex { index } }; +} + +ParseResult StartSection::parse(InputStream& stream) +{ + ScopeLogger logger("StartSection"); + auto result = StartFunction::parse(stream); + if (result.is_error()) + return result.error(); + return StartSection { result.release_value() }; +} + +ParseResult ElementSection::Element::parse(InputStream& stream) +{ + ScopeLogger logger("Element"); + size_t table_index; + if (!LEB128::read_unsigned(stream, table_index)) + return ParseError::InvalidInput; + auto offset = Expression::parse(stream); + if (offset.is_error()) + return offset.error(); + auto init = parse_vector(stream); + if (init.is_error()) + return init.error(); + + Vector typed_init; + typed_init.ensure_capacity(init.value().size()); + for (auto entry : init.value()) + typed_init.unchecked_append(entry); + + return Element { TableIndex { table_index }, offset.release_value(), move(typed_init) }; +} + +ParseResult ElementSection::parse(InputStream& stream) +{ + ScopeLogger logger("ElementSection"); + auto result = Element::parse(stream); + if (result.is_error()) + return result.error(); + return ElementSection { result.release_value() }; +} + +ParseResult Locals::parse(InputStream& stream) +{ + ScopeLogger logger("Locals"); + size_t count; + if (!LEB128::read_unsigned(stream, count)) + return ParseError::InvalidInput; + // TODO: Disallow too many entries. + auto type = ValueType::parse(stream); + if (type.is_error()) + return type.error(); + + return Locals { static_cast(count), type.release_value() }; +} + +ParseResult Func::parse(InputStream& stream) +{ + ScopeLogger logger("Func"); + auto locals = parse_vector(stream); + if (locals.is_error()) + return locals.error(); + auto body = Expression::parse(stream); + if (body.is_error()) + return body.error(); + return Func { locals.release_value(), body.release_value() }; +} + +ParseResult CodeSection::Code::parse(InputStream& stream) +{ + ScopeLogger logger("Code"); + size_t size; + if (!LEB128::read_unsigned(stream, size)) + return ParseError::InvalidInput; + + auto constrained_stream = ConstrainedStream { stream, size }; + auto func = Func::parse(constrained_stream); + if (func.is_error()) + return func.error(); + + return Code { static_cast(size), func.release_value() }; +} + +ParseResult CodeSection::parse(InputStream& stream) +{ + ScopeLogger logger("CodeSection"); + auto result = parse_vector(stream); + if (result.is_error()) + return result.error(); + return CodeSection { result.release_value() }; +} + +ParseResult DataSection::Data::parse(InputStream& stream) +{ + ScopeLogger logger("Data"); + u8 tag; + stream >> tag; + if (stream.has_any_error()) + return ParseError::InvalidInput; + + if (tag > 0x02) + return ParseError::InvalidInput; + + if (tag == 0x00) { + auto expr = Expression::parse(stream); + if (expr.is_error()) + return expr.error(); + auto init = parse_vector(stream); + if (init.is_error()) + return init.error(); + return Data { Active { init.release_value(), { 0 }, expr.release_value() } }; + } + if (tag == 0x01) { + auto init = parse_vector(stream); + if (init.is_error()) + return init.error(); + return Data { Passive { init.release_value() } }; + } + if (tag == 0x02) { + size_t index; + stream >> index; + if (stream.has_any_error()) + return ParseError::InvalidInput; + auto expr = Expression::parse(stream); + if (expr.is_error()) + return expr.error(); + auto init = parse_vector(stream); + if (init.is_error()) + return init.error(); + return Data { Active { init.release_value(), { index }, expr.release_value() } }; + } + VERIFY_NOT_REACHED(); +} + +ParseResult DataSection::parse(InputStream& stream) +{ + ScopeLogger logger("DataSection"); + auto data = parse_vector(stream); + if (data.is_error()) + return data.error(); + + return DataSection { data.release_value() }; +} + +ParseResult DataCountSection::parse([[maybe_unused]] InputStream& stream) +{ + ScopeLogger logger("DataCountSection"); + // FIXME: Implement parsing optional values! + return ParseError::InvalidInput; +} + +ParseResult Module::parse(InputStream& stream) +{ + ScopeLogger logger("Module"); + u8 buf[4]; + if (!stream.read_or_error({ buf, 4 })) + return ParseError::InvalidInput; + if (Bytes { buf, 4 } != wasm_magic.span()) + return ParseError::InvalidInput; + + if (!stream.read_or_error({ buf, 4 })) + return ParseError::InvalidInput; + if (Bytes { buf, 4 } != wasm_version.span()) + return ParseError::InvalidInput; + + Vector sections; + for (;;) { + u8 section_id; + stream >> section_id; + if (stream.unreliable_eof()) { + stream.handle_any_error(); + break; + } + if (stream.has_any_error()) + return ParseError::InvalidInput; + + size_t section_size; + if (!LEB128::read_unsigned(stream, section_size)) + return ParseError::InvalidInput; + + auto section_stream = ConstrainedStream { stream, section_size }; + + switch (section_id) { + case CustomSection::section_id: { + if (auto section = CustomSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case TypeSection::section_id: { + if (auto section = TypeSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case ImportSection::section_id: { + if (auto section = ImportSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case FunctionSection::section_id: { + if (auto section = FunctionSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case TableSection::section_id: { + if (auto section = TableSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case MemorySection::section_id: { + if (auto section = MemorySection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case GlobalSection::section_id: { + if (auto section = GlobalSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case ExportSection::section_id: { + if (auto section = ExportSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case StartSection::section_id: { + if (auto section = StartSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case ElementSection::section_id: { + if (auto section = ElementSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case CodeSection::section_id: { + if (auto section = CodeSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + case DataSection::section_id: { + if (auto section = DataSection::parse(section_stream); !section.is_error()) { + sections.append(section.release_value()); + continue; + } else { + return section.error(); + } + } + default: + return ParseError::InvalidInput; + } + } + + return Module { move(sections) }; +} + +} diff --git a/Userland/Libraries/LibWasm/Types.h b/Userland/Libraries/LibWasm/Types.h new file mode 100644 index 0000000000..0f67277b85 --- /dev/null +++ b/Userland/Libraries/LibWasm/Types.h @@ -0,0 +1,932 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Wasm { + +enum class ParseError { + // FIXME: More descriptive errors! + InvalidInput, +}; + +template +using ParseResult = Result; + +TYPEDEF_DISTINCT_ORDERED_ID(size_t, TypeIndex); +TYPEDEF_DISTINCT_ORDERED_ID(size_t, FunctionIndex); +TYPEDEF_DISTINCT_ORDERED_ID(size_t, TableIndex); +TYPEDEF_DISTINCT_ORDERED_ID(size_t, MemoryIndex); +TYPEDEF_DISTINCT_ORDERED_ID(size_t, LocalIndex); +TYPEDEF_DISTINCT_ORDERED_ID(size_t, GlobalIndex); +TYPEDEF_DISTINCT_ORDERED_ID(size_t, LabelIndex); +TYPEDEF_DISTINCT_ORDERED_ID(size_t, DataIndex); + +class ReconsumableStream : public InputStream { +public: + explicit ReconsumableStream(InputStream& stream) + : m_stream(stream) + { + } + + void unread(ReadonlyBytes data) { m_buffer.append(data.data(), data.size()); } + +private: + size_t read(Bytes bytes) override + { + size_t bytes_read_from_buffer = 0; + if (!m_buffer.is_empty()) { + auto read_size = min(bytes.size(), m_buffer.size()); + m_buffer.span().slice(0, read_size).copy_to(bytes); + bytes = bytes.slice(read_size); + for (size_t i = 0; i < read_size; ++i) + m_buffer.take_first(); + bytes_read_from_buffer = read_size; + } + + return m_stream.read(bytes) + bytes_read_from_buffer; + } + bool unreliable_eof() const override + { + return m_buffer.is_empty() && m_stream.unreliable_eof(); + } + bool read_or_error(Bytes bytes) override + { + if (read(bytes)) + return true; + set_recoverable_error(); + return false; + } + bool discard_or_error(size_t count) override + { + size_t bytes_discarded_from_buffer = 0; + if (!m_buffer.is_empty()) { + auto read_size = min(count, m_buffer.size()); + for (size_t i = 0; i < read_size; ++i) + m_buffer.take_first(); + bytes_discarded_from_buffer = read_size; + } + + return m_stream.discard_or_error(count - bytes_discarded_from_buffer); + } + + InputStream& m_stream; + Vector m_buffer; +}; + +class ConstrainedStream : public InputStream { +public: + explicit ConstrainedStream(InputStream& stream, size_t size) + : m_stream(stream) + , m_bytes_left(size) + { + } + +private: + size_t read(Bytes bytes) override + { + auto to_read = min(m_bytes_left, bytes.size()); + auto nread = m_stream.read(bytes.slice(0, to_read)); + m_bytes_left -= nread; + return nread; + } + bool unreliable_eof() const override + { + return m_bytes_left == 0 || m_stream.unreliable_eof(); + } + bool read_or_error(Bytes bytes) override + { + if (read(bytes)) + return true; + set_recoverable_error(); + return false; + } + bool discard_or_error(size_t count) override + { + auto to_discard = min(m_bytes_left, count); + if (m_stream.discard_or_error(to_discard)) + m_bytes_left -= to_discard; + return to_discard; + } + + InputStream& m_stream; + size_t m_bytes_left { 0 }; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#value-types%E2%91%A2 +class ValueType { +public: + enum Kind { + I32, + I64, + F32, + F64, + FunctionReference, + ExternReference, + }; + + explicit ValueType(Kind kind) + : m_kind(kind) + { + } + + auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference; } + auto is_numeric() const { return !is_reference(); } + auto kind() const { return m_kind; } + + static ParseResult parse(InputStream& stream); + + static String kind_name(Kind kind) + { + switch (kind) { + case I32: + return "i32"; + case I64: + return "i64"; + case F32: + return "f32"; + case F64: + return "f64"; + case FunctionReference: + return "funcref"; + case ExternReference: + return "externref"; + } + VERIFY_NOT_REACHED(); + } + +private: + Kind m_kind; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#result-types%E2%91%A2 +class ResultType { +public: + explicit ResultType(Vector types) + : m_types(move(types)) + { + } + + const auto& types() const { return m_types; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_types; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#function-types%E2%91%A4 +class FunctionType { +public: + FunctionType(Vector parameters, Vector results) + : m_parameters(move(parameters)) + , m_results(move(results)) + { + } + + auto& parameters() const { return m_parameters; } + auto& results() const { return m_results; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_parameters; + Vector m_results; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#limits%E2%91%A5 +class Limits { +public: + explicit Limits(u32 min, Optional max = {}) + : m_min(min) + , m_max(move(max)) + { + } + + auto min() const { return m_min; } + auto& max() const { return m_max; } + + static ParseResult parse(InputStream& stream); + +private: + u32 m_min { 0 }; + Optional m_max; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#memory-types%E2%91%A4 +class MemoryType { +public: + explicit MemoryType(Limits limits) + : m_limits(move(limits)) + { + } + + auto& limits() const { return m_limits; } + + static ParseResult parse(InputStream& stream); + +private: + Limits m_limits; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#table-types%E2%91%A4 +class TableType { +public: + explicit TableType(ValueType element_type, Limits limits) + : m_element_type(element_type) + , m_limits(move(limits)) + { + VERIFY(m_element_type.is_reference()); + } + + auto& limits() const { return m_limits; } + auto& element_type() const { return m_element_type; } + + static ParseResult parse(InputStream& stream); + +private: + ValueType m_element_type; + Limits m_limits; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#global-types%E2%91%A4 +class GlobalType { +public: + GlobalType(ValueType type, bool is_mutable) + : m_type(type) + , m_is_mutable(is_mutable) + { + } + + auto& type() const { return m_type; } + auto is_mutable() const { return m_is_mutable; } + + static ParseResult parse(InputStream& stream); + +private: + ValueType m_type; + bool m_is_mutable { false }; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#binary-blocktype +class BlockType { +public: + enum Kind { + Empty, + Type, + Index, + }; + + BlockType() + : m_kind(Empty) + , m_empty(0) + { + } + + explicit BlockType(ValueType type) + : m_kind(Type) + , m_value_type(type) + { + } + + explicit BlockType(TypeIndex index) + : m_kind(Index) + , m_type_index(index) + { + } + + auto kind() const { return m_kind; } + auto& value_type() const + { + VERIFY(kind() == Type); + return m_value_type; + } + auto& type_index() const + { + VERIFY(kind() == Index); + return m_type_index; + } + + static ParseResult parse(InputStream& stream); + +private: + Kind m_kind { Empty }; + union { + ValueType m_value_type; + TypeIndex m_type_index; + u8 m_empty; + }; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#binary-instr +// https://webassembly.github.io/spec/core/bikeshed/#reference-instructions%E2%91%A6 +// https://webassembly.github.io/spec/core/bikeshed/#parametric-instructions%E2%91%A6 +// https://webassembly.github.io/spec/core/bikeshed/#variable-instructions%E2%91%A6 +// https://webassembly.github.io/spec/core/bikeshed/#table-instructions%E2%91%A6 +// https://webassembly.github.io/spec/core/bikeshed/#memory-instructions%E2%91%A6 +// https://webassembly.github.io/spec/core/bikeshed/#numeric-instructions%E2%91%A6 +class Instruction { +public: + explicit Instruction(OpCode opcode) + : m_opcode(opcode) + , m_arguments(static_cast(0)) + { + } + + struct TableElementArgs { + TableIndex index; + ValueType element_type; + }; + + struct TableTableArgs { + TableIndex lhs; + TableIndex rhs; + }; + + struct BlockAndInstructionSet { + BlockType block_type; + NonnullOwnPtrVector instructions; + }; + + struct BlockAndTwoInstructionSets { + BlockType block_type; + NonnullOwnPtrVector left_instructions; + NonnullOwnPtrVector right_instructions; + }; + + struct TableBranchArgs { + Vector labels; + LabelIndex default_; + }; + + struct IndirectCallArgs { + TypeIndex type; + TableIndex table; + }; + + struct MemoryArgument { + u32 align; + u32 offset; + }; + + template + explicit Instruction(OpCode opcode, T argument) + : m_opcode(opcode) + , m_arguments(move(argument)) + { + } + + static ParseResult parse(InputStream& stream); + +private: + OpCode m_opcode { 0 }; + // clang-format off + Variant< + BlockAndInstructionSet, + BlockAndTwoInstructionSets, + DataIndex, + FunctionIndex, + IndirectCallArgs, + LabelIndex, + MemoryArgument, + TableBranchArgs, + TableElementArgs, + TableIndex, + TableTableArgs, + ValueType, + Vector, + double, + float, + i32, + i64, + u8 // Empty state + > m_arguments; + // clang-format on +}; + +class CustomSection { +public: + static constexpr u8 section_id = 0; + + CustomSection(String name, ByteBuffer contents) + : m_name(move(name)) + , m_contents(move(contents)) + { + } + + auto& name() const { return m_name; } + auto& contents() const { return m_contents; } + + static ParseResult parse(InputStream& stream); + +private: + String m_name; + ByteBuffer m_contents; +}; + +class TypeSection { +public: + static constexpr u8 section_id = 1; + + explicit TypeSection(Vector types) + : m_types(move(types)) + { + } + + auto& types() const { return m_types; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_types; +}; + +class ImportSection { +private: + class Import { + public: + using ImportDesc = Variant; + Import(String module, String name, ImportDesc description) + : m_module(move(module)) + , m_name(move(name)) + , m_description(move(description)) + { + } + + auto& module() const { return m_module; } + auto& name() const { return m_name; } + auto& description() const { return m_description; } + + static ParseResult parse(InputStream& stream); + + private: + template + static ParseResult parse_with_type(auto&& stream, auto&& module, auto&& name) + { + auto result = T::parse(stream); + if (result.is_error()) + return result.error(); + return Import { module.release_value(), name.release_value(), result.release_value() }; + }; + + String m_module; + String m_name; + ImportDesc m_description; + }; + +public: + static constexpr u8 section_id = 2; + + explicit ImportSection(Vector imports) + : m_imports(move(imports)) + { + } + + auto& imports() const { return m_imports; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_imports; +}; + +class FunctionSection { +public: + static constexpr u8 section_id = 3; + + explicit FunctionSection(Vector types) + : m_types(move(types)) + { + } + + auto& types() const { return m_types; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_types; +}; + +class TableSection { +private: + class Table { + public: + explicit Table(TableType type) + : m_type(move(type)) + { + } + + auto& type() const { return m_type; } + + static ParseResult
parse(InputStream& stream); + + private: + TableType m_type; + }; + +public: + static constexpr u8 section_id = 4; + + explicit TableSection(Vector
tables) + : m_tables(move(tables)) + { + } + + auto& tables() const { return m_tables; }; + + static ParseResult parse(InputStream& stream); + +private: + Vector
m_tables; +}; + +class MemorySection { +private: + class Memory { + public: + explicit Memory(MemoryType type) + : m_type(move(type)) + { + } + + auto& type() const { return m_type; } + + static ParseResult parse(InputStream& stream); + + private: + MemoryType m_type; + }; + +public: + static constexpr u8 section_id = 5; + + explicit MemorySection(Vector memorys) + : m_memories(move(memorys)) + { + } + + auto& memories() const { return m_memories; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_memories; +}; + +class Expression { +public: + explicit Expression(Vector instructions) + : m_instructions(move(instructions)) + { + } + + auto& instructions() const { return m_instructions; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_instructions; +}; + +class GlobalSection { +private: + class Global { + public: + explicit Global(GlobalType type, Expression expression) + : m_type(move(type)) + , m_expression(move(expression)) + { + } + + auto& type() const { return m_type; } + auto& expression() const { return m_expression; } + + static ParseResult parse(InputStream& stream); + + private: + GlobalType m_type; + Expression m_expression; + }; + +public: + static constexpr u8 section_id = 6; + + explicit GlobalSection(Vector entries) + : m_entries(move(entries)) + { + } + + auto& entries() const { return m_entries; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_entries; +}; + +class ExportSection { +private: + using ExportDesc = Variant; + class Export { + public: + explicit Export(String name, ExportDesc description) + : m_name(move(name)) + , m_description(move(description)) + { + } + + auto& name() const { return m_name; } + auto& description() const { return m_description; } + + static ParseResult parse(InputStream& stream); + + private: + String m_name; + ExportDesc m_description; + }; + +public: + static constexpr u8 section_id = 7; + + explicit ExportSection(Vector entries) + : m_entries(move(entries)) + { + } + + auto& entries() const { return m_entries; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_entries; +}; + +class StartSection { +private: + class StartFunction { + public: + explicit StartFunction(FunctionIndex index) + : m_index(index) + { + } + + auto& index() const { return m_index; } + + static ParseResult parse(InputStream& stream); + + private: + FunctionIndex m_index; + }; + +public: + static constexpr u8 section_id = 8; + + explicit StartSection(StartFunction func) + : m_function(move(func)) + { + } + + auto& function() const { return m_function; } + + static ParseResult parse(InputStream& stream); + +private: + StartFunction m_function; +}; + +class ElementSection { +private: + class Element { + public: + explicit Element(TableIndex table, Expression expr, Vector init) + : m_table(table) + , m_offset(move(expr)) + , m_init(move(init)) + { + } + + auto& table() const { return m_table; } + auto& offset() const { return m_offset; } + auto& init() const { return m_init; } + + static ParseResult parse(InputStream& stream); + + private: + TableIndex m_table; + Expression m_offset; + Vector m_init; + }; + +public: + static constexpr u8 section_id = 9; + + explicit ElementSection(Element func) + : m_function(move(func)) + { + } + + auto& function() const { return m_function; } + + static ParseResult parse(InputStream& stream); + +private: + Element m_function; +}; + +class Locals { +public: + explicit Locals(u32 n, ValueType type) + : m_n(n) + , m_type(type) + { + } + + // Yikes... + auto n() const { return m_n; } + auto& type() const { return m_type; } + + static ParseResult parse(InputStream& stream); + +private: + u32 m_n { 0 }; + ValueType m_type; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#binary-func +class Func { +public: + explicit Func(Vector locals, Expression body) + : m_locals(move(locals)) + , m_body(move(body)) + { + } + + auto& locals() const { return m_locals; } + auto& body() const { return m_body; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_locals; + Expression m_body; +}; + +class CodeSection { +private: + class Code { + public: + explicit Code(u32 size, Func func) + : m_size(size) + , m_func(move(func)) + { + } + + auto size() const { return m_size; } + auto& func() const { return m_func; } + + static ParseResult parse(InputStream& stream); + + private: + u32 m_size { 0 }; + Func m_func; + }; + +public: + static constexpr u8 section_id = 10; + + explicit CodeSection(Vector funcs) + : m_functions(move(funcs)) + { + } + + auto& functions() const { return m_functions; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_functions; +}; + +class DataSection { +private: + class Data { + struct Passive { + Vector init; + }; + struct Active { + Vector init; + MemoryIndex index; + Expression offset; + }; + using Value = Variant; + + public: + explicit Data(Value value) + : m_value(move(value)) + { + } + + auto& value() const { return m_value; } + + static ParseResult parse(InputStream& stream); + + private: + Value m_value; + }; + +public: + static constexpr u8 section_id = 11; + + explicit DataSection(Vector data) + : m_data(move(data)) + { + } + + auto& data() const { return m_data; } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_data; +}; + +class DataCountSection { +public: + static constexpr u8 section_id = 12; + + explicit DataCountSection(Optional count) + : m_count(move(count)) + { + } + + auto& count() const { return m_count; } + + static ParseResult parse(InputStream& stream); + +private: + Optional m_count; +}; + +class Module { +private: + class Function { + public: + explicit Function(TypeIndex type, Vector local_types, Expression body) + : m_type(type) + , m_local_types(move(local_types)) + , m_body(move(body)) + { + } + + auto& type() const { return m_type; } + auto& locals() const { return m_local_types; } + auto& body() const { return m_body; } + + private: + TypeIndex m_type; + Vector m_local_types; + Expression m_body; + }; + + using AnySection = Variant< + CustomSection, + TypeSection, + ImportSection, + FunctionSection, + TableSection, + MemorySection, + GlobalSection, + ExportSection, + StartSection, + ElementSection, + CodeSection, + DataSection>; + + static constexpr Array wasm_magic { 0, 'a', 's', 'm' }; + static constexpr Array wasm_version { 1, 0, 0, 0 }; + +public: + explicit Module(Vector sections) + : m_sections(move(sections)) + { + } + + static ParseResult parse(InputStream& stream); + +private: + Vector m_sections; +}; +} diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index 9e5a50c48a..1387ba3234 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -56,3 +56,4 @@ target_link_libraries(unzip LibArchive LibCompress) target_link_libraries(zip LibArchive LibCompress LibCrypto) target_link_libraries(CppParserTest LibCpp LibGUI) target_link_libraries(PreprocessorTest LibCpp LibGUI) +target_link_libraries(wasm LibWasm) diff --git a/Userland/Utilities/wasm.cpp b/Userland/Utilities/wasm.cpp new file mode 100644 index 0000000000..7fbbbfc475 --- /dev/null +++ b/Userland/Utilities/wasm.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + const char* filename = nullptr; + + Core::ArgsParser parser; + parser.add_positional_argument(filename, "File name to parse", "file"); + parser.parse(argc, argv); + + auto result = Core::File::open(filename, Core::IODevice::OpenMode::ReadOnly); + if (result.is_error()) { + warnln("Failed to open {}: {}", filename, result.error()); + return 1; + } + + auto stream = Core::InputFileStream(result.release_value()); + auto parse_result = Wasm::Module::parse(stream); + if (parse_result.is_error()) { + warnln("Something went wrong, either the file is invalid, or there's a bug with LibWasm!"); + return 2; + } + + return 0; +}