Skip to content

Commit

Permalink
wasm: Add initial instruction parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
robinlinden committed Oct 22, 2023
1 parent bf297ae commit 8375b67
Show file tree
Hide file tree
Showing 4 changed files with 454 additions and 0 deletions.
168 changes: 168 additions & 0 deletions wasm/instructions.cpp
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
140 changes: 140 additions & 0 deletions wasm/instructions.h
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
Loading

0 comments on commit 8375b67

Please sign in to comment.