mirror of
https://git.suyu.dev/suyu/sirit.git
synced 2026-01-04 13:44:59 +01:00
Stream SPIR-V instructions directly to a binary
Before this commit sirit generated a stream of tokens that would then be inserted to the final SPIR-V binary. This design was carried from the initial design of manually inserting opcodes into the code. Now that all instructions but labels are inserted when their respective function is called, the old design can be dropped in favor of generating a valid stream of SPIR-V opcodes. The API for variables is broken, but adopting the new one is trivial. Instead of calling OpVariable and then adding a global or local variable, OpVariable was removed and global or local variables are generated when they are called. Avoiding duplicates is now done with an std::unordered_set instead of using a linear search jumping through vtables.
This commit is contained in:
parent
c4ea8f4b76
commit
0b9ee36247
31 changed files with 651 additions and 1150 deletions
206
src/stream.h
206
src/stream.h
|
|
@ -6,29 +6,219 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Sirit {
|
||||
|
||||
class Declarations;
|
||||
|
||||
struct OpId {
|
||||
spv::Op opcode;
|
||||
Id result_type;
|
||||
};
|
||||
|
||||
struct EndOp {};
|
||||
|
||||
constexpr size_t WordsInString(std::string_view string) {
|
||||
return string.size() / sizeof(u32);
|
||||
}
|
||||
|
||||
inline void InsertStringView(std::vector<u32>& words, size_t& insert_index,
|
||||
std::string_view string) {
|
||||
const size_t size = string.size();
|
||||
const auto read = [string, size](size_t offset) {
|
||||
return offset < size ? static_cast<u8>(string[offset]) : u8(0);
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < size; i += sizeof(u32)) {
|
||||
words[insert_index++] = read(i) | read(i + 1) << 8 | read(i + 2) << 16 | read(i + 3) << 24;
|
||||
}
|
||||
if (size % sizeof(u32) == 0) {
|
||||
words[insert_index++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Stream {
|
||||
friend Declarations;
|
||||
|
||||
public:
|
||||
explicit Stream(std::vector<u32>& words);
|
||||
~Stream();
|
||||
explicit Stream(u32* bound_) : bound{bound_} {}
|
||||
|
||||
void Write(std::string_view string);
|
||||
void Reserve(size_t num_words) {
|
||||
if (insert_index + num_words <= words.size()) {
|
||||
return;
|
||||
}
|
||||
words.resize(insert_index + num_words);
|
||||
}
|
||||
|
||||
void Write(u64 value);
|
||||
std::span<const u32> Words() const noexcept {
|
||||
return std::span(words.data(), insert_index);
|
||||
}
|
||||
|
||||
void Write(u32 value);
|
||||
Stream& operator<<(spv::Op op) {
|
||||
op_index = insert_index;
|
||||
words[insert_index++] = static_cast<u32>(op);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Write(u16 first, u16 second);
|
||||
Stream& operator<<(OpId op) {
|
||||
op_index = insert_index;
|
||||
words[insert_index++] = static_cast<u32>(op.opcode);
|
||||
if (op.result_type.value != 0) {
|
||||
words[insert_index++] = op.result_type.value;
|
||||
}
|
||||
words[insert_index++] = ++*bound;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Write(u8 first, u8 second, u8 third, u8 fourth);
|
||||
Id operator<<(EndOp) {
|
||||
const size_t num_words = insert_index - op_index;
|
||||
words[op_index] |= static_cast<u32>(num_words) << 16;
|
||||
return Id{*bound};
|
||||
}
|
||||
|
||||
Stream& operator<<(u32 value) {
|
||||
words[insert_index++] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Stream& operator<<(s32 value) {
|
||||
return *this << static_cast<u32>(value);
|
||||
}
|
||||
|
||||
Stream& operator<<(u64 value) {
|
||||
return *this << static_cast<u32>(value) << static_cast<u32>(value >> 32);
|
||||
}
|
||||
|
||||
Stream& operator<<(s64 value) {
|
||||
return *this << static_cast<u64>(value);
|
||||
}
|
||||
|
||||
Stream& operator<<(float value) {
|
||||
return *this << std::bit_cast<u32>(value);
|
||||
}
|
||||
|
||||
Stream& operator<<(double value) {
|
||||
return *this << std::bit_cast<u64>(value);
|
||||
}
|
||||
|
||||
Stream& operator<<(bool value) {
|
||||
return *this << static_cast<u32>(value ? 1 : 0);
|
||||
}
|
||||
|
||||
Stream& operator<<(Id value) {
|
||||
return *this << value.value;
|
||||
}
|
||||
|
||||
Stream& operator<<(const Literal& literal) {
|
||||
std::visit([this](auto value) { *this << value; }, literal);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Stream& operator<<(std::string_view string) {
|
||||
InsertStringView(words, insert_index, string);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires std::is_enum_v<T> Stream& operator<<(T value) {
|
||||
static_assert(sizeof(T) == sizeof(u32));
|
||||
return *this << static_cast<u32>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Stream& operator<<(std::optional<T> value) {
|
||||
if (value) {
|
||||
*this << *value;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Stream& operator<<(std::span<const T> values) {
|
||||
for (const auto& value : values) {
|
||||
*this << value;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u32>& words;
|
||||
u32* bound = nullptr;
|
||||
std::vector<u32> words;
|
||||
size_t insert_index = 0;
|
||||
size_t op_index = 0;
|
||||
};
|
||||
|
||||
class Declarations {
|
||||
public:
|
||||
explicit Declarations(u32* bound) : stream{bound} {}
|
||||
|
||||
void Reserve(size_t num_words) {
|
||||
return stream.Reserve(num_words);
|
||||
}
|
||||
|
||||
std::span<const u32> Words() const noexcept {
|
||||
return stream.Words();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Declarations& operator<<(const T& value) {
|
||||
stream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Declarations without an id don't exist
|
||||
Declarations& operator<<(spv::Op) = delete;
|
||||
|
||||
Declarations& operator<<(OpId op) {
|
||||
id_index = op.result_type.value != 0 ? 2 : 1;
|
||||
stream << op;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Id operator<<(EndOp) {
|
||||
const auto begin = stream.words.begin();
|
||||
std::vector<u32> declarations(begin + stream.op_index, begin + stream.insert_index);
|
||||
|
||||
// Normalize result id for lookups
|
||||
const u32 id = std::exchange(declarations[id_index], 0);
|
||||
|
||||
const auto [entry, inserted] = existing_declarations.emplace(declarations, id);
|
||||
if (inserted) {
|
||||
return stream << EndOp{};
|
||||
}
|
||||
// If the declaration already exists, undo the operation
|
||||
stream.insert_index = stream.op_index;
|
||||
--*stream.bound;
|
||||
|
||||
return Id{entry->second};
|
||||
}
|
||||
|
||||
private:
|
||||
struct HashVector {
|
||||
size_t operator()(const std::vector<u32>& vector) const noexcept {
|
||||
size_t hash = std::hash<size_t>{}(vector.size());
|
||||
for (const u32 value : vector) {
|
||||
hash ^= std::hash<u32>{}(value);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
Stream stream;
|
||||
std::unordered_map<std::vector<u32>, u32, HashVector> existing_declarations;
|
||||
size_t id_index = 0;
|
||||
};
|
||||
|
||||
} // namespace Sirit
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue