srctree

David Zero parent 6e3fa93b 949c9148
wasm: Add instruction serialization with to_string()

Also adds the i32.sub instruction.

inlinesplit
wasm/instructions.cpp added: 368, removed: 6, total 362
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2024 David Zero <zero-one@zer0-one.net>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -131,6 +132,9 @@ std::optional<std::vector<Instruction>> parse(std::istream &is) {
case I32Add::kOpcode:
instructions.emplace_back(I32Add{});
break;
case I32Sub::kOpcode:
instructions.emplace_back(I32Sub{});
break;
case LocalGet::kOpcode: {
auto value = wasm::Leb128<std::uint32_t>::decode_from(is);
if (!value) {
 
wasm/instructions.h added: 368, removed: 6, total 362
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2024 David Zero <zero-one@zer0-one.net>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -10,6 +11,7 @@
#include <cstdint>
#include <iosfwd>
#include <optional>
#include <string_view>
#include <variant>
#include <vector>
 
@@ -30,22 +32,28 @@ struct MemArg {
 
std::uint32_t align{};
std::uint32_t offset{};
 
[[nodiscard]] bool operator==(MemArg const &) const = default;
};
 
// Control instructions
struct Block;
struct Loop;
struct BreakIf;
struct Return;
 
// Numeric instructions
struct I32Const;
struct I32LessThanSigned;
struct I32Add;
struct I32Sub;
 
// Variable instructions
struct LocalGet;
struct LocalSet;
struct LocalTee;
 
// Memory instructions
struct I32Load;
 
using Instruction = std::variant<Block,
@@ -55,6 +63,7 @@ using Instruction = std::variant<Block,
I32Const,
I32LessThanSigned,
I32Add,
I32Sub,
LocalGet,
LocalSet,
LocalTee,
@@ -63,6 +72,7 @@ using Instruction = std::variant<Block,
// https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions
struct Block {
static constexpr std::uint8_t kOpcode = 0x02;
static constexpr std::string_view kMnemonic = "block";
BlockType type{};
std::vector<Instruction> instructions;
[[nodiscard]] bool operator==(Block const &) const;
@@ -70,6 +80,7 @@ struct Block {
 
struct Loop {
static constexpr std::uint8_t kOpcode = 0x03;
static constexpr std::string_view kMnemonic = "loop";
BlockType type{};
std::vector<Instruction> instructions;
[[nodiscard]] bool operator==(Loop const &) const;
@@ -77,52 +88,67 @@ struct Loop {
 
struct BreakIf {
static constexpr std::uint8_t kOpcode = 0x0d;
static constexpr std::string_view kMnemonic = "br_if";
std::uint32_t label_idx{};
[[nodiscard]] bool operator==(BreakIf const &) const = default;
};
 
struct Return {
static constexpr std::uint8_t kOpcode = 0x0f;
static constexpr std::string_view kMnemonic = "return";
[[nodiscard]] bool operator==(Return const &) const = default;
};
 
struct End {
static constexpr std::uint8_t kOpcode = 0x0b;
static constexpr std::string_view kMnemonic = "end";
[[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;
static constexpr std::string_view kMnemonic = "i32.const";
std::int32_t value{};
[[nodiscard]] bool operator==(I32Const const &) const = default;
};
 
struct I32LessThanSigned {
static constexpr std::uint8_t kOpcode = 0x48;
static constexpr std::string_view kMnemonic = "i32.lt_s";
[[nodiscard]] bool operator==(I32LessThanSigned const &) const = default;
};
 
struct I32Add {
static constexpr std::uint8_t kOpcode = 0x6a;
static constexpr std::string_view kMnemonic = "i32.add";
[[nodiscard]] bool operator==(I32Add const &) const = default;
};
 
struct I32Sub {
static constexpr std::uint8_t kOpcode = 0x6b;
static constexpr std::string_view kMnemonic = "i32.sub";
[[nodiscard]] bool operator==(I32Sub const &) const = default;
};
 
// https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions
struct LocalGet {
static constexpr std::uint8_t kOpcode = 0x20;
static constexpr std::string_view kMnemonic = "local.get";
std::uint32_t idx{};
[[nodiscard]] bool operator==(LocalGet const &) const = default;
};
 
struct LocalSet {
static constexpr std::uint8_t kOpcode = 0x21;
static constexpr std::string_view kMnemonic = "local.set";
std::uint32_t idx{};
[[nodiscard]] bool operator==(LocalSet const &) const = default;
};
 
struct LocalTee {
static constexpr std::uint8_t kOpcode = 0x22;
static constexpr std::string_view kMnemonic = "local.tee";
std::uint32_t idx{};
[[nodiscard]] bool operator==(LocalTee const &) const = default;
};
@@ -130,6 +156,7 @@ struct LocalTee {
// https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions
struct I32Load {
static constexpr std::uint8_t kOpcode = 0x28;
static constexpr std::string_view kMnemonic = "i32.load";
MemArg arg{};
[[nodiscard]] bool operator==(I32Load const &) const = default;
};
 
wasm/instructions_test.cpp added: 368, removed: 6, total 362
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2024 David Zero <zero-one@zer0-one.net>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -87,6 +88,10 @@ int main() {
a.expect_eq(parse("\x6a\x0b"), InsnVec{I32Add{}}); //
});
 
s.add_test("i32_sub", [](etest::IActions &a) {
a.expect_eq(parse("\x6b\x0b"), InsnVec{I32Sub{}}); //
});
 
s.add_test("local_get", [](etest::IActions &a) {
// Valid index.
a.expect_eq(parse("\x20\x09\x0b"), InsnVec{LocalGet{.idx = 0x09}});
 
filename was Deleted added: 368, removed: 6, total 362
@@ -0,0 +1,142 @@
// SPDX-FileCopyrightText: 2024 David Zero <zero-one@zer0-one.net>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "wasm/serialize.h"
#include "wasm/instructions.h"
 
#include <cstddef>
#include <optional>
#include <string>
#include <variant>
 
namespace wasm::instructions {
 
void InstructionStringifyVisitor::apply_indent() {
for (std::size_t i = 0; i < indent; i++) {
out += "\t";
}
}
 
void InstructionStringifyVisitor::operator()(Block const &t) {
out += Block::kMnemonic;
out += " ";
out += to_string(t.type);
out += " ";
 
indent++;
 
for (Instruction const &i : t.instructions) {
out += "\n";
apply_indent();
 
std::string subinst = to_string(i, *this);
 
out += subinst;
}
 
indent--;
 
out += "\n";
apply_indent();
out += "end";
}
 
void InstructionStringifyVisitor::operator()(Loop const &t) {
out += Loop::kMnemonic;
out += " ";
out += to_string(t.type);
out += " ";
 
indent++;
 
for (Instruction const &i : t.instructions) {
out += "\n";
apply_indent();
 
std::string subinst = to_string(i, *this);
 
out += subinst;
}
 
indent--;
 
out += "\n";
apply_indent();
out += "end";
}
 
void InstructionStringifyVisitor::operator()(BreakIf const &t) {
out += BreakIf::kMnemonic;
out += " ";
out += std::to_string(t.label_idx);
}
 
void InstructionStringifyVisitor::operator()(Return const &) {
out += Return::kMnemonic;
}
 
void InstructionStringifyVisitor::operator()(I32Const const &t) {
out += I32Const::kMnemonic;
out += " ";
out += std::to_string(t.value);
}
 
void InstructionStringifyVisitor::operator()(I32LessThanSigned const &) {
out += I32LessThanSigned::kMnemonic;
}
 
void InstructionStringifyVisitor::operator()(I32Add const &) {
out += I32Add::kMnemonic;
}
 
void InstructionStringifyVisitor::operator()(I32Sub const &) {
out += I32Sub::kMnemonic;
}
 
void InstructionStringifyVisitor::operator()(LocalGet const &t) {
out += LocalGet::kMnemonic;
out += " ";
out += std::to_string(t.idx);
}
 
void InstructionStringifyVisitor::operator()(LocalSet const &t) {
out += LocalSet::kMnemonic;
out += " ";
out += std::to_string(t.idx);
}
 
void InstructionStringifyVisitor::operator()(LocalTee const &t) {
out += LocalTee::kMnemonic;
out += " ";
out += std::to_string(t.idx);
}
 
void InstructionStringifyVisitor::operator()(I32Load const &t) {
out += I32Load::kMnemonic;
 
std::string memarg = to_string(t.arg, 32);
 
if (!memarg.empty()) {
out += " ";
out += memarg;
}
}
 
std::string to_string(Instruction const &inst, std::optional<InstructionStringifyVisitor> visitor) {
if (visitor.has_value()) {
visitor.value().out.clear();
 
std::visit(*visitor, inst);
 
return visitor.value().out;
}
 
InstructionStringifyVisitor v;
 
std::visit(v, inst);
 
return v.out;
}
 
} // namespace wasm::instructions
 
filename was Deleted added: 368, removed: 6, total 362
@@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: 2024 David Zero <zero-one@zer0-one.net>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef WASM_SERIALIZE_H
#define WASM_SERIALIZE_H
 
#include "wasm/instructions.h"
#include "wasm/wasm.h"
 
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
 
namespace wasm {
 
constexpr std::string_view to_string(ValueType const &vt) {
switch (vt.kind) {
case ValueType::Kind::Int32:
return "i32";
case ValueType::Kind::Int64:
return "i64";
case ValueType::Kind::Float32:
return "f32";
case ValueType::Kind::Float64:
return "f64";
case ValueType::Kind::Vector128:
return "v128";
case ValueType::Kind::FunctionReference:
return "funcref";
case ValueType::Kind::ExternReference:
return "externref";
}
 
return "UNHANDLED_VALUE_TYPE";
}
 
} // namespace wasm
 
namespace wasm::instructions {
 
constexpr std::string to_string(BlockType const &bt) {
std::string out;
 
if (ValueType const *v = std::get_if<ValueType>(&bt.value)) {
out += "(result ";
out += wasm::to_string(*v);
out += ")";
} else if (TypeIdx const *t = std::get_if<TypeIdx>(&bt.value)) {
out += "(type ";
out += std::to_string(*t);
out += ")";
} else {
assert(std::holds_alternative<BlockType::Empty>(bt.value));
}
 
return out;
}
 
constexpr std::string to_string(MemArg const &ma, std::optional<std::uint32_t> natural_alignment = std::nullopt) {
std::string out;
 
// If offset == 0, omit offset
if (ma.offset != 0) {
out += "offset=";
out += std::to_string(ma.offset);
}
 
// Natural alignment, omit "align=" phrase
if (natural_alignment == ma.align) {
return out;
}
 
if (ma.offset != 0) {
out += " ";
}
 
out += "align=";
out += std::to_string(ma.align);
 
return out;
}
 
struct InstructionStringifyVisitor {
std::string out;
std::size_t indent = 0;
 
void apply_indent();
 
void operator()(Block const &t);
void operator()(Loop const &t);
void operator()(BreakIf const &t);
void operator()(Return const &);
void operator()(I32Const const &t);
void operator()(I32LessThanSigned const &);
void operator()(I32Add const &);
void operator()(I32Sub const &);
void operator()(LocalGet const &t);
void operator()(LocalSet const &t);
void operator()(LocalTee const &t);
void operator()(I32Load const &t);
};
 
std::string to_string(Instruction const &inst, std::optional<InstructionStringifyVisitor> = std::nullopt);
 
} // namespace wasm::instructions
 
#endif
 
filename was Deleted added: 368, removed: 6, total 362
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2024 David Zero <zero-one@zer0-one.net>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "wasm/instructions.h"
#include "wasm/serialize.h"
#include "wasm/wasm.h"
 
#include "etest/etest2.h"
 
int main() {
etest::Suite s{"wasm module serialization"};
 
using namespace wasm::instructions;
 
s.add_test("block", [](etest::IActions &a) {
a.expect_eq(to_string(Block{.type{wasm::ValueType{wasm::ValueType::Kind::Int32}},
.instructions{I32Const{2}, I32Const{2}, I32Add{}}}),
"block (result i32) \n\ti32.const 2\n\ti32.const 2\n\ti32.add\nend");
a.expect_eq(to_string(Block{.type{wasm::TypeIdx{7}}, .instructions{I32Const{2}, I32Const{2}, I32Add{}}}),
"block (type 7) \n\ti32.const 2\n\ti32.const 2\n\ti32.add\nend");
a.expect_eq(to_string(Block{.type{wasm::ValueType{wasm::ValueType::Kind::Int32}},
.instructions{Block{.type{wasm::ValueType{wasm::ValueType::Kind::Int32}},
.instructions{I32Const{8}}},
I32Const{2},
I32Const{2},
I32Add{}}}),
"block (result i32) \n\tblock (result i32) \n\t\ti32.const 8\n\tend\n\ti32.const 2\n\ti32.const "
"2\n\ti32.add\nend");
});
 
s.add_test("loop", [](etest::IActions &a) {
a.expect_eq(to_string(Loop{.type{wasm::ValueType{wasm::ValueType::Kind::Int32}},
.instructions{I32Const{2}, I32Const{2}, I32Add{}}}),
"loop (result i32) \n\ti32.const 2\n\ti32.const 2\n\ti32.add\nend");
a.expect_eq(to_string(Loop{.type{wasm::TypeIdx{7}}, .instructions{I32Const{2}, I32Const{2}, I32Add{}}}),
"loop (type 7) \n\ti32.const 2\n\ti32.const 2\n\ti32.add\nend");
a.expect_eq(to_string(Loop{.type{wasm::ValueType{wasm::ValueType::Kind::Int32}},
.instructions{Loop{.type{wasm::ValueType{wasm::ValueType::Kind::Int32}},
.instructions{I32Const{8}}},
I32Const{2},
I32Const{2},
I32Add{}}}),
"loop (result i32) \n\tloop (result i32) \n\t\ti32.const 8\n\tend\n\ti32.const 2\n\ti32.const "
"2\n\ti32.add\nend");
});
 
s.add_test("break_if", [](etest::IActions &a) { a.expect_eq(to_string(BreakIf{}), "br_if 0"); });
 
s.add_test("i32_const", [](etest::IActions &a) { a.expect_eq(to_string(I32Const{}), "i32.const 0"); });
 
s.add_test("i32_less_than_signed",
[](etest::IActions &a) { a.expect_eq(to_string(I32LessThanSigned{}), "i32.lt_s"); });
 
s.add_test("i32_add", [](etest::IActions &a) { a.expect_eq(to_string(I32Add{}), "i32.add"); });
 
s.add_test("i32_sub", [](etest::IActions &a) { a.expect_eq(to_string(I32Sub{}), "i32.sub"); });
 
s.add_test("local_get", [](etest::IActions &a) { a.expect_eq(to_string(LocalGet{}), "local.get 0"); });
 
s.add_test("local_set", [](etest::IActions &a) { a.expect_eq(to_string(LocalSet{}), "local.set 0"); });
 
s.add_test("local_tee", [](etest::IActions &a) { a.expect_eq(to_string(LocalTee{}), "local.tee 0"); });
 
s.add_test("i32_load", [](etest::IActions &a) {
a.expect_eq(to_string(I32Load{32, 0}), "i32.load"); // natural alignment, offset 0
a.expect_eq(to_string(I32Load{64, 0}), "i32.load align=64"); // 64-bit alignment for 32-bit load, offset 0
a.expect_eq(to_string(I32Load{64, 3}), "i32.load offset=3 align=64"); // 64-bit alignment, offset 3
});
 
return s.run();
}