fix(shader): materialize prepared shaders without spirv-tools
This commit is contained in:
parent
05fa426112
commit
746bfa2a20
|
|
@ -1,6 +1,7 @@
|
|||
#include "ShaderDiskCache.hpp"
|
||||
#include "CacheKey.hpp"
|
||||
#include "VertexInputSpecialization.hpp"
|
||||
#include "SpirvBackend/SpirvSpecialization.hpp"
|
||||
#include "Optimization/ResourceMaterializer.hpp"
|
||||
#include "ShaderCacheDirectory.hpp"
|
||||
#include <algorithm>
|
||||
|
|
@ -716,6 +717,69 @@ void verifyVertexTypeSpecialization() {
|
|||
require(rejected, "out-of-range vertex type patch was accepted");
|
||||
}
|
||||
|
||||
void verifyBuiltinSpecialization() {
|
||||
std::vector<std::uint32_t> words{spv::MagicNumber, 0x00010300u, 0u, 40u, 0u};
|
||||
const auto emit = [&](spv::Op op, std::initializer_list<std::uint32_t> operands) {
|
||||
words.push_back((static_cast<std::uint32_t>(operands.size() + 1u) << 16u) | op);
|
||||
words.insert(words.end(), operands);
|
||||
};
|
||||
emit(spv::OpCapability, {spv::CapabilityShader});
|
||||
emit(spv::OpMemoryModel, {spv::AddressingModelLogical, spv::MemoryModelGLSL450});
|
||||
emit(spv::OpEntryPoint, {spv::ExecutionModelGLCompute, 10u, 0x6e69616du, 0u});
|
||||
emit(spv::OpExecutionMode, {10u, spv::ExecutionModeLocalSize, 1u, 1u, 1u});
|
||||
emit(spv::OpTypeVoid, {1u});
|
||||
emit(spv::OpTypeBool, {2u});
|
||||
emit(spv::OpTypeInt, {3u, 32u, 0u});
|
||||
emit(spv::OpTypeVector, {4u, 3u, 4u});
|
||||
emit(spv::OpTypeFunction, {5u, 1u});
|
||||
emit(spv::OpConstant, {3u, 6u, 0u});
|
||||
emit(spv::OpConstant, {3u, 7u, 1u});
|
||||
emit(spv::OpConstant, {3u, 8u, 2u});
|
||||
emit(spv::OpConstant, {3u, 9u, 3u});
|
||||
emit(spv::OpConstantComposite, {4u, 30u, 6u, 7u, 8u, 9u});
|
||||
emit(spv::OpFunction, {1u, 10u, spv::FunctionControlMaskNone, 5u});
|
||||
emit(spv::OpLabel, {11u});
|
||||
emit(spv::OpBitFieldUExtract, {3u, 12u, 9u, 7u, 7u});
|
||||
emit(spv::OpSelectionMerge, {16u, spv::SelectionControlMaskNone});
|
||||
emit(spv::OpSwitch, {12u, 15u, 1u, 14u});
|
||||
emit(spv::OpLabel, {14u});
|
||||
emit(spv::OpBranch, {16u});
|
||||
emit(spv::OpLabel, {15u});
|
||||
emit(spv::OpIAdd, {3u, 17u, 8u, 9u});
|
||||
emit(spv::OpBranch, {16u});
|
||||
emit(spv::OpLabel, {16u});
|
||||
emit(spv::OpPhi, {3u, 18u, 7u, 14u, 17u, 15u});
|
||||
emit(spv::OpIEqual, {2u, 19u, 18u, 7u});
|
||||
emit(spv::OpSelectionMerge, {22u, spv::SelectionControlMaskNone});
|
||||
emit(spv::OpBranchConditional, {19u, 20u, 21u});
|
||||
emit(spv::OpLabel, {20u});
|
||||
emit(spv::OpVectorExtractDynamic, {3u, 23u, 30u, 6u});
|
||||
emit(spv::OpVectorExtractDynamic, {3u, 24u, 30u, 7u});
|
||||
emit(spv::OpVectorExtractDynamic, {3u, 25u, 30u, 8u});
|
||||
emit(spv::OpVectorExtractDynamic, {3u, 26u, 30u, 9u});
|
||||
emit(spv::OpCompositeConstruct, {4u, 27u, 23u, 24u, 25u, 26u});
|
||||
emit(spv::OpBranch, {22u});
|
||||
emit(spv::OpLabel, {21u});
|
||||
emit(spv::OpUnreachable, {});
|
||||
emit(spv::OpLabel, {22u});
|
||||
emit(spv::OpReturn, {});
|
||||
emit(spv::OpFunctionEnd, {});
|
||||
const auto specialized = SpecializeSpirv(words);
|
||||
bool identity = false;
|
||||
bool correctPhi = false;
|
||||
for (std::size_t cursor = 5; cursor < specialized.size();) {
|
||||
const auto count = specialized[cursor] >> 16u;
|
||||
require(count != 0u && count <= specialized.size() - cursor, "builtin specialization produced a truncated instruction");
|
||||
const auto op = static_cast<spv::Op>(specialized[cursor] & 0xffffu);
|
||||
require(op != spv::OpSwitch && op != spv::OpBranchConditional && op != spv::OpPhi && op != spv::OpVectorExtractDynamic, "builtin specialization retained constant control flow or dynamic exports");
|
||||
if (op == spv::OpCopyObject && specialized[cursor + 2u] == 27u) identity = specialized[cursor + 3u] == 30u;
|
||||
if (op == spv::OpConstant && specialized[cursor + 2u] == 18u) correctPhi = specialized[cursor + 3u] == 1u;
|
||||
cursor += count;
|
||||
}
|
||||
require(identity && correctPhi, "builtin specialization selected the wrong export or phi value");
|
||||
require(SpecializeSpirv(specialized) == specialized, "builtin specialization is not stable");
|
||||
}
|
||||
|
||||
void verifyDefaultDirectory(const char* self) {
|
||||
setEnvironment("ANYPS5_SHADER_CACHE_DIR", "");
|
||||
setEnvironment("ANYPS5_NO_SHADER_CACHE", "1");
|
||||
|
|
@ -748,6 +812,7 @@ int main(int argc, char** argv) {
|
|||
verifyFailureMemoSwitch(argv[0]);
|
||||
verifyInvocationIsolation();
|
||||
verifyVertexTypeSpecialization();
|
||||
verifyBuiltinSpecialization();
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(directory, error);
|
||||
std::cout << "shader disk cache tests passed\n";
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ function(add_shader_recompiler target runtimeTarget)
|
|||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvEmitter.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvBufferFormat.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvRuntimeBuffer.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvSpecialization.cpp
|
||||
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvMemory/SpirvConstants.cpp
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "CacheKey.hpp"
|
||||
#include "CompiledVariant.hpp"
|
||||
#include "VertexInputSpecialization.hpp"
|
||||
#include "SpirvBackend/SpirvSpecialization.hpp"
|
||||
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
|
||||
#include "ShaderDiskCache.hpp"
|
||||
#include <list>
|
||||
|
|
@ -450,10 +451,9 @@ std::shared_ptr<const SpecializedModule> specializeModule(const CompiledShaderAr
|
|||
}
|
||||
cursor += count;
|
||||
}
|
||||
materialized = SpecializeSpirv(materialized);
|
||||
#if ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
materialized = ValidateAndOptimizeSpirv(materialized, target.vulkanVersion, target.spirvVersion, target.nonConstantImageOffsets, true, true);
|
||||
#else
|
||||
throw std::runtime_error("prepared shader materialization requires SPIRV-Tools");
|
||||
#endif
|
||||
std::map<std::uint32_t, std::uint32_t> descriptorVariables;
|
||||
std::set<std::uint32_t> usedDescriptors;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef CORE_SHADER_RECOMPILER_SPIRVSPECIALIZATION_HPP
|
||||
#define CORE_SHADER_RECOMPILER_SPIRVSPECIALIZATION_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
[[nodiscard]] std::vector<std::uint32_t> SpecializeSpirv(std::span<const std::uint32_t> words);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
#define SPV_ENABLE_UTILITY_CODE
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#undef SPV_ENABLE_UTILITY_CODE
|
||||
#include "SpirvBackend/SpirvSpecialization.hpp"
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
namespace {
|
||||
|
||||
using Instruction = std::vector<std::uint32_t>;
|
||||
|
||||
spv::Op Opcode(const Instruction& instruction) {
|
||||
return instruction.empty() ? spv::OpNop : static_cast<spv::Op>(instruction[0] & 0xffffu);
|
||||
}
|
||||
|
||||
std::uint32_t Result(const Instruction& instruction) {
|
||||
if (instruction.empty()) return 0u;
|
||||
bool result = false;
|
||||
bool type = false;
|
||||
spv::HasResultAndType(Opcode(instruction), &result, &type);
|
||||
return result ? instruction.at(type ? 2u : 1u) : 0u;
|
||||
}
|
||||
|
||||
Instruction Make(spv::Op op, std::initializer_list<std::uint32_t> operands) {
|
||||
Instruction result{(static_cast<std::uint32_t>(operands.size() + 1u) << 16u) | op};
|
||||
result.insert(result.end(), operands);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ScalarType {
|
||||
std::uint32_t width;
|
||||
bool boolean;
|
||||
};
|
||||
|
||||
class Specialization {
|
||||
public:
|
||||
explicit Specialization(std::span<const std::uint32_t> words) {
|
||||
if (words.size() < 5u || words[0] != spv::MagicNumber) throw std::runtime_error("invalid prepared SPIR-V header");
|
||||
header.assign(words.begin(), words.begin() + 5);
|
||||
for (std::size_t cursor = 5; cursor < words.size();) {
|
||||
const auto count = words[cursor] >> 16u;
|
||||
if (count == 0u || count > words.size() - cursor) throw std::runtime_error("truncated prepared SPIR-V instruction");
|
||||
instructions.emplace_back(words.begin() + cursor, words.begin() + cursor + count);
|
||||
const auto& instruction = instructions.back();
|
||||
const auto op = Opcode(instruction);
|
||||
bool hasResult = false;
|
||||
bool hasType = false;
|
||||
spv::HasResultAndType(op, &hasResult, &hasType);
|
||||
if (hasResult && hasType) resultTypes.emplace(instruction.at(2), instruction.at(1));
|
||||
if (op == spv::OpTypeBool) types.emplace(instruction.at(1), ScalarType{1u, true});
|
||||
if (op == spv::OpTypeInt) types.emplace(instruction.at(1), ScalarType{instruction.at(2), false});
|
||||
if (op == spv::OpConstant && types.contains(instruction.at(1)) && types.at(instruction[1]).width <= 32u) values.emplace(instruction.at(2), instruction.at(3));
|
||||
if (op == spv::OpConstantTrue || op == spv::OpConstantFalse) values.emplace(instruction.at(2), op == spv::OpConstantTrue ? 1u : 0u);
|
||||
cursor += count;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> Run() {
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = fold();
|
||||
changed |= prune();
|
||||
}
|
||||
removeUnusedExtracts();
|
||||
orderPhis();
|
||||
std::vector<std::uint32_t> words = header;
|
||||
bool inserted = false;
|
||||
for (const auto& instruction : instructions) {
|
||||
if (instruction.empty()) continue;
|
||||
if (!inserted && Opcode(instruction) == spv::OpFunction) {
|
||||
for (const auto& constant : constants) words.insert(words.end(), constant.begin(), constant.end());
|
||||
inserted = true;
|
||||
}
|
||||
if ((Opcode(instruction) == spv::OpName || Opcode(instruction) == spv::OpDecorate) && removed.contains(instruction.at(1))) continue;
|
||||
words.insert(words.end(), instruction.begin(), instruction.end());
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<std::uint32_t> value(std::uint32_t id) const {
|
||||
const auto found = values.find(id);
|
||||
return found != values.end() ? std::optional(found->second) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> evaluate(const Instruction& instruction) const {
|
||||
const auto op = Opcode(instruction);
|
||||
if (instruction.size() < 4u) return std::nullopt;
|
||||
const auto type = types.find(instruction[1]);
|
||||
if (type == types.end() || type->second.width > 32u) return std::nullopt;
|
||||
const auto left = value(instruction[3]);
|
||||
if (!left) return std::nullopt;
|
||||
if (op == spv::OpCopyObject) return left;
|
||||
if (op == spv::OpLogicalNot) return *left == 0u;
|
||||
if (op == spv::OpNot) return ~*left;
|
||||
if (instruction.size() < 5u) return std::nullopt;
|
||||
const auto right = value(instruction[4]);
|
||||
if (!right) return std::nullopt;
|
||||
switch (op) {
|
||||
case spv::OpIAdd: return *left + *right;
|
||||
case spv::OpISub: return *left - *right;
|
||||
case spv::OpIMul: return *left * *right;
|
||||
case spv::OpUDiv: return *right != 0u ? std::optional(*left / *right) : std::nullopt;
|
||||
case spv::OpUMod: return *right != 0u ? std::optional(*left % *right) : std::nullopt;
|
||||
case spv::OpBitwiseAnd: return *left & *right;
|
||||
case spv::OpBitwiseOr: return *left | *right;
|
||||
case spv::OpBitwiseXor: return *left ^ *right;
|
||||
case spv::OpShiftRightLogical: return *right < type->second.width ? std::optional(*left >> *right) : std::nullopt;
|
||||
case spv::OpShiftLeftLogical: return *right < type->second.width ? std::optional(*left << *right) : std::nullopt;
|
||||
case spv::OpIEqual: return *left == *right;
|
||||
case spv::OpINotEqual: return *left != *right;
|
||||
case spv::OpULessThan: return *left < *right;
|
||||
case spv::OpULessThanEqual: return *left <= *right;
|
||||
case spv::OpUGreaterThan: return *left > *right;
|
||||
case spv::OpUGreaterThanEqual: return *left >= *right;
|
||||
case spv::OpLogicalEqual: return (*left != 0u) == (*right != 0u);
|
||||
case spv::OpLogicalNotEqual: return (*left != 0u) != (*right != 0u);
|
||||
case spv::OpLogicalAnd: return *left != 0u && *right != 0u;
|
||||
case spv::OpLogicalOr: return *left != 0u || *right != 0u;
|
||||
case spv::OpBitFieldUExtract: {
|
||||
if (instruction.size() != 6u) throw std::runtime_error("invalid prepared bitfield instruction");
|
||||
const auto count = value(instruction[5]);
|
||||
if (!count || *right > 32u || *count > 32u - *right) return std::nullopt;
|
||||
if (*count == 0u) return 0u;
|
||||
const auto mask = *count == 32u ? UINT32_MAX : (1u << *count) - 1u;
|
||||
return (*left >> *right) & mask;
|
||||
}
|
||||
default: return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool fold() {
|
||||
bool changed = false;
|
||||
bool function = false;
|
||||
std::map<std::uint32_t, std::pair<std::uint32_t, std::uint32_t>> extracts;
|
||||
for (auto& instruction : instructions) {
|
||||
if (instruction.empty()) continue;
|
||||
auto op = Opcode(instruction);
|
||||
if (op == spv::OpFunction) function = true;
|
||||
if (op == spv::OpFunctionEnd) function = false;
|
||||
if (!function) continue;
|
||||
if (op == spv::OpSelect && instruction.size() == 6u) {
|
||||
if (const auto condition = value(instruction[3])) {
|
||||
instruction = Make(spv::OpCopyObject, {instruction[1], instruction[2], instruction[*condition != 0u ? 4u : 5u]});
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (op == spv::OpVectorExtractDynamic && instruction.size() == 5u) {
|
||||
if (const auto index = value(instruction[4])) {
|
||||
instruction = Make(spv::OpCompositeExtract, {instruction[1], instruction[2], instruction[3], *index});
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
op = Opcode(instruction);
|
||||
if (op == spv::OpCompositeExtract && instruction.size() == 5u) extracts.emplace(instruction[2], std::pair{instruction[3], instruction[4]});
|
||||
if (op == spv::OpCompositeConstruct && instruction.size() == 7u) {
|
||||
const auto first = extracts.find(instruction[3]);
|
||||
bool shuffle = first != extracts.end();
|
||||
for (std::size_t index = 4; shuffle && index < instruction.size(); ++index) {
|
||||
const auto found = extracts.find(instruction[index]);
|
||||
shuffle = found != extracts.end() && found->second.first == first->second.first;
|
||||
}
|
||||
if (shuffle) {
|
||||
const auto source = first->second.first;
|
||||
bool identity = resultTypes.contains(source) && resultTypes.at(source) == instruction[1];
|
||||
Instruction replacement = Make(spv::OpVectorShuffle, {instruction[1], instruction[2], source, source});
|
||||
for (std::size_t index = 3; index < instruction.size(); ++index) {
|
||||
const auto channel = extracts.at(instruction[index]).second;
|
||||
replacement.push_back(channel);
|
||||
identity &= channel == index - 3u;
|
||||
}
|
||||
replacement[0] = (static_cast<std::uint32_t>(replacement.size()) << 16u) | spv::OpVectorShuffle;
|
||||
instruction = identity ? Make(spv::OpCopyObject, {instruction[1], instruction[2], source}) : std::move(replacement);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
const auto result = evaluate(instruction);
|
||||
if (!result) continue;
|
||||
const auto type = types.at(instruction[1]);
|
||||
const auto bits = type.width == 32u ? *result : *result & ((1u << type.width) - 1u);
|
||||
values[instruction[2]] = bits;
|
||||
constants.push_back(type.boolean ? Make(bits != 0u ? spv::OpConstantTrue : spv::OpConstantFalse, {instruction[1], instruction[2]}) : Make(spv::OpConstant, {instruction[1], instruction[2], bits}));
|
||||
instruction.clear();
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool prune() {
|
||||
std::map<std::uint32_t, std::vector<std::size_t>> blocks;
|
||||
std::vector<std::uint32_t> entries;
|
||||
std::uint32_t block = 0;
|
||||
bool entry = false;
|
||||
for (std::size_t index = 0; index < instructions.size(); ++index) {
|
||||
const auto& instruction = instructions[index];
|
||||
if (instruction.empty()) continue;
|
||||
const auto op = Opcode(instruction);
|
||||
if (op == spv::OpFunction) entry = true;
|
||||
if (op == spv::OpFunctionEnd) block = 0;
|
||||
if (op == spv::OpLabel) {
|
||||
block = instruction.at(1);
|
||||
if (entry) entries.push_back(block);
|
||||
entry = false;
|
||||
}
|
||||
if (block != 0u) blocks[block].push_back(index);
|
||||
}
|
||||
bool changed = false;
|
||||
std::map<std::uint32_t, std::vector<std::uint32_t>> edges;
|
||||
for (const auto& [label, indices] : blocks) {
|
||||
auto& terminal = instructions[indices.back()];
|
||||
auto op = Opcode(terminal);
|
||||
std::uint32_t target = 0;
|
||||
bool loop = false;
|
||||
for (const auto index : indices) loop |= Opcode(instructions[index]) == spv::OpLoopMerge;
|
||||
if (!loop && op == spv::OpBranchConditional) {
|
||||
if (const auto condition = value(terminal.at(1))) target = terminal.at(*condition != 0u ? 2u : 3u);
|
||||
} else if (!loop && op == spv::OpSwitch) {
|
||||
if (const auto selector = value(terminal.at(1))) {
|
||||
target = terminal.at(2);
|
||||
if ((terminal.size() - 3u) % 2u != 0u) throw std::runtime_error("invalid prepared 32-bit switch");
|
||||
for (std::size_t index = 3; index < terminal.size(); index += 2u) if (terminal[index] == *selector) target = terminal[index + 1u];
|
||||
}
|
||||
}
|
||||
if (target != 0u) {
|
||||
for (const auto index : indices) if (Opcode(instructions[index]) == spv::OpSelectionMerge) instructions[index].clear();
|
||||
terminal = Make(spv::OpBranch, {target});
|
||||
op = spv::OpBranch;
|
||||
changed = true;
|
||||
}
|
||||
auto& successors = edges[label];
|
||||
if (op == spv::OpBranch) successors.push_back(terminal.at(1));
|
||||
else if (op == spv::OpBranchConditional) successors = {terminal.at(2), terminal.at(3)};
|
||||
else if (op == spv::OpSwitch) {
|
||||
successors.push_back(terminal.at(2));
|
||||
const auto selectorType = resultTypes.find(terminal.at(1));
|
||||
const auto stride = selectorType != resultTypes.end() && types.contains(selectorType->second) && types.at(selectorType->second).width == 64u ? 3u : 2u;
|
||||
for (std::size_t index = 3; index + stride <= terminal.size(); index += stride) successors.push_back(terminal[index + stride - 1u]);
|
||||
}
|
||||
}
|
||||
std::set<std::uint32_t> live;
|
||||
auto pending = entries;
|
||||
while (!pending.empty()) {
|
||||
const auto label = pending.back();
|
||||
pending.pop_back();
|
||||
if (!blocks.contains(label)) throw std::runtime_error("prepared branch references a missing block");
|
||||
if (!live.insert(label).second) continue;
|
||||
const auto& successors = edges.at(label);
|
||||
pending.insert(pending.end(), successors.begin(), successors.end());
|
||||
}
|
||||
for (const auto& [label, indices] : blocks) {
|
||||
if (!live.contains(label)) {
|
||||
if (indices.size() == 2u && Opcode(instructions[indices.back()]) == spv::OpUnreachable) continue;
|
||||
for (std::size_t index = 1; index < indices.size(); ++index) {
|
||||
auto& instruction = instructions[indices[index]];
|
||||
if (const auto result = Result(instruction)) removed.insert(result);
|
||||
instruction.clear();
|
||||
}
|
||||
instructions[indices.back()] = Make(spv::OpUnreachable, {});
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
for (const auto index : indices) {
|
||||
auto& instruction = instructions[index];
|
||||
if (instruction.empty() || Opcode(instruction) != spv::OpPhi) continue;
|
||||
Instruction phi(instruction.begin(), instruction.begin() + 3);
|
||||
for (std::size_t operand = 3; operand + 1u < instruction.size(); operand += 2u) {
|
||||
const auto parent = instruction[operand + 1u];
|
||||
if (live.contains(parent) && std::ranges::find(edges.at(parent), label) != edges.at(parent).end()) phi.insert(phi.end(), {instruction[operand], parent});
|
||||
}
|
||||
if (phi.size() == 3u) throw std::runtime_error("reachable prepared phi has no predecessor");
|
||||
if (phi.size() == instruction.size() && phi.size() != 5u) continue;
|
||||
phi[0] = (static_cast<std::uint32_t>(phi.size()) << 16u) | spv::OpPhi;
|
||||
instruction = phi.size() == 5u ? Make(spv::OpCopyObject, {phi[1], phi[2], phi[3]}) : std::move(phi);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
void orderPhis() {
|
||||
std::vector<Instruction> ordered;
|
||||
std::vector<Instruction> copies;
|
||||
bool prefix = false;
|
||||
for (auto& instruction : instructions) {
|
||||
if (instruction.empty()) continue;
|
||||
const auto op = Opcode(instruction);
|
||||
if (op == spv::OpLabel) prefix = true;
|
||||
else if (prefix && op == spv::OpCopyObject) {
|
||||
copies.push_back(std::move(instruction));
|
||||
continue;
|
||||
} else if (prefix && op != spv::OpPhi && op != spv::OpLine && op != spv::OpNoLine) {
|
||||
for (auto& copy : copies) ordered.push_back(std::move(copy));
|
||||
copies.clear();
|
||||
prefix = false;
|
||||
}
|
||||
ordered.push_back(std::move(instruction));
|
||||
}
|
||||
if (!copies.empty()) throw std::runtime_error("unterminated prepared phi block");
|
||||
instructions = std::move(ordered);
|
||||
}
|
||||
|
||||
void removeUnusedExtracts() {
|
||||
std::set<std::uint32_t> referenced;
|
||||
for (const auto& instruction : instructions) {
|
||||
if (instruction.empty()) continue;
|
||||
const auto op = Opcode(instruction);
|
||||
if (op == spv::OpName || op == spv::OpDecorate) continue;
|
||||
bool hasResult = false;
|
||||
bool hasType = false;
|
||||
spv::HasResultAndType(op, &hasResult, &hasType);
|
||||
const auto resultIndex = hasResult ? (hasType ? 2u : 1u) : 0u;
|
||||
for (std::size_t index = 1; index < instruction.size(); ++index) if (index != resultIndex) referenced.insert(instruction[index]);
|
||||
}
|
||||
for (auto& instruction : instructions) {
|
||||
if (Opcode(instruction) != spv::OpCompositeExtract || referenced.contains(Result(instruction))) continue;
|
||||
removed.insert(Result(instruction));
|
||||
instruction.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> header;
|
||||
std::vector<Instruction> instructions;
|
||||
std::vector<Instruction> constants;
|
||||
std::map<std::uint32_t, ScalarType> types;
|
||||
std::map<std::uint32_t, std::uint32_t> values;
|
||||
std::map<std::uint32_t, std::uint32_t> resultTypes;
|
||||
std::set<std::uint32_t> removed;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> SpecializeSpirv(std::span<const std::uint32_t> words) {
|
||||
return Specialization(words).Run();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue