-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wasm: Add initial instruction parsing
- Loading branch information
1 parent
bf297ae
commit 8375b67
Showing
4 changed files
with
454 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// SPDX-FileCopyrightText: 2023 Robin Lindén <[email protected]> | ||
// | ||
// SPDX-License-Identifier: BSD-2-Clause | ||
|
||
#include "wasm/instructions.h" | ||
|
||
#include "wasm/leb128.h" | ||
|
||
#include <iomanip> | ||
#include <iostream> | ||
#include <istream> | ||
#include <sstream> | ||
|
||
namespace wasm::instructions { | ||
|
||
// clangd (16) crashes if this is = default even though though it's allowed and | ||
// clang has alledegly implemented it starting with Clang 14: | ||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2085r0.html | ||
// https://clang.llvm.org/cxx_status.html | ||
bool Block::operator==(Block const &b) const { | ||
return b.type == type && b.instructions == instructions; | ||
} | ||
|
||
bool Loop::operator==(Loop const &l) const { | ||
return l.type == type && l.instructions == instructions; | ||
} | ||
|
||
std::optional<BlockType> BlockType::parse(std::istream &is) { | ||
std::uint8_t type{}; | ||
if (!is.read(reinterpret_cast<char *>(&type), sizeof(type))) { | ||
return std::nullopt; | ||
} | ||
|
||
constexpr std::uint8_t kEmptyTag = 0x40; | ||
if (type == kEmptyTag) { | ||
return BlockType{{BlockType::Empty{}}}; | ||
} | ||
|
||
std::stringstream ss{std::string{static_cast<char>(type)}}; | ||
auto value_type = ValueType::parse(ss); | ||
if (value_type) { | ||
return BlockType{{*std::move(value_type)}}; | ||
} | ||
|
||
std::cerr << "Unhandled BlockType\n"; | ||
return std::nullopt; | ||
} | ||
|
||
std::optional<MemArg> MemArg::parse(std::istream &is) { | ||
auto a = wasm::Leb128<std::uint32_t>::decode_from(is); | ||
if (!a) { | ||
return std::nullopt; | ||
} | ||
|
||
auto o = wasm::Leb128<std::uint32_t>::decode_from(is); | ||
if (!o) { | ||
return std::nullopt; | ||
} | ||
|
||
return MemArg{.align = *std::move(a), .offset = *std::move(o)}; | ||
} | ||
|
||
std::optional<std::vector<Instruction>> parse(std::istream &is) { | ||
std::vector<Instruction> instructions{}; | ||
|
||
while (true) { | ||
std::uint8_t opcode{}; | ||
if (!is.read(reinterpret_cast<char *>(&opcode), sizeof(opcode))) { | ||
return std::nullopt; | ||
} | ||
|
||
switch (opcode) { | ||
case Block::kOpcode: { | ||
auto type = BlockType::parse(is); | ||
if (!type) { | ||
return std::nullopt; | ||
} | ||
|
||
auto block_instructions = parse(is); | ||
if (!block_instructions) { | ||
return std::nullopt; | ||
} | ||
|
||
instructions.emplace_back(Block{*std::move(type), *std::move(block_instructions)}); | ||
break; | ||
} | ||
case Loop::kOpcode: { | ||
auto type = BlockType::parse(is); | ||
if (!type) { | ||
return std::nullopt; | ||
} | ||
|
||
auto block_instructions = parse(is); | ||
if (!block_instructions) { | ||
return std::nullopt; | ||
} | ||
|
||
instructions.emplace_back(Loop{*std::move(type), *std::move(block_instructions)}); | ||
break; | ||
} | ||
case BreakIf::kOpcode: { | ||
auto value = wasm::Leb128<std::uint32_t>::decode_from(is); | ||
if (!value) { | ||
return std::nullopt; | ||
} | ||
instructions.emplace_back(BreakIf{*value}); | ||
break; | ||
} | ||
case Return::kOpcode: | ||
instructions.emplace_back(Return{}); | ||
break; | ||
case End::kOpcode: | ||
return instructions; | ||
case I32Const::kOpcode: { | ||
auto value = wasm::Leb128<std::int32_t>::decode_from(is); | ||
if (!value) { | ||
return std::nullopt; | ||
} | ||
instructions.emplace_back(I32Const{*value}); | ||
break; | ||
} | ||
case I32LessThanSigned::kOpcode: | ||
instructions.emplace_back(I32LessThanSigned{}); | ||
break; | ||
case I32Add::kOpcode: | ||
instructions.emplace_back(I32Add{}); | ||
break; | ||
case LocalGet::kOpcode: { | ||
auto value = wasm::Leb128<std::uint32_t>::decode_from(is); | ||
if (!value) { | ||
return std::nullopt; | ||
} | ||
instructions.emplace_back(LocalGet{*value}); | ||
break; | ||
} | ||
case LocalSet::kOpcode: { | ||
auto value = wasm::Leb128<std::uint32_t>::decode_from(is); | ||
if (!value) { | ||
return std::nullopt; | ||
} | ||
instructions.emplace_back(LocalSet{*value}); | ||
break; | ||
} | ||
case LocalTee::kOpcode: { | ||
auto value = wasm::Leb128<std::uint32_t>::decode_from(is); | ||
if (!value) { | ||
return std::nullopt; | ||
} | ||
instructions.emplace_back(LocalTee{*value}); | ||
break; | ||
} | ||
case I32Load::kOpcode: { | ||
auto arg = MemArg::parse(is); | ||
if (!arg) { | ||
return std::nullopt; | ||
} | ||
|
||
instructions.emplace_back(I32Load{*std::move(arg)}); | ||
break; | ||
} | ||
default: | ||
std::cerr << "Unhandled opcode 0x" << std::setw(2) << std::setfill('0') << std::hex << +opcode; | ||
return std::nullopt; | ||
} | ||
} | ||
} | ||
|
||
} // namespace wasm::instructions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// SPDX-FileCopyrightText: 2023 Robin Lindén <[email protected]> | ||
// | ||
// SPDX-License-Identifier: BSD-2-Clause | ||
|
||
#ifndef WASM_INSTRUCTIONS_H_ | ||
#define WASM_INSTRUCTIONS_H_ | ||
|
||
#include "wasm/wasm.h" | ||
|
||
#include <cstdint> | ||
#include <iosfwd> | ||
#include <optional> | ||
#include <variant> | ||
#include <vector> | ||
|
||
namespace wasm::instructions { | ||
|
||
struct BlockType { | ||
static std::optional<BlockType> parse(std::istream &); | ||
|
||
struct Empty { | ||
[[nodiscard]] bool operator==(Empty const &) const = default; | ||
}; | ||
std::variant<Empty, ValueType, TypeIdx> value; | ||
[[nodiscard]] bool operator==(BlockType const &) const = default; | ||
}; | ||
|
||
struct MemArg { | ||
static std::optional<MemArg> parse(std::istream &); | ||
|
||
std::uint32_t align{}; | ||
std::uint32_t offset{}; | ||
[[nodiscard]] bool operator==(MemArg const &) const = default; | ||
}; | ||
|
||
struct Block; | ||
struct Loop; | ||
struct BreakIf; | ||
struct Return; | ||
|
||
struct I32Const; | ||
struct I32LessThanSigned; | ||
struct I32Add; | ||
|
||
struct LocalGet; | ||
struct LocalSet; | ||
struct LocalTee; | ||
|
||
struct I32Load; | ||
|
||
using Instruction = std::variant<Block, | ||
Loop, | ||
BreakIf, | ||
Return, | ||
I32Const, | ||
I32LessThanSigned, | ||
I32Add, | ||
LocalGet, | ||
LocalSet, | ||
LocalTee, | ||
I32Load>; | ||
|
||
// https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions | ||
struct Block { | ||
static constexpr std::uint8_t kOpcode = 0x02; | ||
BlockType type{}; | ||
std::vector<Instruction> instructions; | ||
[[nodiscard]] bool operator==(Block const &) const; | ||
}; | ||
|
||
struct Loop { | ||
static constexpr std::uint8_t kOpcode = 0x03; | ||
BlockType type{}; | ||
std::vector<Instruction> instructions; | ||
[[nodiscard]] bool operator==(Loop const &) const; | ||
}; | ||
|
||
struct BreakIf { | ||
static constexpr std::uint8_t kOpcode = 0x0d; | ||
std::uint32_t label_idx{}; | ||
[[nodiscard]] bool operator==(BreakIf const &) const = default; | ||
}; | ||
|
||
struct Return { | ||
static constexpr std::uint8_t kOpcode = 0x0f; | ||
[[nodiscard]] bool operator==(Return const &) const = default; | ||
}; | ||
|
||
struct End { | ||
static constexpr std::uint8_t kOpcode = 0x0b; | ||
[[nodiscard]] bool operator==(End const &) const = default; | ||
}; | ||
|
||
// https://webassembly.github.io/spec/core/binary/instructions.html#numeric-instructions | ||
struct I32Const { | ||
static constexpr std::uint8_t kOpcode = 0x41; | ||
std::int32_t value{}; | ||
[[nodiscard]] bool operator==(I32Const const &) const = default; | ||
}; | ||
|
||
struct I32LessThanSigned { | ||
static constexpr std::uint8_t kOpcode = 0x48; | ||
[[nodiscard]] bool operator==(I32LessThanSigned const &) const = default; | ||
}; | ||
|
||
struct I32Add { | ||
static constexpr std::uint8_t kOpcode = 0x6a; | ||
[[nodiscard]] bool operator==(I32Add const &) const = default; | ||
}; | ||
|
||
// https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions | ||
struct LocalGet { | ||
static constexpr std::uint8_t kOpcode = 0x20; | ||
std::uint32_t idx{}; | ||
[[nodiscard]] bool operator==(LocalGet const &) const = default; | ||
}; | ||
|
||
struct LocalSet { | ||
static constexpr std::uint8_t kOpcode = 0x21; | ||
std::uint32_t idx{}; | ||
[[nodiscard]] bool operator==(LocalSet const &) const = default; | ||
}; | ||
|
||
struct LocalTee { | ||
static constexpr std::uint8_t kOpcode = 0x22; | ||
std::uint32_t idx{}; | ||
[[nodiscard]] bool operator==(LocalTee const &) const = default; | ||
}; | ||
|
||
// https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions | ||
struct I32Load { | ||
static constexpr std::uint8_t kOpcode = 0x28; | ||
MemArg arg{}; | ||
[[nodiscard]] bool operator==(I32Load const &) const = default; | ||
}; | ||
std::optional<std::vector<Instruction>> parse(std::istream &); | ||
|
||
} // namespace wasm::instructions | ||
|
||
#endif |
Oops, something went wrong.