Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,107 @@ void testInternedSpirvReleasesUnusedWords() {
check(std::ranges::equal(again.Words(), words) && released.Lock().empty(), "SPIR-V interned again after its release changed its words");
}

std::vector<std::uint32_t> foldedBitcasts(bool separateBlocks, bool differentInput, bool differentType, std::uint32_t nanOpcode, std::uint32_t decorationMask = 0u) {
SpirvModule module;
module.EmitCapability(spv::CapabilityShader);
module.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
const auto voidType = module.Type(spv::OpTypeVoid);
const auto boolType = module.Type(spv::OpTypeBool);
const auto wordType = module.Type(spv::OpTypeInt, 32u, 0u);
const auto floatType = module.Type(spv::OpTypeFloat, 32u);
const auto otherType = differentType ? module.Type(spv::OpTypeInt, 32u, 1u) : floatType;
const auto vectorType = module.Type(spv::OpTypeVector, wordType, 3u);
const auto input = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassInput, vectorType), spv::StorageClassInput);
module.AddAnnotation(spv::OpDecorate, input, spv::DecorationBuiltIn, spv::BuiltInGlobalInvocationId);
const auto firstOutputType = nanOpcode != 0u ? boolType : floatType;
const auto secondOutputType = nanOpcode != 0u ? boolType : otherType;
const std::array outputs{
module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassPrivate, firstOutputType), spv::StorageClassPrivate),
module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassPrivate, secondOutputType), spv::StorageClassPrivate)};
const auto mainId = module.AllocateId();
const auto functionType = module.Type(spv::OpTypeFunction, voidType);
const auto zero = module.Constant(spv::OpConstant, wordType, 0u);
const auto trueValue = module.Constant(spv::OpConstantTrue, boolType);
module.EmitEntryPoint(spv::ExecutionModelGLCompute, mainId, "main", {input});
module.AddExecutionMode(mainId, spv::ExecutionModeLocalSize, 1u, 1u, 1u);
module.AddFunction(spv::OpFunction, voidType, mainId, spv::FunctionControlMaskNone, functionType);
module.AddFunction(spv::OpLabel, module.AllocateId());
const auto loaded = module.AllocateId();
const auto first = module.AllocateId();
const auto second = module.AllocateId();
const auto alias = module.AllocateId();
module.AddFunction(spv::OpLoad, vectorType, loaded, input);
module.AddFunction(spv::OpCompositeExtract, wordType, first, loaded, 0u);
module.AddFunction(spv::OpCompositeExtract, wordType, second, loaded, 1u);
module.AddFunction(spv::OpSelect, wordType, alias, trueValue, first, second);
const auto emit = [&](bool other) {
const auto cast = module.AllocateId();
module.AddFunction(spv::OpBitcast, other ? otherType : floatType, cast, other ? (differentInput ? second : alias) : first);
if ((decorationMask & (other ? 2u : 1u)) != 0u) module.AddAnnotation(spv::OpDecorate, cast, spv::DecorationRelaxedPrecision);
if ((decorationMask & (other ? 8u : 4u)) != 0u) {
const auto group = module.AllocateId();
module.AddAnnotation(spv::OpDecorationGroup, group);
module.AddAnnotation(spv::OpDecorate, group, spv::DecorationRelaxedPrecision);
module.AddAnnotation(spv::OpGroupDecorate, group, cast);
}
auto result = cast;
if (nanOpcode != 0u) {
result = module.AllocateId();
if (nanOpcode == spv::OpIsNan) module.AddFunction(nanOpcode, boolType, result, cast);
else module.AddFunction(nanOpcode, boolType, result, cast, cast);
}
module.AddFunction(spv::OpStore, outputs[other ? 1u : 0u], result);
};
if (separateBlocks) {
const auto condition = module.AllocateId();
const auto left = module.AllocateId();
const auto right = module.AllocateId();
const auto merge = module.AllocateId();
module.AddFunction(spv::OpINotEqual, boolType, condition, first, zero);
module.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone);
module.AddFunction(spv::OpBranchConditional, condition, left, right);
module.AddFunction(spv::OpLabel, left);
emit(false);
module.AddFunction(spv::OpBranch, merge);
module.AddFunction(spv::OpLabel, right);
emit(true);
module.AddFunction(spv::OpBranch, merge);
module.AddFunction(spv::OpLabel, merge);
} else {
emit(false);
emit(true);
}
module.AddFunction(spv::OpReturn);
module.AddFunction(spv::OpFunctionEnd);
return SpecializeSpirv(module.Finalize());
}

void testFoldedBitcastsAndNanTests() {
for (const auto nanOpcode : std::array<std::uint32_t, 3>{0u, spv::OpIsNan, spv::OpFUnordNotEqual}) {
const auto same = opcodeCounts(foldedBitcasts(false, false, false, nanOpcode));
const auto other = opcodeCounts(foldedBitcasts(false, true, false, nanOpcode));
const auto branches = opcodeCounts(foldedBitcasts(true, false, false, nanOpcode));
const auto count = [](const auto& counts, std::uint32_t op) {
const auto found = counts.find(op);
return found == counts.end() ? 0u : found->second;
};
check(count(same, spv::OpBitcast) == 1u, "folding a select left repeated bitcasts of one value in a block");
check(count(other, spv::OpBitcast) == 2u, "folding merged bitcasts of different input values");
check(count(branches, spv::OpBitcast) == 2u, "folding reused a bitcast from a sibling branch");
if (nanOpcode != 0u) {
check(count(same, nanOpcode) == 1u, "folding left repeated NaN tests of one value in a block");
check(count(other, nanOpcode) == 2u, "folding merged NaN tests of different input values");
check(count(branches, nanOpcode) == 2u, "folding reused a NaN test from a sibling branch");
}
}
check(opcodeCounts(foldedBitcasts(false, false, true, 0u))[spv::OpBitcast] == 2u, "folding merged bitcasts to different result types");
for (const auto decorations : {1u, 2u, 3u, 4u, 8u}) {
check(opcodeCounts(foldedBitcasts(false, false, false, 0u, decorations))[spv::OpBitcast] == 2u, "folding merged a decorated bitcast with another value");
}
}

int main() {
testFoldedBitcastsAndNanTests();
testFoldingLeavesNoDeadBlocksOrConstants();
testIdsBeyondTheBoundAreRejected();
testStructExtractsAreNotShuffled();
Expand Down
29 changes: 28 additions & 1 deletion core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#undef SPV_ENABLE_UTILITY_CODE
#include "SpirvBackend/SpirvSpecialization.hpp"
#include <algorithm>
#include <array>
#include <initializer_list>
#include <map>
#include <memory>
Expand Down Expand Up @@ -579,6 +580,7 @@ class Specialization {
resultTypes.Reset(bound);
vectorTypes.Reset(bound);
removed.Reset(bound);
decoratedResults.Reset(bound);
aliases.Reset(bound);
composites.Reset(bound);
privateTables.Reset(bound);
Expand All @@ -592,6 +594,8 @@ class Specialization {
bool hasType = false;
spv::HasResultAndType(op, &hasResult, &hasType);
if (hasResult && hasType) resultTypes.emplace(instruction.at(2), instruction.at(1));
if (op == spv::OpDecorate || op == spv::OpDecorateId || op == spv::OpDecorateString) decoratedResults.insert(instruction.at(1));
if (op == spv::OpGroupDecorate) for (std::size_t index = 2; index < instruction.size(); ++index) decoratedResults.insert(instruction[index]);
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::OpTypeVector) vectorTypes.insert(instruction.at(1));
Expand Down Expand Up @@ -720,11 +724,17 @@ class Specialization {
extracts.Reset(bound);
IdTable<std::uint32_t> bitcasts;
bitcasts.Reset(bound);
std::map<std::pair<std::uint32_t, std::uint32_t>, std::uint32_t> blockBitcasts;
std::map<std::array<std::uint32_t, 3>, std::uint32_t> blockNanTests;
IdTable<std::pair<std::uint32_t, std::uint32_t>> constPointers;
constPointers.Reset(bound);
for (auto& instruction : instructions) {
if (instruction.empty()) continue;
auto op = Opcode(instruction);
if (op == spv::OpFunction || op == spv::OpLabel) {
blockBitcasts.clear();
blockNanTests.clear();
}
if (op == spv::OpFunction) function = true;
if (op == spv::OpFunctionEnd) function = false;
if (!function) continue;
Expand Down Expand Up @@ -771,7 +781,23 @@ class Specialization {
instruction = Make(spv::OpCopyObject, {instruction[1], instruction[2], source});
changed = true;
} else {
bitcasts.emplace(instruction[2], instruction[3]);
const auto [found, inserted] = decoratedResults.contains(instruction[2])
? std::pair{blockBitcasts.end(), true}
: blockBitcasts.try_emplace(std::pair{instruction[1], instruction[3]}, instruction[2]);
if (inserted) {
bitcasts.emplace(instruction[2], instruction[3]);
} else {
instruction = Make(spv::OpCopyObject, {instruction[1], instruction[2], found->second});
changed = true;
}
}
}
const bool nanTest = (op == spv::OpIsNan && instruction.size() == 4u) || (op == spv::OpFUnordNotEqual && instruction.size() == 5u && instruction[3] == instruction[4]);
if (nanTest && !decoratedResults.contains(instruction[2])) {
const auto [found, inserted] = blockNanTests.try_emplace(std::array<std::uint32_t, 3>{op, instruction[1], instruction[3]}, instruction[2]);
if (!inserted) {
instruction = Make(spv::OpCopyObject, {instruction[1], instruction[2], found->second});
changed = true;
}
}
op = Opcode(instruction);
Expand Down Expand Up @@ -1147,6 +1173,7 @@ class Specialization {
IdTable<std::uint32_t> resultTypes;
IdSet vectorTypes;
IdSet removed;
IdSet decoratedResults;
IdTable<std::uint32_t> aliases;
IdTable<std::vector<std::uint32_t>> composites;
IdTable<std::uint32_t> privateTables;
Expand Down
Loading