diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2021-04-26 12:48:13 +0430 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-05-08 22:14:39 +0200 |
commit | aa4d8d26b971f18921a1a6a368ef15fd5b8e2457 (patch) | |
tree | 9f176cf97261eb6b6b14ccedab894331077d0a7c /Userland/Libraries | |
parent | 56a6d7924eeeb38df065494c9f3a9b476225abf7 (diff) | |
download | serenity-aa4d8d26b971f18921a1a6a368ef15fd5b8e2457.zip |
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 :^)
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/CMakeLists.txt | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/Constants.h | 37 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/Opcode.h | 202 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/Parser/Parser.cpp | 772 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/Types.h | 932 |
6 files changed, 1950 insertions, 0 deletions
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 <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Types.h> + +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 <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/DistinctNumeric.h> + +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 <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/LEB128.h> +#include <AK/ScopeLogger.h> +#include <LibWasm/Types.h> + +namespace Wasm { + +template<typename T> +static ParseResult<Vector<T>> parse_vector(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger; + size_t count; + if (!LEB128::read_unsigned(stream, count)) + return ParseError::InvalidInput; + + Vector<T> entries; + for (size_t i = 0; i < count; ++i) { + if constexpr (IsSame<T, size_t>) { + size_t value; + if (!LEB128::read_unsigned(stream, value)) + return ParseError::InvalidInput; + entries.append(value); + } else if constexpr (IsSame<T, ssize_t>) { + ssize_t value; + if (!LEB128::read_signed(stream, value)) + return ParseError::InvalidInput; + entries.append(value); + } else if constexpr (IsSame<T, u8>) { + 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<String> parse_name(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger; + auto data = parse_vector<u8>(stream); + if (data.is_error()) + return data.error(); + + return String::copy(data.value()); +} + +template<typename T> +struct ParseUntilAnyOfResult { + u8 terminator { 0 }; + Vector<T> values; +}; +template<typename T, typename... Args> +static ParseResult<ParseUntilAnyOfResult<T>> parse_until_any_of(InputStream& stream, Args... terminators) requires(requires(InputStream& stream) { T::parse(stream); }) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger; + ReconsumableStream new_stream { stream }; + ParseUntilAnyOfResult<T> 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> ValueType::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> ResultType::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("ResultType"); + auto types = parse_vector<ValueType>(stream); + if (types.is_error()) + return types.error(); + return ResultType { types.release_value() }; +} + +ParseResult<FunctionType> FunctionType::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<ValueType>(stream); + if (parameters_result.is_error()) + return parameters_result.error(); + auto results_result = parse_vector<ValueType>(stream); + if (results_result.is_error()) + return results_result.error(); + + return FunctionType { parameters_result.release_value(), results_result.release_value() }; +} + +ParseResult<Limits> Limits::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<u32> max; + if (flag) { + size_t value; + if (LEB128::read_unsigned(stream, value)) + return ParseError::InvalidInput; + max = value; + } + + return Limits { static_cast<u32>(min), move(max) }; +} + +ParseResult<MemoryType> MemoryType::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> TableType::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> GlobalType::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> BlockType::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> Instruction::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<Instruction>(stream, 0x0b, 0x05); + if (result.is_error()) + return result.error(); + + if (result.value().terminator == 0x0b) { + // block/loop/if without else + NonnullOwnPtrVector<Instruction> instructions; + for (auto& entry : result.value().values) + instructions.append(make<Instruction>(move(entry))); + + return Instruction { opcode, BlockAndInstructionSet { block_type.release_value(), move(instructions) } }; + } + + VERIFY(result.value().terminator == 0x05); + NonnullOwnPtrVector<Instruction> left_instructions, right_instructions; + for (auto& entry : result.value().values) + left_instructions.append(make<Instruction>(move(entry))); + // if with else + { + auto result = parse_until_any_of<Instruction>(stream, 0x0b); + if (result.is_error()) + return result.error(); + + for (auto& entry : result.value().values) + right_instructions.append(make<Instruction>(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> CustomSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> TypeSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("TypeSection"); + auto types = parse_vector<FunctionType>(stream); + if (types.is_error()) + return types.error(); + return TypeSection { types.release_value() }; +} + +ParseResult<ImportSection::Import> ImportSection::Import::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<TableType>(stream, module, name); + case Constants::extern_memory_tag: + return parse_with_type<MemoryType>(stream, module, name); + case Constants::extern_global_tag: + return parse_with_type<GlobalType>(stream, module, name); + default: + return ParseError::InvalidInput; + } +} + +ParseResult<ImportSection> ImportSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("ImportSection"); + auto imports = parse_vector<Import>(stream); + if (imports.is_error()) + return imports.error(); + return ImportSection { imports.release_value() }; +} + +ParseResult<FunctionSection> FunctionSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("FunctionSection"); + auto indices = parse_vector<size_t>(stream); + if (indices.is_error()) + return indices.error(); + + Vector<TypeIndex> 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> TableSection::Table::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("Table"); + auto type = TableType::parse(stream); + if (type.is_error()) + return type.error(); + return Table { type.release_value() }; +} + +ParseResult<TableSection> TableSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("TableSection"); + auto tables = parse_vector<Table>(stream); + if (tables.is_error()) + return tables.error(); + return TableSection { tables.release_value() }; +} + +ParseResult<MemorySection::Memory> MemorySection::Memory::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("Memory"); + auto type = MemoryType::parse(stream); + if (type.is_error()) + return type.error(); + return Memory { type.release_value() }; +} + +ParseResult<MemorySection> MemorySection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("MemorySection"); + auto memorys = parse_vector<Memory>(stream); + if (memorys.is_error()) + return memorys.error(); + return MemorySection { memorys.release_value() }; +} + +ParseResult<Expression> Expression::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("Expression"); + auto instructions = parse_until_any_of<Instruction>(stream, 0x0b); + if (instructions.is_error()) + return instructions.error(); + + return Expression { move(instructions.value().values) }; +} + +ParseResult<GlobalSection::Global> GlobalSection::Global::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> GlobalSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("GlobalSection"); + auto result = parse_vector<Global>(stream); + if (result.is_error()) + return result.error(); + return GlobalSection { result.release_value() }; +} + +ParseResult<ExportSection::Export> ExportSection::Export::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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> ExportSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("ExportSection"); + auto result = parse_vector<Export>(stream); + if (result.is_error()) + return result.error(); + return ExportSection { result.release_value() }; +} + +ParseResult<StartSection::StartFunction> StartSection::StartFunction::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("StartFunction"); + size_t index; + if (!LEB128::read_unsigned(stream, index)) + return ParseError::InvalidInput; + return StartFunction { FunctionIndex { index } }; +} + +ParseResult<StartSection> StartSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("StartSection"); + auto result = StartFunction::parse(stream); + if (result.is_error()) + return result.error(); + return StartSection { result.release_value() }; +} + +ParseResult<ElementSection::Element> ElementSection::Element::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<size_t>(stream); + if (init.is_error()) + return init.error(); + + Vector<FunctionIndex> 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> ElementSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("ElementSection"); + auto result = Element::parse(stream); + if (result.is_error()) + return result.error(); + return ElementSection { result.release_value() }; +} + +ParseResult<Locals> Locals::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<u32>(count), type.release_value() }; +} + +ParseResult<Func> Func::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("Func"); + auto locals = parse_vector<Locals>(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> CodeSection::Code::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<u32>(size), func.release_value() }; +} + +ParseResult<CodeSection> CodeSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("CodeSection"); + auto result = parse_vector<Code>(stream); + if (result.is_error()) + return result.error(); + return CodeSection { result.release_value() }; +} + +ParseResult<DataSection::Data> DataSection::Data::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<u8>(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<u8>(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<u8>(stream); + if (init.is_error()) + return init.error(); + return Data { Active { init.release_value(), { index }, expr.release_value() } }; + } + VERIFY_NOT_REACHED(); +} + +ParseResult<DataSection> DataSection::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("DataSection"); + auto data = parse_vector<Data>(stream); + if (data.is_error()) + return data.error(); + + return DataSection { data.release_value() }; +} + +ParseResult<DataCountSection> DataCountSection::parse([[maybe_unused]] InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> logger("DataCountSection"); + // FIXME: Implement parsing optional values! + return ParseError::InvalidInput; +} + +ParseResult<Module> Module::parse(InputStream& stream) +{ + ScopeLogger<WASM_BINPARSER_DEBUG> 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<AnySection> 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 <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Debug.h> +#include <AK/DistinctNumeric.h> +#include <AK/MemoryStream.h> +#include <AK/NonnullOwnPtrVector.h> +#include <AK/Result.h> +#include <AK/String.h> +#include <AK/Variant.h> +#include <LibWasm/Constants.h> +#include <LibWasm/Opcode.h> + +namespace Wasm { + +enum class ParseError { + // FIXME: More descriptive errors! + InvalidInput, +}; + +template<typename T> +using ParseResult = Result<T, ParseError>; + +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<u8, 8> 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<ValueType> 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<ValueType> types) + : m_types(move(types)) + { + } + + const auto& types() const { return m_types; } + + static ParseResult<ResultType> parse(InputStream& stream); + +private: + Vector<ValueType> m_types; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#function-types%E2%91%A4 +class FunctionType { +public: + FunctionType(Vector<ValueType> parameters, Vector<ValueType> results) + : m_parameters(move(parameters)) + , m_results(move(results)) + { + } + + auto& parameters() const { return m_parameters; } + auto& results() const { return m_results; } + + static ParseResult<FunctionType> parse(InputStream& stream); + +private: + Vector<ValueType> m_parameters; + Vector<ValueType> m_results; +}; + +// https://webassembly.github.io/spec/core/bikeshed/#limits%E2%91%A5 +class Limits { +public: + explicit Limits(u32 min, Optional<u32> max = {}) + : m_min(min) + , m_max(move(max)) + { + } + + auto min() const { return m_min; } + auto& max() const { return m_max; } + + static ParseResult<Limits> parse(InputStream& stream); + +private: + u32 m_min { 0 }; + Optional<u32> 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<MemoryType> 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<TableType> 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<GlobalType> 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<BlockType> 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<u8>(0)) + { + } + + struct TableElementArgs { + TableIndex index; + ValueType element_type; + }; + + struct TableTableArgs { + TableIndex lhs; + TableIndex rhs; + }; + + struct BlockAndInstructionSet { + BlockType block_type; + NonnullOwnPtrVector<Instruction> instructions; + }; + + struct BlockAndTwoInstructionSets { + BlockType block_type; + NonnullOwnPtrVector<Instruction> left_instructions; + NonnullOwnPtrVector<Instruction> right_instructions; + }; + + struct TableBranchArgs { + Vector<LabelIndex> labels; + LabelIndex default_; + }; + + struct IndirectCallArgs { + TypeIndex type; + TableIndex table; + }; + + struct MemoryArgument { + u32 align; + u32 offset; + }; + + template<typename T> + explicit Instruction(OpCode opcode, T argument) + : m_opcode(opcode) + , m_arguments(move(argument)) + { + } + + static ParseResult<Instruction> 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<ValueType>, + 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<CustomSection> parse(InputStream& stream); + +private: + String m_name; + ByteBuffer m_contents; +}; + +class TypeSection { +public: + static constexpr u8 section_id = 1; + + explicit TypeSection(Vector<FunctionType> types) + : m_types(move(types)) + { + } + + auto& types() const { return m_types; } + + static ParseResult<TypeSection> parse(InputStream& stream); + +private: + Vector<FunctionType> m_types; +}; + +class ImportSection { +private: + class Import { + public: + using ImportDesc = Variant<TypeIndex, TableType, MemoryType, GlobalType>; + 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<Import> parse(InputStream& stream); + + private: + template<typename T> + static ParseResult<Import> 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<Import> imports) + : m_imports(move(imports)) + { + } + + auto& imports() const { return m_imports; } + + static ParseResult<ImportSection> parse(InputStream& stream); + +private: + Vector<Import> m_imports; +}; + +class FunctionSection { +public: + static constexpr u8 section_id = 3; + + explicit FunctionSection(Vector<TypeIndex> types) + : m_types(move(types)) + { + } + + auto& types() const { return m_types; } + + static ParseResult<FunctionSection> parse(InputStream& stream); + +private: + Vector<TypeIndex> m_types; +}; + +class TableSection { +private: + class Table { + public: + explicit Table(TableType type) + : m_type(move(type)) + { + } + + auto& type() const { return m_type; } + + static ParseResult<Table> parse(InputStream& stream); + + private: + TableType m_type; + }; + +public: + static constexpr u8 section_id = 4; + + explicit TableSection(Vector<Table> tables) + : m_tables(move(tables)) + { + } + + auto& tables() const { return m_tables; }; + + static ParseResult<TableSection> parse(InputStream& stream); + +private: + Vector<Table> m_tables; +}; + +class MemorySection { +private: + class Memory { + public: + explicit Memory(MemoryType type) + : m_type(move(type)) + { + } + + auto& type() const { return m_type; } + + static ParseResult<Memory> parse(InputStream& stream); + + private: + MemoryType m_type; + }; + +public: + static constexpr u8 section_id = 5; + + explicit MemorySection(Vector<Memory> memorys) + : m_memories(move(memorys)) + { + } + + auto& memories() const { return m_memories; } + + static ParseResult<MemorySection> parse(InputStream& stream); + +private: + Vector<Memory> m_memories; +}; + +class Expression { +public: + explicit Expression(Vector<Instruction> instructions) + : m_instructions(move(instructions)) + { + } + + auto& instructions() const { return m_instructions; } + + static ParseResult<Expression> parse(InputStream& stream); + +private: + Vector<Instruction> 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<Global> parse(InputStream& stream); + + private: + GlobalType m_type; + Expression m_expression; + }; + +public: + static constexpr u8 section_id = 6; + + explicit GlobalSection(Vector<Global> entries) + : m_entries(move(entries)) + { + } + + auto& entries() const { return m_entries; } + + static ParseResult<GlobalSection> parse(InputStream& stream); + +private: + Vector<Global> m_entries; +}; + +class ExportSection { +private: + using ExportDesc = Variant<FunctionIndex, TableIndex, MemoryIndex, GlobalIndex>; + 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<Export> parse(InputStream& stream); + + private: + String m_name; + ExportDesc m_description; + }; + +public: + static constexpr u8 section_id = 7; + + explicit ExportSection(Vector<Export> entries) + : m_entries(move(entries)) + { + } + + auto& entries() const { return m_entries; } + + static ParseResult<ExportSection> parse(InputStream& stream); + +private: + Vector<Export> m_entries; +}; + +class StartSection { +private: + class StartFunction { + public: + explicit StartFunction(FunctionIndex index) + : m_index(index) + { + } + + auto& index() const { return m_index; } + + static ParseResult<StartFunction> 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<StartSection> parse(InputStream& stream); + +private: + StartFunction m_function; +}; + +class ElementSection { +private: + class Element { + public: + explicit Element(TableIndex table, Expression expr, Vector<FunctionIndex> 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<Element> parse(InputStream& stream); + + private: + TableIndex m_table; + Expression m_offset; + Vector<FunctionIndex> 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<ElementSection> 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<Locals> 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> 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<Func> parse(InputStream& stream); + +private: + Vector<Locals> 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<Code> parse(InputStream& stream); + + private: + u32 m_size { 0 }; + Func m_func; + }; + +public: + static constexpr u8 section_id = 10; + + explicit CodeSection(Vector<Code> funcs) + : m_functions(move(funcs)) + { + } + + auto& functions() const { return m_functions; } + + static ParseResult<CodeSection> parse(InputStream& stream); + +private: + Vector<Code> m_functions; +}; + +class DataSection { +private: + class Data { + struct Passive { + Vector<u8> init; + }; + struct Active { + Vector<u8> init; + MemoryIndex index; + Expression offset; + }; + using Value = Variant<Passive, Active>; + + public: + explicit Data(Value value) + : m_value(move(value)) + { + } + + auto& value() const { return m_value; } + + static ParseResult<Data> parse(InputStream& stream); + + private: + Value m_value; + }; + +public: + static constexpr u8 section_id = 11; + + explicit DataSection(Vector<Data> data) + : m_data(move(data)) + { + } + + auto& data() const { return m_data; } + + static ParseResult<DataSection> parse(InputStream& stream); + +private: + Vector<Data> m_data; +}; + +class DataCountSection { +public: + static constexpr u8 section_id = 12; + + explicit DataCountSection(Optional<u32> count) + : m_count(move(count)) + { + } + + auto& count() const { return m_count; } + + static ParseResult<DataCountSection> parse(InputStream& stream); + +private: + Optional<u32> m_count; +}; + +class Module { +private: + class Function { + public: + explicit Function(TypeIndex type, Vector<ValueType> 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<ValueType> m_local_types; + Expression m_body; + }; + + using AnySection = Variant< + CustomSection, + TypeSection, + ImportSection, + FunctionSection, + TableSection, + MemorySection, + GlobalSection, + ExportSection, + StartSection, + ElementSection, + CodeSection, + DataSection>; + + static constexpr Array<u8, 4> wasm_magic { 0, 'a', 's', 'm' }; + static constexpr Array<u8, 4> wasm_version { 1, 0, 0, 0 }; + +public: + explicit Module(Vector<AnySection> sections) + : m_sections(move(sections)) + { + } + + static ParseResult<Module> parse(InputStream& stream); + +private: + Vector<AnySection> m_sections; +}; +} |