14924 lines
696 KiB
C++
14924 lines
696 KiB
C++
#include "common/emulatorConfig.h"
|
|
#include "common/logging/log.h"
|
|
#include "common/stringUtils.h"
|
|
#include "common/subsystems.h"
|
|
#include "common/threads.h"
|
|
#include "graphics/guest_gpu/hardwareContext.h"
|
|
#include "graphics/guest_gpu/pm4.h"
|
|
#include "graphics/host_gpu/renderer/image/imageView.h"
|
|
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
|
#include "graphics/host_gpu/renderer/pipeline/blendMapping.h"
|
|
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
|
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
|
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
|
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
|
|
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
|
#include "graphics/shader/recompiler/frontend/translate/Translate.h"
|
|
#include "graphics/shader/recompiler/ir/IREmitter.h"
|
|
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
|
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
|
|
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
|
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
|
|
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
|
|
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
|
|
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
|
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
|
|
#include "graphics/shader/shader.h"
|
|
#include "graphics/shader/shaderCompiler.h"
|
|
#include "libs/agc.h"
|
|
#include "spirv-tools/libspirv.hpp"
|
|
#include "xxhash.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <bit>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <initializer_list>
|
|
#include <iterator>
|
|
#include <span>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
#include <cerrno>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace Libs::Graphics {
|
|
namespace {
|
|
|
|
void Check(bool value, const char *text) {
|
|
if (!value) {
|
|
std::fprintf(stderr, "ShaderCfgTests: failed: %s\n", text);
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
template <typename Function>
|
|
void ExpectFatal(Function function, const char *text) {
|
|
const pid_t pid = ::fork();
|
|
Check(pid >= 0, "fork failed while starting fatal shader test");
|
|
if (pid == 0) {
|
|
function();
|
|
::_exit(0);
|
|
}
|
|
|
|
int status = 0;
|
|
pid_t waited = -1;
|
|
do {
|
|
waited = ::waitpid(pid, &status, 0);
|
|
} while (waited < 0 && errno == EINTR);
|
|
Check(waited == pid, "waitpid failed while collecting fatal shader test");
|
|
Check(WIFEXITED(status) && WEXITSTATUS(status) == (321 & 0xff), text);
|
|
}
|
|
#endif
|
|
|
|
ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) {
|
|
static const ShaderVertexInputInfo vertex{};
|
|
static const ShaderPixelInputInfo pixel{};
|
|
static const ShaderComputeInputInfo compute{};
|
|
static const auto user_data = [] {
|
|
std::array<uint32_t, 64> data{};
|
|
data[3] = 3u << 28u; // Default fixture buffer uses raw offset bounds.
|
|
return data;
|
|
}();
|
|
|
|
ShaderRecompiler::CompileOptions options;
|
|
options.stage = stage;
|
|
options.user_data = user_data;
|
|
switch (stage) {
|
|
case ShaderType::Vertex:
|
|
options.input_info.vertex = &vertex;
|
|
break;
|
|
case ShaderType::Pixel:
|
|
options.input_info.pixel = &pixel;
|
|
break;
|
|
case ShaderType::Compute:
|
|
options.input_info.compute = &compute;
|
|
break;
|
|
default:
|
|
std::abort();
|
|
}
|
|
return options;
|
|
}
|
|
|
|
bool ReadHostTestMemory(void *, uint64_t address, std::span<uint32_t> values) {
|
|
if (address == 0 || values.empty()) {
|
|
return false;
|
|
}
|
|
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
|
|
return true;
|
|
}
|
|
|
|
struct TestCompileResult {
|
|
std::vector<uint32_t> spirv;
|
|
std::string decoded_dump;
|
|
std::string ir_dump;
|
|
ShaderRecompiler::IR::Program program;
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
};
|
|
|
|
TestCompileResult RecompileForTest(
|
|
std::span<const uint32_t> code,
|
|
const ShaderRecompiler::CompileOptions &options,
|
|
ShaderRecompiler::IR::SrtMemoryReader read_memory = nullptr,
|
|
void *read_memory_data = nullptr, uint32_t push_data_start_dword = 0) {
|
|
auto translated = ShaderRecompiler::TranslateProgram(code, options);
|
|
auto plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
.user_data = options.user_data,
|
|
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
|
.read_memory = read_memory,
|
|
.userdata = read_memory_data,
|
|
.read_specialization_memory = ReadHostTestMemory,
|
|
};
|
|
Check(ShaderRecompiler::IR::MaterializeResources(
|
|
plan, runtime, resources, specialization),
|
|
"test shader resources did not materialize");
|
|
auto compiled = ShaderRecompiler::CompileProgram(
|
|
std::move(translated), options, specialization, push_data_start_dword);
|
|
return {std::move(compiled.spirv), std::move(compiled.decoded_dump),
|
|
std::move(compiled.ir_dump), std::move(compiled.program),
|
|
std::move(resources)};
|
|
}
|
|
|
|
void CompilePixelRuntime(const ShaderParams ¶ms,
|
|
ShaderPixelInputInfo &input_info) {
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.shader_hash = params.hash;
|
|
options.user_data = std::span(params.user_data).first(params.user_data_count);
|
|
|
|
options.input_info.pixel = &input_info;
|
|
auto result = RecompileForTest(params.code, options);
|
|
static std::deque<ShaderRecompiler::IR::CompiledShaderInfo> programs;
|
|
static std::deque<ShaderRecompiler::IR::ResourceSnapshot> resources;
|
|
programs.push_back(std::move(result.program).TakeCompiledInfo());
|
|
input_info.stage.program = &programs.back();
|
|
resources.push_back(std::move(result.resources));
|
|
input_info.stage.resources = &resources.back();
|
|
}
|
|
|
|
template <typename InputInfo>
|
|
std::vector<uint32_t> MakeStageStaticKey(const InputInfo &input_info) {
|
|
std::vector<uint32_t> key;
|
|
BuildStageStaticKey(input_info, key);
|
|
return key;
|
|
}
|
|
|
|
std::vector<uint32_t>
|
|
CfgInstructionCoverage(const ShaderRecompiler::CFG::Graph &graph,
|
|
size_t instruction_count) {
|
|
std::vector<uint32_t> coverage(instruction_count);
|
|
for (const auto &block : graph.blocks) {
|
|
Check(block.inst_begin <= block.inst_end &&
|
|
block.inst_end <= instruction_count,
|
|
"CFG block has an invalid instruction range");
|
|
for (size_t index = block.inst_begin; index < block.inst_end; index++) {
|
|
coverage[index]++;
|
|
}
|
|
}
|
|
return coverage;
|
|
}
|
|
|
|
void CheckSpirvBinaryValidates(const std::vector<uint32_t> &binary) {
|
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
|
std::string messages;
|
|
|
|
tools.SetMessageConsumer([&messages](spv_message_level_t, const char *,
|
|
const spv_position_t &position,
|
|
const char *message) {
|
|
char buffer[1024] = {};
|
|
std::snprintf(buffer, sizeof(buffer), "%zu:%zu: %s\n", position.line,
|
|
position.column, message);
|
|
messages += buffer;
|
|
});
|
|
|
|
if (!tools.Validate(binary)) {
|
|
std::fprintf(stderr, "SPIR-V binary validation failed:\n%s\n",
|
|
messages.c_str());
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
std::string DisassembleSpirvBinary(const std::vector<uint32_t> &binary) {
|
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
|
std::string messages;
|
|
std::string source;
|
|
|
|
tools.SetMessageConsumer([&messages](spv_message_level_t, const char *,
|
|
const spv_position_t &position,
|
|
const char *message) {
|
|
char buffer[1024] = {};
|
|
std::snprintf(buffer, sizeof(buffer), "%zu:%zu: %s\n", position.line,
|
|
position.column, message);
|
|
messages += buffer;
|
|
});
|
|
|
|
if (!tools.Disassemble(binary.data(), binary.size(), &source,
|
|
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
|
|
SPV_BINARY_TO_TEXT_OPTION_INDENT)) {
|
|
std::fprintf(stderr, "SPIR-V binary disassembly failed:\n%s\n",
|
|
messages.c_str());
|
|
std::abort();
|
|
}
|
|
return std::string(source.c_str());
|
|
}
|
|
|
|
uint32_t CountSourceOccurrences(const std::string &source, const char *needle) {
|
|
uint32_t count = 0;
|
|
size_t from = 0;
|
|
for (;;) {
|
|
const auto found = source.find(needle, from);
|
|
if (found == std::string::npos) {
|
|
return count;
|
|
}
|
|
count++;
|
|
from = found + std::strlen(needle);
|
|
}
|
|
}
|
|
|
|
bool SpirvSourceHasInstructionUsing(const std::string &source,
|
|
const char *opcode, const char *name) {
|
|
std::istringstream stream(source);
|
|
std::string line;
|
|
while (std::getline(stream, line)) {
|
|
if ((line.find(opcode) != std::string::npos) && (line.find(name) != std::string::npos)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpirvContainsOpcode(const std::vector<uint32_t> &binary, uint32_t opcode) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if ((word & 0xffffu) == opcode) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint32_t SpirvInstructionOpcodeCount(const std::vector<uint32_t> &binary,
|
|
uint32_t opcode) {
|
|
uint32_t count = 0;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t op = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return count;
|
|
}
|
|
if (op == opcode) {
|
|
count++;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void CheckSpirvPhiParents(const std::vector<uint32_t> &binary) {
|
|
struct PhiParents {
|
|
uint32_t target = 0;
|
|
std::vector<uint32_t> parents;
|
|
};
|
|
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> successors;
|
|
std::vector<PhiParents> phis;
|
|
uint32_t current_label = 0;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word_count = binary[i] >> 16u;
|
|
const uint32_t opcode = binary[i] & 0xffffu;
|
|
Check(word_count != 0u && i + word_count <= binary.size(),
|
|
"SPIR-V instruction stream is malformed");
|
|
switch (opcode) {
|
|
case 245u: { // OpPhi
|
|
Check(current_label != 0u && word_count >= 5u &&
|
|
((word_count - 3u) % 2u) == 0u,
|
|
"OpPhi has malformed incoming operands");
|
|
PhiParents phi{.target = current_label};
|
|
for (size_t operand = 4; operand < word_count; operand += 2u) {
|
|
phi.parents.push_back(binary[i + operand]);
|
|
}
|
|
phis.push_back(std::move(phi));
|
|
break;
|
|
}
|
|
case 248u: // OpLabel
|
|
Check(word_count == 2u, "OpLabel has malformed operands");
|
|
current_label = binary[i + 1u];
|
|
break;
|
|
case 249u: // OpBranch
|
|
successors[current_label].insert(binary[i + 1u]);
|
|
break;
|
|
case 250u: // OpBranchConditional
|
|
successors[current_label].insert(binary[i + 2u]);
|
|
successors[current_label].insert(binary[i + 3u]);
|
|
break;
|
|
case 251u: // OpSwitch
|
|
successors[current_label].insert(binary[i + 2u]);
|
|
for (size_t operand = 4; operand < word_count; operand += 2u) {
|
|
successors[current_label].insert(binary[i + operand]);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
i += word_count;
|
|
}
|
|
for (const auto &phi : phis) {
|
|
std::unordered_set<uint32_t> unique_parents;
|
|
for (const auto parent : phi.parents) {
|
|
Check(unique_parents.insert(parent).second &&
|
|
successors[parent].contains(phi.target),
|
|
"OpPhi parent is not the physical block that branches to it");
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SpirvMetrics {
|
|
size_t words = 0;
|
|
uint32_t instructions = 0;
|
|
uint32_t ext_inst_imports = 0;
|
|
uint32_t type_voids = 0;
|
|
uint32_t type_bools = 0;
|
|
uint32_t type_ints = 0;
|
|
uint32_t type_floats = 0;
|
|
uint32_t type_vectors = 0;
|
|
uint32_t type_images = 0;
|
|
uint32_t type_samplers = 0;
|
|
uint32_t type_sampled_images = 0;
|
|
uint32_t runtime_arrays = 0;
|
|
uint32_t type_pointers = 0;
|
|
uint32_t type_functions = 0;
|
|
uint32_t image_pointers = 0;
|
|
uint32_t variables = 0;
|
|
uint32_t function_variables = 0;
|
|
uint32_t workgroup_variables = 0;
|
|
uint32_t loads = 0;
|
|
uint32_t stores = 0;
|
|
uint32_t array_lengths = 0;
|
|
uint32_t phis = 0;
|
|
uint32_t labels = 0;
|
|
uint32_t loop_merges = 0;
|
|
uint32_t selection_merges = 0;
|
|
uint32_t branches = 0;
|
|
uint32_t conditional_branches = 0;
|
|
uint32_t switches = 0;
|
|
uint32_t ballots = 0;
|
|
uint32_t quad_broadcasts = 0;
|
|
uint32_t sampled_1d_capabilities = 0;
|
|
uint32_t image_1d_capabilities = 0;
|
|
uint32_t image_query_capabilities = 0;
|
|
uint32_t storage_read_without_format_capabilities = 0;
|
|
uint32_t storage_write_without_format_capabilities = 0;
|
|
uint32_t image_texel_pointers = 0;
|
|
};
|
|
|
|
SpirvMetrics MeasureSpirv(const std::vector<uint32_t> &binary) {
|
|
SpirvMetrics metrics{};
|
|
metrics.words = binary.size();
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
break;
|
|
}
|
|
metrics.instructions++;
|
|
switch (opcode) {
|
|
case 11u: // OpExtInstImport
|
|
metrics.ext_inst_imports++;
|
|
break;
|
|
case 17u: // OpCapability
|
|
if (word_count >= 2u) {
|
|
switch (binary[i + 1u]) {
|
|
case 43u:
|
|
metrics.sampled_1d_capabilities++;
|
|
break;
|
|
case 44u:
|
|
metrics.image_1d_capabilities++;
|
|
break;
|
|
case 50u:
|
|
metrics.image_query_capabilities++;
|
|
break;
|
|
case 55u:
|
|
metrics.storage_read_without_format_capabilities++;
|
|
break;
|
|
case 56u:
|
|
metrics.storage_write_without_format_capabilities++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 19u: // OpTypeVoid
|
|
metrics.type_voids++;
|
|
break;
|
|
case 20u: // OpTypeBool
|
|
metrics.type_bools++;
|
|
break;
|
|
case 21u: // OpTypeInt
|
|
metrics.type_ints++;
|
|
break;
|
|
case 22u: // OpTypeFloat
|
|
metrics.type_floats++;
|
|
break;
|
|
case 23u: // OpTypeVector
|
|
metrics.type_vectors++;
|
|
break;
|
|
case 25u: // OpTypeImage
|
|
metrics.type_images++;
|
|
break;
|
|
case 26u: // OpTypeSampler
|
|
metrics.type_samplers++;
|
|
break;
|
|
case 27u: // OpTypeSampledImage
|
|
metrics.type_sampled_images++;
|
|
break;
|
|
case 29u: // OpTypeRuntimeArray
|
|
metrics.runtime_arrays++;
|
|
break;
|
|
case 32u: // OpTypePointer
|
|
metrics.type_pointers++;
|
|
if (word_count >= 4u && binary[i + 2u] == 11u) {
|
|
metrics.image_pointers++;
|
|
}
|
|
break;
|
|
case 33u: // OpTypeFunction
|
|
metrics.type_functions++;
|
|
break;
|
|
case 59u: // OpVariable
|
|
metrics.variables++;
|
|
if (word_count >= 4u && binary[i + 3u] == 7u) {
|
|
metrics.function_variables++;
|
|
} else if (word_count >= 4u && binary[i + 3u] == 4u) {
|
|
metrics.workgroup_variables++;
|
|
}
|
|
break;
|
|
case 60u: // OpImageTexelPointer
|
|
metrics.image_texel_pointers++;
|
|
break;
|
|
case 61u: // OpLoad
|
|
metrics.loads++;
|
|
break;
|
|
case 62u: // OpStore
|
|
metrics.stores++;
|
|
break;
|
|
case 68u: // OpArrayLength
|
|
metrics.array_lengths++;
|
|
break;
|
|
case 245u: // OpPhi
|
|
metrics.phis++;
|
|
break;
|
|
case 246u: // OpLoopMerge
|
|
metrics.loop_merges++;
|
|
break;
|
|
case 247u: // OpSelectionMerge
|
|
metrics.selection_merges++;
|
|
break;
|
|
case 248u: // OpLabel
|
|
metrics.labels++;
|
|
break;
|
|
case 249u: // OpBranch
|
|
metrics.branches++;
|
|
break;
|
|
case 250u: // OpBranchConditional
|
|
metrics.conditional_branches++;
|
|
break;
|
|
case 251u: // OpSwitch
|
|
metrics.switches++;
|
|
break;
|
|
case 339u: // OpGroupNonUniformBallot
|
|
metrics.ballots++;
|
|
break;
|
|
case 365u: // OpGroupNonUniformQuadBroadcast
|
|
metrics.quad_broadcasts++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return metrics;
|
|
}
|
|
|
|
void CheckSpirvBudget(const char *name, const std::vector<uint32_t> &binary,
|
|
const SpirvMetrics &budget) {
|
|
const auto actual = MeasureSpirv(binary);
|
|
const bool within =
|
|
actual.words <= budget.words &&
|
|
actual.instructions <= budget.instructions &&
|
|
actual.ext_inst_imports <= budget.ext_inst_imports &&
|
|
actual.type_images <= budget.type_images &&
|
|
actual.type_samplers <= budget.type_samplers &&
|
|
actual.type_sampled_images <= budget.type_sampled_images &&
|
|
actual.runtime_arrays <= budget.runtime_arrays &&
|
|
actual.image_pointers <= budget.image_pointers &&
|
|
actual.variables <= budget.variables && actual.loads <= budget.loads &&
|
|
actual.function_variables <= budget.function_variables &&
|
|
actual.workgroup_variables <= budget.workgroup_variables &&
|
|
actual.stores <= budget.stores &&
|
|
actual.array_lengths <= budget.array_lengths &&
|
|
actual.phis <= budget.phis && actual.labels <= budget.labels &&
|
|
actual.loop_merges <= budget.loop_merges &&
|
|
actual.selection_merges <= budget.selection_merges &&
|
|
actual.branches <= budget.branches &&
|
|
actual.conditional_branches <= budget.conditional_branches &&
|
|
actual.switches <= budget.switches && actual.ballots <= budget.ballots &&
|
|
actual.quad_broadcasts <= budget.quad_broadcasts &&
|
|
actual.sampled_1d_capabilities <= budget.sampled_1d_capabilities &&
|
|
actual.image_1d_capabilities <= budget.image_1d_capabilities &&
|
|
actual.image_query_capabilities <= budget.image_query_capabilities &&
|
|
actual.storage_read_without_format_capabilities <=
|
|
budget.storage_read_without_format_capabilities &&
|
|
actual.storage_write_without_format_capabilities <=
|
|
budget.storage_write_without_format_capabilities &&
|
|
actual.image_texel_pointers <= budget.image_texel_pointers;
|
|
if (!within) {
|
|
std::fprintf(
|
|
stderr,
|
|
"SPIR-V budget %s exceeded\n"
|
|
" actual: words=%zu insts=%u images=%u vars=%u loads=%u stores=%u "
|
|
"lengths=%u phis=%u labels=%u loops=%u selections=%u branches=%u "
|
|
"cond=%u switches=%u ballots=%u quad=%u\n"
|
|
" budget: words=%zu insts=%u images=%u vars=%u loads=%u stores=%u "
|
|
"lengths=%u phis=%u labels=%u loops=%u selections=%u branches=%u "
|
|
"cond=%u switches=%u ballots=%u quad=%u\n",
|
|
name, actual.words, actual.instructions, actual.type_images,
|
|
actual.variables, actual.loads, actual.stores, actual.array_lengths,
|
|
actual.phis, actual.labels, actual.loop_merges, actual.selection_merges,
|
|
actual.branches, actual.conditional_branches, actual.switches,
|
|
actual.ballots, actual.quad_broadcasts, budget.words,
|
|
budget.instructions, budget.type_images, budget.variables, budget.loads,
|
|
budget.stores, budget.array_lengths, budget.phis, budget.labels,
|
|
budget.loop_merges, budget.selection_merges, budget.branches,
|
|
budget.conditional_branches, budget.switches, budget.ballots,
|
|
budget.quad_broadcasts);
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
uint32_t SpirvArrayLengthCount(const std::vector<uint32_t> &binary,
|
|
uint32_t requested_length) {
|
|
std::vector<uint32_t> length_ids;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t opcode = binary[i] & 0xffffu;
|
|
const uint32_t word_count = binary[i] >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return 0;
|
|
}
|
|
if (opcode == 43u && word_count == 4u &&
|
|
binary[i + 3u] == requested_length) { // OpConstant
|
|
length_ids.push_back(binary[i + 2u]);
|
|
}
|
|
i += word_count;
|
|
}
|
|
|
|
uint32_t count = 0;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t opcode = binary[i] & 0xffffu;
|
|
const uint32_t word_count = binary[i] >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return count;
|
|
}
|
|
if (opcode == 28u && word_count == 4u && // OpTypeArray
|
|
std::find(length_ids.begin(), length_ids.end(), binary[i + 3u]) !=
|
|
length_ids.end()) {
|
|
count++;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
uint32_t SpirvUnsignedLessThanBoundCount(const std::vector<uint32_t> &binary,
|
|
uint32_t requested_bound) {
|
|
std::vector<uint32_t> bound_ids;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t opcode = binary[i] & 0xffffu;
|
|
const uint32_t word_count = binary[i] >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return 0;
|
|
}
|
|
if (opcode == 43u && word_count == 4u &&
|
|
binary[i + 3u] == requested_bound) { // OpConstant
|
|
bound_ids.push_back(binary[i + 2u]);
|
|
}
|
|
i += word_count;
|
|
}
|
|
|
|
uint32_t count = 0;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t opcode = binary[i] & 0xffffu;
|
|
const uint32_t word_count = binary[i] >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return count;
|
|
}
|
|
if (opcode == 176u && word_count == 5u && // OpULessThan
|
|
std::find(bound_ids.begin(), bound_ids.end(), binary[i + 4u]) !=
|
|
bound_ids.end()) {
|
|
count++;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool SpirvSourceHasInstructionOperand(const std::string &source,
|
|
const char *opcode, const char *operand) {
|
|
std::istringstream lines(source);
|
|
std::string line;
|
|
while (std::getline(lines, line)) {
|
|
std::istringstream words(line);
|
|
std::string word;
|
|
bool instruction = false;
|
|
while (words >> word) {
|
|
instruction |= word == opcode;
|
|
if (instruction && word == operand) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpirvContainsVectorShuffle(const std::vector<uint32_t> &binary,
|
|
const std::array<uint32_t, 4> &selectors) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t op = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (op == 79u && word_count == 9u &&
|
|
std::equal(selectors.begin(), selectors.end(),
|
|
binary.begin() + i + 5u)) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpirvContainsCapability(const std::vector<uint32_t> &binary,
|
|
uint32_t capability) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (opcode == 17u && word_count >= 2u && binary[i + 1] == capability) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpirvContainsExecutionMode(const std::vector<uint32_t> &binary,
|
|
uint32_t mode) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (opcode == 16u && word_count >= 3u && binary[i + 2] == mode) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpirvContainsExtInst(const std::vector<uint32_t> &binary,
|
|
uint32_t ext_inst) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (opcode == 12u && word_count >= 5u && binary[i + 4] == ext_inst) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint32_t SpirvExtInstCount(const std::vector<uint32_t> &binary,
|
|
uint32_t ext_inst) {
|
|
uint32_t count = 0;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return count;
|
|
}
|
|
if (opcode == 12u && word_count >= 5u && binary[i + 4] == ext_inst) {
|
|
count++;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool SpirvContainsTypeImage(const std::vector<uint32_t> &binary, uint32_t dim,
|
|
uint32_t arrayed, uint32_t sampled,
|
|
uint32_t multisampled = 0) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (opcode == 25u && word_count >= 9u && binary[i + 3] == dim &&
|
|
binary[i + 5] == arrayed && binary[i + 6] == multisampled &&
|
|
binary[i + 7] == sampled) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint32_t SpirvDecorationValueCount(const std::vector<uint32_t> &binary,
|
|
uint32_t decoration, uint32_t value) {
|
|
uint32_t count = 0;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return count;
|
|
}
|
|
if (opcode == 71u && word_count >= 4u && binary[i + 2] == decoration &&
|
|
binary[i + 3] == value) {
|
|
count++;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool SpirvHasDecorationValue(const std::vector<uint32_t> &binary,
|
|
uint32_t decoration, uint32_t value) {
|
|
return SpirvDecorationValueCount(binary, decoration, value) != 0;
|
|
}
|
|
|
|
bool SpirvHasMemberDecorationValue(std::span<const uint32_t> binary,
|
|
uint32_t decoration, uint32_t value) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (opcode == 72u && word_count >= 5u && binary[i + 3] == decoration &&
|
|
binary[i + 4] == value) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint32_t>
|
|
SpirvDecorationValueTargets(const std::vector<uint32_t> &binary,
|
|
uint32_t decoration, uint32_t value) {
|
|
std::vector<uint32_t> targets;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return targets;
|
|
}
|
|
if (opcode == 71u && word_count >= 4u && binary[i + 2] == decoration &&
|
|
binary[i + 3] == value) {
|
|
targets.push_back(binary[i + 1]);
|
|
}
|
|
i += word_count;
|
|
}
|
|
return targets;
|
|
}
|
|
|
|
std::vector<uint32_t>
|
|
SpirvStoredBuiltInElements(const std::vector<uint32_t> &binary,
|
|
uint32_t builtin) {
|
|
struct Access {
|
|
uint32_t base = 0;
|
|
std::vector<uint32_t> indices;
|
|
};
|
|
std::unordered_map<uint32_t, uint32_t> direct_builtins;
|
|
std::unordered_map<uint64_t, uint32_t> member_builtins;
|
|
std::unordered_map<uint32_t, uint32_t> pointer_pointees;
|
|
std::unordered_map<uint32_t, uint32_t> variable_types;
|
|
std::unordered_map<uint32_t, uint32_t> constants;
|
|
std::unordered_map<uint32_t, Access> accesses;
|
|
std::vector<uint32_t> stores;
|
|
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t opcode = binary[i] & 0xffffu;
|
|
const uint32_t count = binary[i] >> 16u;
|
|
if (count == 0 || i + count > binary.size()) {
|
|
return {};
|
|
}
|
|
if (opcode == 71u && count >= 4u && binary[i + 2] == 11u) {
|
|
direct_builtins[binary[i + 1]] = binary[i + 3];
|
|
} else if (opcode == 72u && count >= 5u && binary[i + 3] == 11u) {
|
|
const uint64_t key = (static_cast<uint64_t>(binary[i + 1]) << 32u) |
|
|
binary[i + 2];
|
|
member_builtins[key] = binary[i + 4];
|
|
} else if (opcode == 32u && count == 4u) {
|
|
pointer_pointees[binary[i + 1]] = binary[i + 3];
|
|
} else if (opcode == 43u && count >= 4u) {
|
|
constants[binary[i + 2]] = binary[i + 3];
|
|
} else if (opcode == 59u && count >= 4u) {
|
|
variable_types[binary[i + 2]] = binary[i + 1];
|
|
} else if (opcode == 65u && count >= 5u) {
|
|
accesses[binary[i + 2]] =
|
|
{binary[i + 3], std::vector<uint32_t>(binary.begin() + i + 4,
|
|
binary.begin() + i + count)};
|
|
} else if (opcode == 62u && count >= 3u) {
|
|
stores.push_back(binary[i + 1]);
|
|
}
|
|
i += count;
|
|
}
|
|
|
|
std::vector<uint32_t> elements;
|
|
for (const auto pointer : stores) {
|
|
if (const auto decorated = direct_builtins.find(pointer);
|
|
decorated != direct_builtins.end()) {
|
|
if (decorated->second == builtin) {
|
|
elements.push_back(UINT32_MAX);
|
|
}
|
|
continue;
|
|
}
|
|
const auto access = accesses.find(pointer);
|
|
if (access == accesses.end()) {
|
|
continue;
|
|
}
|
|
if (const auto decorated = direct_builtins.find(access->second.base);
|
|
decorated != direct_builtins.end()) {
|
|
if (decorated->second == builtin && !access->second.indices.empty()) {
|
|
const auto index = constants.find(access->second.indices.back());
|
|
if (index != constants.end()) {
|
|
elements.push_back(index->second);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
const auto variable = variable_types.find(access->second.base);
|
|
if (variable == variable_types.end() || access->second.indices.empty()) {
|
|
continue;
|
|
}
|
|
const auto pointee = pointer_pointees.find(variable->second);
|
|
const auto member = constants.find(access->second.indices.front());
|
|
if (pointee == pointer_pointees.end() || member == constants.end()) {
|
|
continue;
|
|
}
|
|
const uint64_t key = (static_cast<uint64_t>(pointee->second) << 32u) |
|
|
member->second;
|
|
if (const auto decorated = member_builtins.find(key);
|
|
decorated != member_builtins.end() && decorated->second == builtin) {
|
|
elements.push_back(UINT32_MAX);
|
|
}
|
|
}
|
|
std::sort(elements.begin(), elements.end());
|
|
return elements;
|
|
}
|
|
|
|
bool SpirvBuiltInStoreUsesOperation(const std::vector<uint32_t> &binary,
|
|
uint32_t builtin, uint32_t operation,
|
|
std::initializer_list<uint32_t> literals) {
|
|
std::unordered_set<uint32_t> variables;
|
|
std::unordered_map<uint32_t, uint32_t> constants;
|
|
std::unordered_map<uint32_t, std::vector<uint32_t>> operations;
|
|
std::unordered_map<uint32_t, uint32_t> stores;
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t opcode = binary[i] & 0xffffu;
|
|
const uint32_t count = binary[i] >> 16u;
|
|
if (count == 0 || i + count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (opcode == 71u && count >= 4u && binary[i + 2] == 11u &&
|
|
binary[i + 3] == builtin) {
|
|
variables.insert(binary[i + 1]);
|
|
} else if (opcode == 43u && count >= 4u) {
|
|
constants[binary[i + 2]] = binary[i + 3];
|
|
} else if (opcode == operation && count == 4u + literals.size()) {
|
|
operations[binary[i + 2]] = {binary.begin() + i + 4, binary.begin() + i + count};
|
|
} else if (opcode == 62u && count >= 3u) {
|
|
stores[binary[i + 1]] = binary[i + 2];
|
|
}
|
|
i += count;
|
|
}
|
|
for (const auto variable : variables) {
|
|
const auto store = stores.find(variable);
|
|
if (store == stores.end()) {
|
|
continue;
|
|
}
|
|
const auto value = operations.find(store->second);
|
|
if (value == operations.end()) {
|
|
continue;
|
|
}
|
|
if (std::equal(value->second.begin(), value->second.end(), literals.begin(),
|
|
[&](uint32_t id, uint32_t expected) {
|
|
const auto literal = constants.find(id);
|
|
return literal != constants.end() && literal->second == expected;
|
|
})) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpirvTargetHasDecoration(const std::vector<uint32_t> &binary,
|
|
uint32_t target, uint32_t decoration) {
|
|
for (size_t i = 5; i < binary.size();) {
|
|
const uint32_t word = binary[i];
|
|
const uint32_t opcode = word & 0xffffu;
|
|
const uint32_t word_count = word >> 16u;
|
|
if (word_count == 0 || i + word_count > binary.size()) {
|
|
return false;
|
|
}
|
|
if (opcode == 71u && word_count >= 3u && binary[i + 1] == target &&
|
|
binary[i + 2] == decoration) {
|
|
return true;
|
|
}
|
|
i += word_count;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpirvHasDecorationValueWithDecoration(const std::vector<uint32_t> &binary,
|
|
uint32_t value_decoration,
|
|
uint32_t value,
|
|
uint32_t decoration) {
|
|
const auto targets =
|
|
SpirvDecorationValueTargets(binary, value_decoration, value);
|
|
return std::any_of(targets.begin(), targets.end(), [&](uint32_t target) {
|
|
return SpirvTargetHasDecoration(binary, target, decoration);
|
|
});
|
|
}
|
|
|
|
bool ProgramHasInput(const ShaderRecompiler::IR::Program &program,
|
|
ShaderRecompiler::IR::StageInputKind kind) {
|
|
return std::any_of(program.info.inputs.begin(), program.info.inputs.end(),
|
|
[kind](const auto &input) { return input.kind == kind; });
|
|
}
|
|
|
|
uint32_t ProgramInputCount(const ShaderRecompiler::IR::Program &program,
|
|
ShaderRecompiler::IR::StageInputKind kind) {
|
|
return static_cast<uint32_t>(
|
|
std::count_if(program.info.inputs.begin(), program.info.inputs.end(),
|
|
[kind](const auto &input) { return input.kind == kind; }));
|
|
}
|
|
|
|
ShaderComputeInputInfo RegressionComputeInputInfo() {
|
|
ShaderComputeInputInfo input_info;
|
|
input_info.threads_num[0] = 1;
|
|
input_info.threads_num[1] = 1;
|
|
input_info.threads_num[2] = 1;
|
|
input_info.lds_size_dwords = 1024;
|
|
input_info.workgroup_register = 40;
|
|
return input_info;
|
|
}
|
|
|
|
ShaderPixelInputInfo RegressionPixelInputInfo() { return {}; }
|
|
|
|
void SetIdentityInterpolatorSettings(ShaderPixelInputInfo *input_info) {
|
|
Check(input_info != nullptr, "invalid pixel input info");
|
|
for (uint32_t i = 0; i < std::size(input_info->interpolator_settings); i++) {
|
|
input_info->interpolator_settings[i] = i;
|
|
}
|
|
}
|
|
|
|
void EnsureConfigInitialized() {
|
|
static bool config_initialized = false;
|
|
if (!config_initialized) {
|
|
static Common::Subsystems subsystems;
|
|
Common::InitializeThreads();
|
|
subsystems.Initialize<Config::Lifecycle>();
|
|
Config::ConfigOptions options;
|
|
options.printf_direction = Config::LogDirection::Silent;
|
|
Config::Load(options);
|
|
subsystems.Initialize<Log::Lifecycle>();
|
|
ShaderInit();
|
|
config_initialized = true;
|
|
}
|
|
}
|
|
|
|
void TestResourceDescriptorClassification() {
|
|
uint32_t raw_texture[8] = {};
|
|
raw_texture[3] = 9u << 28u;
|
|
raw_texture[5] = 2u << 27u;
|
|
Check(ShaderClassifyResourceDescriptor(raw_texture) ==
|
|
ResourceDescriptorType::Texture,
|
|
"raw texture descriptor must not be classified by ResourceDescriptor "
|
|
"byte 23 fields");
|
|
|
|
uint32_t tagged_buffer[8] = {};
|
|
tagged_buffer[5] = 1u << 27u;
|
|
Check(ShaderClassifyResourceDescriptor(tagged_buffer) ==
|
|
ResourceDescriptorType::Buffer,
|
|
"tagged ResourceDescriptor buffer was not recognized");
|
|
|
|
uint32_t tagged_sampler[8] = {};
|
|
tagged_sampler[5] = 2u << 27u;
|
|
Check(ShaderClassifyResourceDescriptor(tagged_sampler) ==
|
|
ResourceDescriptorType::Sampler,
|
|
"tagged ResourceDescriptor sampler was not recognized");
|
|
|
|
uint32_t tagged_unused[8] = {};
|
|
tagged_unused[5] = 3u << 27u;
|
|
Check(ShaderClassifyResourceDescriptor(tagged_unused) ==
|
|
ResourceDescriptorType::Unused,
|
|
"tagged ResourceDescriptor unused slot was not recognized");
|
|
|
|
uint32_t raw_buffer[8] = {};
|
|
Check(ShaderClassifyResourceDescriptor(raw_buffer) ==
|
|
ResourceDescriptorType::Buffer,
|
|
"raw buffer descriptor fallback was not preserved");
|
|
}
|
|
|
|
constexpr uint32_t EncodeSMovB32(uint32_t dst, uint32_t src) {
|
|
return 0x80000000u | (0x7du << 23u) | ((dst & 0x7fu) << 16u) | (0x03u << 8u) |
|
|
(src & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeSop1(uint32_t opcode, uint32_t dst, uint32_t src) {
|
|
return 0x80000000u | (0x7du << 23u) | ((dst & 0x7fu) << 16u) |
|
|
((opcode & 0xffu) << 8u) | (src & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeSop2(uint32_t opcode, uint32_t dst, uint32_t src0,
|
|
uint32_t src1) {
|
|
return 0x80000000u | ((opcode & 0x7fu) << 23u) | ((dst & 0x7fu) << 16u) |
|
|
((src1 & 0xffu) << 8u) | (src0 & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeSopk(uint32_t opcode, uint32_t dst, int16_t imm) {
|
|
return 0x80000000u | (((opcode + 0x60u) & 0x7fu) << 23u) |
|
|
((dst & 0x7fu) << 16u) | static_cast<uint16_t>(imm);
|
|
}
|
|
|
|
constexpr uint32_t EncodeSopc(uint32_t opcode, uint32_t src0, uint32_t src1) {
|
|
return 0x80000000u | (0x7eu << 23u) | ((opcode & 0x7fu) << 16u) |
|
|
((src1 & 0xffu) << 8u) | (src0 & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeSopp(uint32_t opcode, uint32_t simm = 0) {
|
|
return 0x80000000u | (0x7fu << 23u) | ((opcode & 0x7fu) << 16u) |
|
|
(simm & 0xffffu);
|
|
}
|
|
|
|
void TestShaderBufferResourceSize() {
|
|
ShaderBufferResource descriptor;
|
|
descriptor.fields[2] = UINT32_MAX;
|
|
Check(descriptor.GetSize() == UINT32_MAX,
|
|
"zero-stride buffer records must count bytes");
|
|
descriptor.fields[1] = 0x3FFFu << 16u;
|
|
Check(descriptor.GetSize() == uint64_t{0x3FFF} * UINT32_MAX,
|
|
"strided buffer byte size must preserve the full 64-bit product");
|
|
descriptor.UpdateAddress48(0x123456789ABCull);
|
|
Check(descriptor.GetSize() == uint64_t{0x3FFF} * UINT32_MAX,
|
|
"buffer address must not affect the descriptor byte size");
|
|
descriptor.fields[2] = 0;
|
|
Check(descriptor.GetSize() == 0,
|
|
"zero-record buffer must be empty even with a nonzero stride");
|
|
}
|
|
|
|
void TestNativeShaderResourceDependencies() {
|
|
const auto stages = ShaderPipelineStages(vk::ShaderStageFlagBits::eVertex |
|
|
vk::ShaderStageFlagBits::eFragment |
|
|
vk::ShaderStageFlagBits::eCompute);
|
|
Check(stages == (vk::PipelineStageFlagBits::eVertexShader |
|
|
vk::PipelineStageFlagBits::eFragmentShader |
|
|
vk::PipelineStageFlagBits::eComputeShader),
|
|
"shader-stage dependency mapping was not exact");
|
|
const auto shader_barrier = MakeShaderWriteDependency();
|
|
Check(shader_barrier.srcAccessMask == vk::AccessFlagBits::eShaderWrite &&
|
|
(shader_barrier.dstAccessMask & vk::AccessFlagBits::eShaderRead) &&
|
|
(shader_barrier.dstAccessMask &
|
|
vk::AccessFlagBits::eVertexAttributeRead),
|
|
"shader-write dependency does not expose draw/dispatch buffer writes");
|
|
|
|
ShaderRecompiler::IR::Program program;
|
|
program.info.buffers.resize(3);
|
|
program.info.buffers[0].written = true;
|
|
program.info.buffers[1].read = true;
|
|
program.info.buffers[2].written = true;
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
resources.buffers.resize(3);
|
|
auto set_buffer = [&](uint32_t index, uint64_t address, uint16_t stride,
|
|
uint32_t records) {
|
|
ShaderBufferResource descriptor;
|
|
descriptor.UpdateAddress48(address);
|
|
descriptor.fields[1] |= static_cast<uint32_t>(stride) << 16u;
|
|
descriptor.fields[2] = records;
|
|
std::memcpy(resources.buffers[index].dwords.data(), descriptor.fields,
|
|
sizeof(descriptor.fields));
|
|
resources.buffers[index].dword_count = 4;
|
|
};
|
|
set_buffer(0, 0x1000, 16, 3);
|
|
set_buffer(1, 0x1800, 4, 7);
|
|
set_buffer(2, 0x2000, 0, 64);
|
|
const ShaderRecompiler::IR::CompiledShaderInfo compiled{
|
|
.stage = program.stage,
|
|
.shader_hash = program.shader_hash,
|
|
.wave_size = program.wave_size,
|
|
.user_data_base = program.user_data_base,
|
|
.user_data_count = program.user_data_count,
|
|
.scratch_dwords = program.scratch_dwords,
|
|
.info = program.info,
|
|
.bindings = program.bindings,
|
|
};
|
|
ShaderStageRuntime runtime{.program = &compiled, .resources = &resources};
|
|
Check(HasShaderBufferWrites(runtime),
|
|
"graphics/compute write predicate lost nonempty written buffers");
|
|
set_buffer(0, 0, 16, 3);
|
|
set_buffer(2, 0x2000, 0, 0);
|
|
Check(!HasShaderBufferWrites(runtime),
|
|
"graphics/compute write predicate included null, empty, or read-only buffers");
|
|
set_buffer(2, 0x2000, 0, 64);
|
|
Check(HasShaderBufferWrites(runtime),
|
|
"graphics/compute write predicate lost a byte-addressed buffer");
|
|
set_buffer(2, 0x2000, 0x3FFF, UINT32_MAX);
|
|
Check(HasShaderBufferWrites(runtime),
|
|
"graphics/compute write predicate lost a maximum-size strided buffer");
|
|
|
|
const vk::Buffer buffer{reinterpret_cast<vk::Buffer::CType>(uintptr_t{1})};
|
|
const auto gds_barrier = MakeGdsDependency(buffer);
|
|
Check((gds_barrier.srcAccessMask & vk::AccessFlagBits::eHostWrite) &&
|
|
(gds_barrier.srcAccessMask & vk::AccessFlagBits::eTransferWrite) &&
|
|
(gds_barrier.srcAccessMask & vk::AccessFlagBits::eShaderWrite) &&
|
|
gds_barrier.dstAccessMask == (vk::AccessFlagBits::eShaderRead |
|
|
vk::AccessFlagBits::eShaderWrite) &&
|
|
gds_barrier.size == VK_WHOLE_SIZE,
|
|
"GDS dependency does not order host/transfer/shader writes");
|
|
}
|
|
|
|
void TestNormalizedImageContracts() {
|
|
// Captured PS5 256x256 atlas: each layer is a 0x56000-byte mip chain.
|
|
ImageInfo container{};
|
|
container.data = {0x241741000, 0x2198000};
|
|
container.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
container.guest_format = Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
container.type = Prospero::ImageType::kColor2D;
|
|
container.extent = {256, 256, 1};
|
|
container.resources = {9, 100};
|
|
container.pitch = 256;
|
|
container.bytes_per_block = 4;
|
|
container.samples = 1;
|
|
container.tile_mode = Prospero::TileMode::kStandard4KB;
|
|
container.mip_layout[0] = {0x16000, 0x1900000, 256, 256};
|
|
container.mip_layout[1] = {0x6000, 0x640000, 128, 128};
|
|
container.mip_layout[2] = {0x2000, 0x190000, 64, 64};
|
|
container.mip_layout[3] = {0x1000, 0x64000, 32, 32};
|
|
for (uint32_t level = 4; level < container.resources.levels; ++level) {
|
|
container.mip_layout[level] = {0, 0x64000, 32, 32};
|
|
}
|
|
|
|
ImageInfo subresource = container;
|
|
subresource.data = {0x2417ad000, 0x40000};
|
|
subresource.resources = {1, 1};
|
|
subresource.mip_layout = {};
|
|
subresource.mip_layout[0] = {0, 0x40000, 256, 256};
|
|
|
|
Check(container.BlockExtent() == vk::Extent2D{256, 256},
|
|
"normalized image block extent changed");
|
|
Check(subresource.IsCompatible(container),
|
|
"normalized compatible image was rejected");
|
|
Check(subresource.MipOf(container) == 0 &&
|
|
subresource.SliceOf(container, 0) == 1,
|
|
"PS5 mip-chain stride did not resolve the captured second array layer");
|
|
|
|
auto smaller = subresource;
|
|
smaller.data = {0x241747000, 0x10000};
|
|
smaller.extent = {128, 128, 1};
|
|
smaller.pitch = 128;
|
|
smaller.mip_layout[0] = {0, 0x10000, 128, 128};
|
|
Check(smaller.MipOf(container) == 1 && smaller.SliceOf(container, 1) == 0,
|
|
"PS5 reversed mip ordering did not resolve the captured smaller mip");
|
|
|
|
auto boundary = subresource;
|
|
boundary.data.address = container.data.address + 99 * 0x56000 + 0x16000;
|
|
Check(boundary.SliceOf(container, 0) == 99,
|
|
"PS5 array lookup rejected the final complete layer");
|
|
boundary.data.address += 0x56000;
|
|
Check(boundary.MipOf(container) == -1,
|
|
"PS5 array lookup accepted a layer beyond the parent range");
|
|
boundary = subresource;
|
|
boundary.data.address += 0x1000;
|
|
Check(boundary.MipOf(container) == -1,
|
|
"PS5 array lookup accepted a misaligned layer address");
|
|
boundary = subresource;
|
|
boundary.resources.layers = 2;
|
|
boundary.data.size *= 2;
|
|
boundary.mip_layout[0].size *= 2;
|
|
Check(boundary.MipOf(container) == -1,
|
|
"PS5 array lookup accepted contiguous child layers with a different stride");
|
|
boundary = subresource;
|
|
boundary.mip_layout[0].pitch *= 2;
|
|
Check(boundary.MipOf(container) == -1,
|
|
"PS5 array lookup accepted a different storage pitch");
|
|
|
|
auto small_atlas = container;
|
|
small_atlas.data = {0x239001000, 0x64000};
|
|
small_atlas.extent = {32, 32, 1};
|
|
small_atlas.pitch = 32;
|
|
small_atlas.resources = {6, 50};
|
|
small_atlas.mip_layout[0] = {0x1000, 0x32000, 32, 32};
|
|
for (uint32_t level = 1; level < small_atlas.resources.levels; ++level) {
|
|
small_atlas.mip_layout[level] = {0, 0x32000, 32, 32};
|
|
}
|
|
auto small_child = subresource;
|
|
small_child.data = {0x23900e000, 0x1000};
|
|
small_child.extent = {32, 32, 1};
|
|
small_child.pitch = 32;
|
|
small_child.mip_layout[0] = {0, 0x1000, 32, 32};
|
|
Check(small_child.MipOf(small_atlas) == 0 &&
|
|
small_child.SliceOf(small_atlas, 0) == 6,
|
|
"PS5 array lookup doubled the captured small-atlas layer index");
|
|
small_child.data.address = 0x23900d000;
|
|
Check(small_child.MipOf(small_atlas) == -1,
|
|
"PS5 array lookup treated a packed tail block as the full-size mip");
|
|
|
|
auto incompatible = subresource;
|
|
incompatible.samples = 2;
|
|
Check(!incompatible.IsCompatible(container) &&
|
|
incompatible.MipOf(container) == -1,
|
|
"sample-count mismatch was accepted as a compatible image");
|
|
|
|
auto compressed = container;
|
|
compressed.guest_format = Prospero::BufferFormat::kBc3UNorm;
|
|
compressed.pitch = 128;
|
|
compressed.extent.height = 64;
|
|
Check(compressed.BlockExtent() == vk::Extent2D{32, 16},
|
|
"block-compressed extent was not expressed in blocks");
|
|
|
|
Check(ImageViewOps::FormatsCompatible(vk::Format::eR8G8B8A8Unorm,
|
|
vk::Format::eR8G8B8A8Uint) &&
|
|
!ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat,
|
|
vk::Format::eR32Sfloat) &&
|
|
ImageViewOps::FormatsCompatible(vk::Format::eBc3UnormBlock,
|
|
vk::Format::eR32G32B32A32Uint),
|
|
"Vulkan image-view compatibility classes diverged from production");
|
|
}
|
|
|
|
void TestSpirvRequirementsAnalysis() {
|
|
using namespace ShaderRecompiler::IR;
|
|
|
|
Program program;
|
|
program.stage = ShaderType::Pixel;
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
auto *block = program.block_storage.back().get();
|
|
program.blocks.push_back(block);
|
|
program.memory_info.push_back({.kind = ResourceKind::Lds});
|
|
program.export_info.push_back({.vm = true});
|
|
|
|
block->AppendNewInst(ValueOpcode::DppMoveU32, {Value(0u), Value(true)});
|
|
block->AppendNewInst(ValueOpcode::ImageQueryLod,
|
|
{Value(0u), Value(0u), Value(0u)});
|
|
block->AppendNewInst(ValueOpcode::ImageGatherRaw,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u), Value(0u)});
|
|
auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32,
|
|
{Value(0u), Value(true)});
|
|
shared.SetFlags(MemoryFlags{.index = 0});
|
|
auto &export_value =
|
|
block->AppendNewInst(ValueOpcode::SetAttribute, {Value(0u), Value(true)});
|
|
export_value.SetFlags(ExportFlags{.index = 0});
|
|
|
|
const auto requirements =
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
|
|
Check(requirements.subgroup_ballot && requirements.subgroup_shuffle &&
|
|
requirements.subgroup_local_invocation_id &&
|
|
requirements.compute_derivatives &&
|
|
requirements.image_gather_extended && requirements.function_lds &&
|
|
requirements.pixel_valid_mask,
|
|
"consolidated SPIR-V requirements missed an IR dependency");
|
|
|
|
program.stage = ShaderType::Compute;
|
|
const auto compute_requirements =
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
|
|
Check(!compute_requirements.function_lds &&
|
|
!compute_requirements.pixel_valid_mask,
|
|
"stage-specific SPIR-V requirements leaked into compute");
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
ExpectFatal(
|
|
[&] {
|
|
program.stage = ShaderType::Pixel;
|
|
shared.SetFlags(MemoryFlags{.index = 1});
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
|
|
},
|
|
"invalid shared-memory requirement metadata did not terminate analysis");
|
|
ExpectFatal(
|
|
[&] {
|
|
program.stage = ShaderType::Pixel;
|
|
export_value.SetFlags(ExportFlags{.index = 1});
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
|
|
},
|
|
"invalid export requirement metadata did not terminate analysis");
|
|
#endif
|
|
|
|
Program add_tid;
|
|
add_tid.stage = ShaderType::Compute;
|
|
add_tid.info.buffers.resize(1);
|
|
add_tid.info.buffers[0].packed_stride = 1u << 20u;
|
|
add_tid.block_storage.push_back(std::make_unique<Block>());
|
|
auto *add_tid_block = add_tid.block_storage.back().get();
|
|
add_tid.blocks.push_back(add_tid_block);
|
|
add_tid.memory_info.push_back(
|
|
{.kind = ResourceKind::Buffer, .resource = 0});
|
|
auto &buffer = add_tid_block->AppendNewInst(
|
|
ValueOpcode::GetBufferResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
auto &load = add_tid_block->AppendNewInst(
|
|
ValueOpcode::LoadBufferU32,
|
|
{Value(&buffer), Value(0u), Value(0u), Value(0u), Value(true)});
|
|
load.SetFlags(MemoryFlags{.index = 0});
|
|
const auto add_tid_requirements =
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
|
|
Check(add_tid_requirements.subgroup_local_invocation_id &&
|
|
!add_tid_requirements.subgroup_ballot &&
|
|
!add_tid_requirements.subgroup_shuffle,
|
|
"buffer ADD_TID requested the wrong subgroup contract");
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
ExpectFatal(
|
|
[&] {
|
|
add_tid.memory_info[0].resource = 1;
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
|
|
},
|
|
"invalid buffer requirement metadata did not terminate analysis");
|
|
ExpectFatal(
|
|
[&] {
|
|
add_tid.stage = ShaderType::Vertex;
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
|
|
},
|
|
"graphics buffer ADD_TID did not terminate requirements analysis");
|
|
#endif
|
|
|
|
Program empty;
|
|
const auto empty_requirements =
|
|
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(empty);
|
|
Check(!empty_requirements.subgroup_ballot &&
|
|
!empty_requirements.subgroup_shuffle &&
|
|
!empty_requirements.subgroup_local_invocation_id &&
|
|
!empty_requirements.compute_derivatives &&
|
|
!empty_requirements.image_gather_extended &&
|
|
!empty_requirements.function_lds &&
|
|
!empty_requirements.pixel_valid_mask,
|
|
"empty IR unexpectedly requested SPIR-V features");
|
|
}
|
|
|
|
std::array<uint32_t, 64>
|
|
ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
|
|
std::array<uint32_t, 64> data{};
|
|
for (uint32_t start = 0; start + 3u < data.size(); start += 4u) {
|
|
data[start] = 0x1000u + start * 0x100u;
|
|
data[start + 1u] = static_cast<uint32_t>(Prospero::BufferFormat::k8UNorm)
|
|
<< 20u;
|
|
data[start + 2u] = UINT32_MAX;
|
|
data[start + 3u] = static_cast<uint32_t>(type) << 28u;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
void SetImageTestType(std::array<uint32_t, 64> *data, uint32_t srsrc,
|
|
Prospero::ImageType type) {
|
|
const auto type_dword = srsrc * 4u + 3u;
|
|
Check(data != nullptr && type_dword < data->size(),
|
|
"invalid image test descriptor source");
|
|
(*data)[type_dword] = static_cast<uint32_t>(type) << 28u;
|
|
}
|
|
|
|
void SetImageTestFormat(std::array<uint32_t, 64> *data, uint32_t srsrc,
|
|
Prospero::BufferFormat format) {
|
|
const auto format_dword = srsrc * 4u + 1u;
|
|
Check(data != nullptr && format_dword < data->size(),
|
|
"invalid image test descriptor source");
|
|
(*data)[format_dword] = static_cast<uint32_t>(format) << 20u;
|
|
}
|
|
|
|
bool ReadZeroTestMemory(void *, uint64_t, std::span<uint32_t> values) {
|
|
std::ranges::fill(values, 0u);
|
|
return true;
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop2(uint32_t opcode, uint32_t dst, uint32_t src0,
|
|
uint32_t src1) {
|
|
return ((opcode & 0x3fu) << 25u) | ((dst & 0xffu) << 17u) |
|
|
((src1 & 0xffu) << 9u) | (src0 & 0x1ffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop1(uint32_t opcode, uint32_t dst, uint32_t src0) {
|
|
return (0x3fu << 25u) | ((dst & 0xffu) << 17u) | ((opcode & 0xffu) << 9u) |
|
|
(src0 & 0x1ffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop1Sdwa(uint32_t src0, uint32_t dst_sel = 6,
|
|
uint32_t dst_u = 0, uint32_t src0_sel = 6,
|
|
uint32_t src0_sext = 0, uint32_t src0_neg = 0,
|
|
uint32_t src0_abs = 0, uint32_t s0 = 0) {
|
|
return (src0 & 0xffu) | ((dst_sel & 0x7u) << 8u) | ((dst_u & 0x3u) << 11u) |
|
|
((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((s0 & 0x1u) << 23u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop1Dpp(uint32_t src0, uint32_t dpp_ctrl = 0,
|
|
uint32_t row_mask = 0xf,
|
|
uint32_t bank_mask = 0xf) {
|
|
return (src0 & 0xffu) | ((dpp_ctrl & 0x1ffu) << 8u) |
|
|
((bank_mask & 0xfu) << 24u) | ((row_mask & 0xfu) << 28u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop2Sdwa(uint32_t src0, uint32_t dst_sel = 6,
|
|
uint32_t dst_u = 0, uint32_t src0_sel = 6,
|
|
uint32_t src1_sel = 6, uint32_t src0_sext = 0,
|
|
uint32_t src1_sext = 0, uint32_t src0_neg = 0,
|
|
uint32_t src0_abs = 0, uint32_t src1_neg = 0,
|
|
uint32_t src1_abs = 0, uint32_t s0 = 0,
|
|
uint32_t s1 = 0) {
|
|
return (src0 & 0xffu) | ((dst_sel & 0x7u) << 8u) | ((dst_u & 0x3u) << 11u) |
|
|
((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((s0 & 0x1u) << 23u) | ((src1_sel & 0x7u) << 24u) |
|
|
((src1_sext & 0x1u) << 27u) | ((src1_neg & 0x1u) << 28u) |
|
|
((src1_abs & 0x1u) << 29u) | ((s1 & 0x1u) << 31u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop2Dpp(uint32_t src0, uint32_t dpp_ctrl = 0,
|
|
uint32_t row_mask = 0xf,
|
|
uint32_t bank_mask = 0xf,
|
|
uint32_t src0_neg = 0, uint32_t src0_abs = 0,
|
|
uint32_t src1_neg = 0, uint32_t src1_abs = 0) {
|
|
return (src0 & 0xffu) | ((dpp_ctrl & 0x1ffu) << 8u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((src1_neg & 0x1u) << 22u) | ((src1_abs & 0x1u) << 23u) |
|
|
((bank_mask & 0xfu) << 24u) | ((row_mask & 0xfu) << 28u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVopc(uint32_t opcode, uint32_t src0, uint32_t src1) {
|
|
return (0x3eu << 25u) | ((opcode & 0xffu) << 17u) | ((src1 & 0xffu) << 9u) |
|
|
(src0 & 0x1ffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVopcSdwa(uint32_t src0, uint32_t sdst = 0,
|
|
uint32_t sd = 0, uint32_t src0_sel = 6,
|
|
uint32_t src1_sel = 6, uint32_t src0_sext = 0,
|
|
uint32_t src1_sext = 0, uint32_t src0_neg = 0,
|
|
uint32_t src0_abs = 0, uint32_t src1_neg = 0,
|
|
uint32_t src1_abs = 0, uint32_t s0 = 0,
|
|
uint32_t s1 = 0) {
|
|
return (src0 & 0xffu) | ((sdst & 0x7fu) << 8u) | ((sd & 0x1u) << 15u) |
|
|
((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((s0 & 0x1u) << 23u) | ((src1_sel & 0x7u) << 24u) |
|
|
((src1_sext & 0x1u) << 27u) | ((src1_neg & 0x1u) << 28u) |
|
|
((src1_abs & 0x1u) << 29u) | ((s1 & 0x1u) << 31u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop3Word0(uint32_t opcode, uint32_t dst,
|
|
uint32_t op_sel = 0, uint32_t abs = 0,
|
|
bool clamp = false) {
|
|
return (0x35u << 26u) | ((opcode & 0x3ffu) << 16u) | (dst & 0xffu) |
|
|
((abs & 0x7u) << 8u) | ((op_sel & 0xfu) << 11u) |
|
|
(clamp ? (1u << 15u) : 0u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop3Word0Sdst(uint32_t opcode, uint32_t dst,
|
|
uint32_t sdst) {
|
|
return (0x35u << 26u) | ((opcode & 0x3ffu) << 16u) | ((sdst & 0x7fu) << 8u) |
|
|
(dst & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop3Word1(uint32_t src0, uint32_t src1,
|
|
uint32_t src2) {
|
|
return (src0 & 0x1ffu) | ((src1 & 0x1ffu) << 9u) | ((src2 & 0x1ffu) << 18u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop3pWord0(uint32_t opcode, uint32_t dst,
|
|
uint32_t op_sel = 0, uint32_t op_sel_hi = 0,
|
|
uint32_t neg_hi = 0, bool clamp = false) {
|
|
return (0x33u << 26u) | ((opcode & 0x7fu) << 16u) | (dst & 0xffu) |
|
|
((neg_hi & 0x7u) << 8u) | ((op_sel & 0x7u) << 11u) |
|
|
(((op_sel_hi >> 2u) & 0x1u) << 14u) | (clamp ? (1u << 15u) : 0u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVop3pWord1(uint32_t src0, uint32_t src1, uint32_t src2,
|
|
uint32_t op_sel_hi = 0, uint32_t neg = 0) {
|
|
return (src0 & 0x1ffu) | ((src1 & 0x1ffu) << 9u) | ((src2 & 0x1ffu) << 18u) |
|
|
((op_sel_hi & 0x3u) << 27u) | ((neg & 0x7u) << 29u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeSmem0(uint32_t opcode, uint32_t dst, uint32_t sbase) {
|
|
return (0x3du << 26u) | ((opcode & 0xffu) << 18u) | ((dst & 0x7fu) << 6u) |
|
|
(sbase & 0x3fu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeMubuf0(uint32_t opcode, uint32_t offset = 0,
|
|
bool idxen = true, bool glc = false) {
|
|
return (0x38u << 26u) | ((opcode & 0x7fu) << 18u) |
|
|
(idxen ? (1u << 13u) : 0u) | (glc ? (1u << 14u) : 0u) |
|
|
(offset & 0xfffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeMubuf1(uint32_t vdata, uint32_t srsrc, uint32_t vaddr,
|
|
uint32_t soffset = 128) {
|
|
return ((soffset & 0xffu) << 24u) | ((srsrc & 0x1fu) << 16u) |
|
|
((vdata & 0xffu) << 8u) | (vaddr & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeMtbuf0(uint32_t opcode, uint32_t dfmt, uint32_t nfmt,
|
|
uint32_t offset = 0, bool idxen = true,
|
|
bool offen = false) {
|
|
return (0x3au << 26u) | (offset & 0xfffu) | (offen ? (1u << 12u) : 0u) |
|
|
(idxen ? (1u << 13u) : 0u) | ((opcode & 0x7u) << 16u) |
|
|
((dfmt & 0xfu) << 19u) | ((nfmt & 0x7u) << 23u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeMtbuf1(uint32_t opcode, uint32_t vdata, uint32_t srsrc,
|
|
uint32_t vaddr, uint32_t soffset = 128) {
|
|
return ((opcode >> 3u) & 1u) << 21u | ((soffset & 0xffu) << 24u) |
|
|
((srsrc & 0x1fu) << 16u) | ((vdata & 0xffu) << 8u) | (vaddr & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeFlat0(uint32_t opcode, uint32_t seg,
|
|
uint32_t offset = 0) {
|
|
return (0x37u << 26u) | ((opcode & 0x7fu) << 18u) | ((seg & 0x3u) << 14u) |
|
|
(offset & 0xfffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeFlat1(uint32_t vdst, uint32_t saddr, uint32_t data,
|
|
uint32_t addr) {
|
|
return ((vdst & 0xffu) << 24u) | ((saddr & 0x7fu) << 16u) |
|
|
((data & 0xffu) << 8u) | (addr & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeDs0(uint32_t opcode, uint32_t offset = 0) {
|
|
return (0x36u << 26u) | ((opcode & 0xffu) << 18u) | (offset & 0xffffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeDs1Ex(uint32_t vdst, uint32_t data1, uint32_t data0,
|
|
uint32_t addr) {
|
|
return ((vdst & 0xffu) << 24u) | ((data1 & 0xffu) << 16u) |
|
|
((data0 & 0xffu) << 8u) | (addr & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeDs1(uint32_t vdst, uint32_t data0, uint32_t addr) {
|
|
return EncodeDs1Ex(vdst, 0, data0, addr);
|
|
}
|
|
|
|
constexpr uint32_t EncodeMimg0(uint32_t opcode, uint32_t dmask,
|
|
bool glc = false, uint32_t dim = 1) {
|
|
return (0x3cu << 26u) | ((opcode & 0x7fu) << 18u) | ((dmask & 0xfu) << 8u) |
|
|
((dim & 0x7u) << 3u) | (glc ? (1u << 13u) : 0u) |
|
|
((opcode >> 7u) & 1u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeMimg1(uint32_t vdata, uint32_t srsrc, uint32_t ssamp,
|
|
uint32_t vaddr, bool a16 = false) {
|
|
return ((ssamp & 0x1fu) << 21u) | ((srsrc & 0x1fu) << 16u) |
|
|
((vdata & 0xffu) << 8u) | (vaddr & 0xffu) | (a16 ? (1u << 30u) : 0u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeVintrp(uint32_t opcode, uint32_t vdst, uint32_t attr,
|
|
uint32_t chan, uint32_t vsrc) {
|
|
return (0x32u << 26u) | ((opcode & 0x3u) << 16u) | ((vdst & 0xffu) << 18u) |
|
|
((attr & 0x3fu) << 10u) | ((chan & 0x3u) << 8u) | (vsrc & 0xffu);
|
|
}
|
|
|
|
constexpr uint32_t EncodeExp0(uint32_t target, uint32_t en, bool done = true,
|
|
bool compr = false, bool vm = false) {
|
|
return (0x3eu << 26u) | ((target & 0x3fu) << 4u) | (en & 0xfu) |
|
|
(compr ? (1u << 10u) : 0u) | (done ? (1u << 11u) : 0u) |
|
|
(vm ? (1u << 12u) : 0u);
|
|
}
|
|
|
|
constexpr uint32_t EncodeExp1(uint32_t src0, uint32_t src1, uint32_t src2,
|
|
uint32_t src3) {
|
|
return (src0 & 0xffu) | ((src1 & 0xffu) << 8u) | ((src2 & 0xffu) << 16u) |
|
|
((src3 & 0xffu) << 24u);
|
|
}
|
|
|
|
void TestNewShaderRecompilerSMovB32() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 129), // s_mov_b32 s0, 1
|
|
EncodeSMovB32(1, 255), // s_mov_b32 s1, 0x12345678
|
|
0x12345678u,
|
|
EncodeSMovB32(2, 1), // s_mov_b32 s2, s1
|
|
EncodeVop1(0x01, 0, 0),
|
|
EncodeMubuf0(0x1c, 0, false),
|
|
EncodeMubuf1(0, 12, 0),
|
|
EncodeVop1(0x01, 1, 1),
|
|
EncodeMubuf0(0x1c, 4, false),
|
|
EncodeMubuf1(1, 12, 0),
|
|
EncodeVop1(0x01, 2, 2),
|
|
EncodeMubuf0(0x1c, 8, false),
|
|
EncodeMubuf1(2, 12, 0),
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.spirv.empty(), "new shader recompiler produced no SPIR-V");
|
|
Check(result.spirv.front() == 0x07230203u,
|
|
"new shader recompiler did not emit SPIR-V binary");
|
|
Check((result.decoded_dump.find("S_MOV_B32 s0, 1") != std::string::npos),
|
|
"new decoder did not decode inline S_MOV_B32 operand");
|
|
Check((result.decoded_dump.find("S_MOV_B32 s1, 0x12345678") != std::string::npos),
|
|
"new decoder did not decode literal S_MOV_B32 operand");
|
|
Check((result.decoded_dump.find("S_MOV_B32 s2, s1") != std::string::npos),
|
|
"new decoder did not decode register S_MOV_B32 operand");
|
|
Check((result.ir_dump.find("StoreBufferU32") != std::string::npos) &&
|
|
(result.ir_dump.find("0x00000001") != std::string::npos) &&
|
|
(result.ir_dump.find("0x12345678") != std::string::npos),
|
|
"typed SSA did not preserve live S_MOV_B32 values");
|
|
Check((result.ir_dump.find("SetScalarRegister") == std::string::npos) &&
|
|
(result.ir_dump.find("GetScalarRegister") == std::string::npos),
|
|
"typed SSA retained register-state pseudo operations");
|
|
Check(std::find(result.spirv.begin(), result.spirv.end(), 0x12345678u) !=
|
|
result.spirv.end(),
|
|
"new SPIR-V emitter did not encode the literal as a binary word");
|
|
Check(SpirvContainsCapability(result.spirv, 4466),
|
|
"SPIR-V binary does not request signed-zero/Inf/NaN preservation");
|
|
Check(SpirvContainsExecutionMode(result.spirv, 4461),
|
|
"SPIR-V binary does not enable signed-zero/Inf/NaN preservation");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestShaderStageBarriers() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x0a, 0), // s_barrier
|
|
EncodeSopp(0x01, 0), // s_endpgm
|
|
};
|
|
|
|
const auto result =
|
|
RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
|
Check(SpirvContainsOpcode(result.spirv, 224),
|
|
"SPIR-V binary does not contain OpControlBarrier");
|
|
Check(
|
|
std::find(result.spirv.begin(), result.spirv.end(), 264u) !=
|
|
result.spirv.end(),
|
|
"SPIR-V barrier does not use workgroup acquire-release memory semantics");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto vertex_result =
|
|
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
|
|
CheckSpirvBinaryValidates(vertex_result.spirv);
|
|
Check(!SpirvContainsOpcode(vertex_result.spirv, 224),
|
|
"independent vertex invocations retained a workgroup barrier");
|
|
}
|
|
|
|
void TestVertexBufferGrouping() {
|
|
const uint32_t shader[] = {EncodeSopp(0x01)};
|
|
std::array<uint16_t, static_cast<size_t>(AgcDirectResourceType::Last) + 1> offsets;
|
|
offsets.fill(AGC_ILLEGAL_DIRECT_OFFSET);
|
|
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 0;
|
|
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexAttribDescTable)] = 2;
|
|
ShaderUserData user_data{};
|
|
user_data.direct_resource_count = offsets.size();
|
|
user_data.direct_resource_offset = offsets.data();
|
|
std::array<ShaderSemantic, 4> semantics{};
|
|
for (uint32_t i = 0; i < semantics.size(); i++) {
|
|
semantics[i].semantic = i;
|
|
semantics[i].hardware_mapping = i;
|
|
semantics[i].size_in_elements = 1;
|
|
}
|
|
ShaderMappedData mapped{};
|
|
mapped.user_data = &user_data;
|
|
mapped.code_size_bytes = sizeof(shader);
|
|
mapped.input_semantics = semantics.data();
|
|
mapped.num_input_semantics = semantics.size();
|
|
HW::VertexShaderInfo regs{};
|
|
regs.es_regs.data_addr = reinterpret_cast<uint64_t>(shader);
|
|
regs.gs_regs.rsrc2.user_sgpr = 4;
|
|
ShaderMapUserData(regs.es_regs.data_addr, mapped);
|
|
std::array<ShaderBufferResource, 4> descriptors{};
|
|
const std::array<uint32_t, 4> attributes{0, 1u | (1u << 26u), 2, 3};
|
|
const std::array<uint64_t, 2> pointers{
|
|
reinterpret_cast<uint64_t>(descriptors.data()),
|
|
reinterpret_cast<uint64_t>(attributes.data())};
|
|
std::memcpy(regs.gs_user_sgpr.value, pointers.data(), sizeof(pointers));
|
|
ShaderVertexInputInfo input{};
|
|
for (const uint64_t base : {0x1000u, 0x3000u}) {
|
|
for (auto &descriptor : descriptors) {
|
|
descriptor.fields[1] = 16u << 16u;
|
|
descriptor.fields[2] = 8;
|
|
descriptor.UpdateAddress48(base);
|
|
}
|
|
descriptors[0].UpdateAddress48(base + 8);
|
|
descriptors[1].UpdateAddress48(base + 4);
|
|
descriptors[3].fields[1] = 0; // Constant attributes use their own buffer.
|
|
(void)PrepareProgram(regs, HW::Context{}, HW::UserConfig{}, input);
|
|
Check(input.resources_num == 4 && input.buffers_num == 3 &&
|
|
input.resources_dst[0].buffer_index == 0 &&
|
|
input.resources_dst[1].buffer_index == 1 &&
|
|
input.resources_dst[2].buffer_index == 0 &&
|
|
input.resources_dst[3].buffer_index == 2,
|
|
"interleaved vertex attributes lost their stride or instance-rate binding");
|
|
Check(input.buffers[0].addr == base && input.buffers[1].addr == base + 4 &&
|
|
input.buffers[1].fetch_index == 1 && input.buffers[2].stride == 0 &&
|
|
input.resources[0].Base48() - input.buffers[0].addr == 8 &&
|
|
input.resources[2].Base48() == input.buffers[0].addr,
|
|
"a later lower-address attribute or changed table left stale vertex offsets");
|
|
}
|
|
}
|
|
|
|
void TestNggVertexEntryState() {
|
|
using namespace ShaderRecompiler;
|
|
// PPSA03309 shader 5a39eb2021a5d2c1: retain its NGG prologue and replace
|
|
// the resource-dependent vertex body with a position export.
|
|
const uint32_t shader[] = {
|
|
0xbfa00003u, 0x93ebff03u, 0x00040018u, 0xbefe03c1u,
|
|
0x9380ff02u, 0x00090016u, 0x9381ff02u, 0x0009000cu,
|
|
0xbf8a0000u, 0xbf076b80u, 0xbf850003u, 0x8f6a8c00u,
|
|
0x887c6a01u, 0xbf900009u, 0xd7650001u, 0x000100c1u,
|
|
0xd7460001u, 0x04050a6bu, 0x7da80200u, 0xbf880002u,
|
|
0xf8000941u, 0x00000000u, 0xbf8cff0fu, 0xbefe03c1u,
|
|
0x7da80201u, EncodeSopp(0x08, 2),
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(5, 5, 5, 5), EncodeSopp(0x01),
|
|
};
|
|
HW::VertexShaderInfo regs{};
|
|
regs.es_regs.data_addr = reinterpret_cast<uint64_t>(shader);
|
|
ShaderUserData user_data{};
|
|
ShaderMappedData mapped{};
|
|
mapped.user_data = &user_data;
|
|
mapped.code_size_bytes = sizeof(shader);
|
|
ShaderMapUserData(regs.es_regs.data_addr, mapped);
|
|
std::vector<uint32_t> previous_key;
|
|
for (const uint32_t wave_size : {32u, 64u}) {
|
|
HW::Context context;
|
|
context.SetShaderStages(wave_size == 32u ? 0x00400000u : 0u);
|
|
ShaderVertexInputInfo input{};
|
|
const auto params = PrepareProgram(regs, context, HW::UserConfig{}, input);
|
|
Check(input.wave_size == wave_size,
|
|
"vertex preparation lost the native NGG wave size");
|
|
const auto key = MakeStageStaticKey(input);
|
|
Check(previous_key != key, "NGG wave sizes shared a shader cache key");
|
|
previous_key = key;
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.user_data_base = 8;
|
|
options.user_data = std::span(params.user_data).first(params.user_data_count);
|
|
options.input_info.vertex = &input;
|
|
options.wave_size = input.wave_size;
|
|
const auto result = RecompileForTest(params.code, options);
|
|
bool live_vertex_range = false;
|
|
for (const auto *block : result.program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() == IR::ValueOpcode::UGreaterThan32) {
|
|
const auto count = inst.Arg(0).Resolve();
|
|
live_vertex_range |= count.IsImmediate() && count.U32() == wave_size;
|
|
}
|
|
}
|
|
}
|
|
Check(live_vertex_range && std::ranges::any_of(result.program.info.outputs,
|
|
[](const auto &output) {
|
|
return output.kind == IR::StageOutputKind::Position;
|
|
}),
|
|
"NGG launch counts suppressed the vertex export");
|
|
Check(result.program.wave_size == wave_size,
|
|
"NGG wave size was lost during translation");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestNativeStoreCompletion() {
|
|
using namespace ShaderRecompiler;
|
|
const std::array register_wait = {EncodeSopk(0x17, 4, static_cast<int16_t>(0x9234)), EncodeSopp(0x01)};
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(register_wait, decoded);
|
|
const auto &wait = decoded.instructions.front();
|
|
Check(wait.opcode == Decoder::Opcode::S_WAITCNT_VSCNT &&
|
|
wait.src0.kind == Decoder::OperandKind::Sgpr && wait.src0.reg == 4 &&
|
|
wait.src1.value == 0x9234u,
|
|
"store completion lost its counter kind, source, or unsigned immediate");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
ShaderComputeInputInfo compute{};
|
|
compute.threads_num[1] = compute.threads_num[2] = 1;
|
|
options.input_info.compute = &compute;
|
|
for (const uint32_t wave_size : {32u, 64u}) {
|
|
compute.threads_num[0] = compute.wave_size = options.wave_size = wave_size;
|
|
const std::array barrier_only = {0xbbfd0000u, EncodeSopp(0x01)};
|
|
const auto minimal = RecompileForTest(barrier_only, options);
|
|
CheckSpirvBinaryValidates(minimal.spirv);
|
|
Check(SpirvContainsCapability(minimal.spirv, 61),
|
|
"standalone store completion lacks GroupNonUniform capability");
|
|
|
|
// Captured 5b10:0x2464 drains node stores before the next parent atomic.
|
|
const std::array publication = {
|
|
EncodeVop1(0x01, 1, 129),
|
|
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(1, 0, 0),
|
|
0xbbfd0000u,
|
|
EncodeMubuf0(0x32, 4, false, true), EncodeMubuf1(1, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto result = RecompileForTest(publication, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto store = result.ir_dump.find("StoreBufferU32");
|
|
const auto completion = result.ir_dump.find("StoreCompletion");
|
|
const auto atomic = result.ir_dump.find("BufferAtomicIAdd32");
|
|
Check(store < completion && completion < atomic && atomic != std::string::npos,
|
|
"store completion moved outside its native publication order");
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
const auto barrier = source.find("OpControlBarrier %uint_3 %uint_1 %uint_2116");
|
|
Check(barrier != std::string::npos &&
|
|
source.find("OpStore") < barrier &&
|
|
barrier < source.find("OpAtomicIAdd") &&
|
|
source.find("OpControlBarrier", barrier + 1) == std::string::npos,
|
|
"store completion must synchronize the subgroup once after both wave halves");
|
|
}
|
|
|
|
const std::array no_store_wait = {
|
|
EncodeSopk(0x17, 125, 0xffff), // Effective VSCNT 63 waits for nothing.
|
|
EncodeSopk(0x18, 125, 0), EncodeSopk(0x19, 125, 0),
|
|
EncodeSopk(0x1a, 125, 0), EncodeSopp(0x01),
|
|
};
|
|
const auto no_wait = RecompileForTest(no_store_wait, options);
|
|
const auto source = DisassembleSpirvBinary(no_wait.spirv);
|
|
Check(source.find("OpControlBarrier") == std::string::npos &&
|
|
source.find("OpMemoryBarrier") == std::string::npos,
|
|
"other wait counters or VSCNT 63 became a store completion barrier");
|
|
#ifndef _WIN32
|
|
for (const uint32_t word : {EncodeSopk(0x17, 125, 1), register_wait[0]}) {
|
|
const std::array unsupported = {word, EncodeSopp(0x01)};
|
|
ExpectFatal([&] { (void)RecompileForTest(unsupported, options); },
|
|
"unsupported partial or register-based store wait was dropped");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void TestSopkCompareImmediateExtension() {
|
|
using namespace ShaderRecompiler;
|
|
for (uint32_t opcode = 0x03; opcode <= 0x0e; opcode++) {
|
|
for (uint32_t immediate : {0x8000u, 0xffffu}) {
|
|
const std::array shader = {
|
|
EncodeSopk(opcode, 2, static_cast<int16_t>(immediate)),
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(shader, decoded);
|
|
const auto &operand = decoded.instructions.front().src1;
|
|
int32_t expected = static_cast<int32_t>(immediate);
|
|
if (opcode < 0x09) {
|
|
expected = immediate == 0x8000u ? -32768 : -1;
|
|
}
|
|
Check(operand.value == static_cast<uint32_t>(expected) &&
|
|
operand.signed_val == expected,
|
|
"SOPK compare immediate extension does not match its signedness");
|
|
}
|
|
}
|
|
|
|
// GTA3's LUT GS exports its UV parameter only when packed primitive count > 0.
|
|
for (uint32_t counts : {0x00000006u, 0x00020006u}) {
|
|
const std::array shader = {
|
|
EncodeSMovB32(2, 255), counts,
|
|
0xb582ffffu, // s_cmpk_gt_u32 s2, 0xffff
|
|
EncodeSopp(0x04, 2), // s_cbranch_scc0 past exp param0
|
|
0xf800020fu, 0x06060504u, // exp param0 v4, v5, v6, v6
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto translated =
|
|
TranslateProgram(shader, MakeCompileOptions(ShaderType::Vertex));
|
|
const auto branch = std::ranges::find_if(
|
|
translated.program.block_info, [](const auto &block) {
|
|
return block.terminator.kind == CFG::TerminatorKind::ConditionalBranch;
|
|
});
|
|
Check(branch != translated.program.block_info.end(),
|
|
"captured LUT parameter export branch was lost");
|
|
const auto condition = branch->condition.Resolve();
|
|
Check(condition.IsImmediate() && condition.U1() == (counts != 6u),
|
|
"captured LUT unsigned compare suppressed a live parameter export");
|
|
}
|
|
}
|
|
|
|
void TestDisabledDebugBranches() {
|
|
using namespace ShaderRecompiler;
|
|
// Both debug branches fall through when conditional shader debugging is disabled.
|
|
// Retain the post-ENDPGM helper displacement from PPSA08709 and exercise the
|
|
// OR_USER variant encountered in PPSA03671 with the same body.
|
|
for (const auto opcode : {Decoder::Opcode::S_CBRANCH_CDBGSYS,
|
|
Decoder::Opcode::S_CBRANCH_CDBGSYS_OR_USER}) {
|
|
std::array<uint32_t, 274> shader;
|
|
shader.fill(EncodeSopp(0x00));
|
|
shader[0] = EncodeSopp(opcode == Decoder::Opcode::S_CBRANCH_CDBGSYS ? 0x17 : 0x19, 0x10f);
|
|
shader[1] = EncodeVop1(0x01, 1, 129);
|
|
shader[2] = EncodeMubuf0(0x1c, 0, false);
|
|
shader[3] = EncodeMubuf1(1, 0, 0);
|
|
shader[271] = EncodeSopp(0x01);
|
|
shader[272] = 0xffffffffu; // Debug-only helper must not be decoded.
|
|
shader[273] = 0xffffffffu;
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(shader, decoded);
|
|
Check(decoded.instructions.front().opcode == opcode &&
|
|
decoded.instructions.front().branch_target == 0x440u &&
|
|
decoded.instructions.back().pc == 0x43cu &&
|
|
decoded.instructions.back().opcode == Decoder::Opcode::S_ENDPGM,
|
|
"disabled debug branch reached its post-ENDPGM validation helper");
|
|
auto graph = CFG::BuildGraph(decoded);
|
|
Check(graph.blocks.size() == 1u && graph.FindBlockByPc(0x440u) == nullptr &&
|
|
graph.blocks.front().terminator.kind == CFG::TerminatorKind::Return,
|
|
"disabled debug branch changed normal shader control flow");
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find(" 0x00000440") != std::string::npos) &&
|
|
(result.ir_dump.find("StoreBufferU32") != std::string::npos),
|
|
"disabled debug branch lost its identity or fallthrough write");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerRdna2ScalarOpcodes() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(2, 135), // s2 = 7
|
|
EncodeSMovB32(106, 144), // vcc_lo = 16
|
|
EncodeSop1(0x1d, 106, 128), // s_bitset1_b32 vcc_lo, 0
|
|
EncodeSopk(0x13, 106, 0x1019), // s_setreg_b32 vcc_lo, 0x1019
|
|
EncodeSop2(0x02, 106, 2,
|
|
239), // s_add_i32 vcc_lo, s2, pops_exiting_wave_id
|
|
EncodeSopp(0x0e, 0), // s_sleep 0
|
|
EncodeSop2(0x02, 106, 239,
|
|
2), // s_add_i32 vcc_lo, pops_exiting_wave_id, s2
|
|
EncodeSopp(0x01, 0), // s_endpgm
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_bitset1_b32 vcc_lo, 0") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_BITSET1_B32");
|
|
Check((result.decoded_dump.find("s_setreg_b32 vcc_lo, 0x00001019") != std::string::npos),
|
|
"new decoder did not decode S_SETREG_B32");
|
|
Check((result.decoded_dump.find("s_add_i32 vcc_lo, s2, pops_exiting_wave_id") != std::string::npos),
|
|
"new decoder did not decode pops_exiting_wave_id as RHS scalar source");
|
|
Check((result.decoded_dump.find("s_add_i32 vcc_lo, pops_exiting_wave_id, s2") != std::string::npos),
|
|
"new decoder did not decode pops_exiting_wave_id as LHS scalar source");
|
|
Check((result.decoded_dump.find("s_sleep 0x00000000") != std::string::npos),
|
|
"new decoder did not decode S_SLEEP");
|
|
Check((result.ir_dump.find("BitSetU32 vcc_lo, vcc_lo, 0x00000000") != std::string::npos),
|
|
"S_BITSET1_B32 did not lower to bit-set IR using the destination as "
|
|
"input");
|
|
Check(
|
|
(result.ir_dump.find("ScalarSignedAddOverflowI32 vcc_lo, s2, 0x00000000") != std::string::npos),
|
|
"pops_exiting_wave_id RHS did not lower to a deterministic zero value");
|
|
Check(
|
|
(result.ir_dump.find("ScalarSignedAddOverflowI32 vcc_lo, 0x00000000, s2") != std::string::npos),
|
|
"pops_exiting_wave_id LHS did not lower to a deterministic zero value");
|
|
Check((result.ir_dump.find("ControlNop null, vcc_lo") != std::string::npos),
|
|
"S_SETREG_B32 did not lower to an explicit control marker");
|
|
Check((result.ir_dump.find("ControlNop null, 0x00000000") != std::string::npos),
|
|
"S_SLEEP did not lower to an explicit control marker");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical for S_BITSET1_B32");
|
|
Check(SpirvContainsOpcode(result.spirv, 197),
|
|
"SPIR-V binary does not contain OpBitwiseOr for S_BITSET1_B32");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerScalarVectorAlu() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 129), // s0 = 1
|
|
EncodeSMovB32(1, 130), // s1 = 2
|
|
EncodeSop2(0x00, 2, 0, 1), // s_add_u32 s2, s0, s1
|
|
EncodeSop2(0x01, 3, 2, 129), // s_sub_u32 s3, s2, 1
|
|
EncodeSop2(0x0e, 4, 2, 3), // s_and_b32 s4, s2, s3
|
|
EncodeSop2(0x10, 5, 2, 3), // s_or_b32 s5, s2, s3
|
|
EncodeSop2(0x12, 6, 2, 3), // s_xor_b32 s6, s2, s3
|
|
EncodeSop2(0x1e, 7, 2, 129), // s_lshl_b32 s7, s2, 1
|
|
EncodeSop2(0x20, 8, 7, 129), // s_lshr_b32 s8, s7, 1
|
|
EncodeSop2(0x2e, 9, 2, 1), // s_lshl1_add_u32 s9, s2, s1
|
|
EncodeSop2(0x2f, 10, 9, 1), // s_lshl2_add_u32 s10, s9, s1
|
|
EncodeSop2(0x30, 11, 10, 1), // s_lshl3_add_u32 s11, s10, s1
|
|
EncodeSop2(0x31, 12, 11, 1), // s_lshl4_add_u32 s12, s11, s1
|
|
EncodeSop2(0x35, 13, 12, 1), // s_mul_hi_u32 s13, s12, s1
|
|
EncodeSop2(0x36, 15, 12, 1), // s_mul_hi_i32 s15, s12, s1
|
|
EncodeSopc(0x08, 8, 1), // s_cmp_gt_u32 s8, s1
|
|
EncodeSop2(0x04, 14, 13, 129), // s_addc_u32 s14, s13, 1
|
|
EncodeVop2(0x03, 1, 242, 0), // v_add_f32 v1, 1.0, v0
|
|
EncodeVop2(0x08, 2, 1 + 256, 1), // v_mul_f32 v2, v1, v1
|
|
EncodeVop2(0x25, 3, 1 + 256, 2), // v_add_nc_u32 v3, v1, v2
|
|
EncodeVop2(0x1b, 4, 3 + 256, 2), // v_and_b32 v4, v3, v2
|
|
EncodeVop2(0x0b, 6, 3 + 256, 4), // v_mul_u32_u24 v6, v3, v4
|
|
EncodeVop2(0x09, 8, 4 + 256, 6), // v_mul_i32_i24 v8, v4, v6
|
|
EncodeVop2(0x22, 7, 4 + 256, 6), // v_bcnt_u32_b32 v7, v4, v6
|
|
EncodeVopc(0xc4, 3 + 256, 2), // v_cmp_gt_u32 v3, v2
|
|
EncodeVop2(0x01, 5, 1 + 256, 2), // v_cndmask_b32 v5, v1, v2
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_add_u32 s2, s0, s1") != std::string::npos),
|
|
"new decoder did not decode SOP2 add");
|
|
Check((result.decoded_dump.find("s_addc_u32 s14, s13, 1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_ADD_C_U32");
|
|
Check((result.decoded_dump.find("s_cmp_gt_u32 s8, s1") != std::string::npos),
|
|
"new decoder did not decode SOPC compare");
|
|
Check((result.decoded_dump.find("s_lshl1_add_u32 s9, s2, s1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_LSHL1_ADD_U32");
|
|
Check((result.decoded_dump.find("s_lshl2_add_u32 s10, s9, s1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_LSHL2_ADD_U32");
|
|
Check(
|
|
(result.decoded_dump.find("s_lshl3_add_u32 s11, s10, s1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_LSHL3_ADD_U32");
|
|
Check(
|
|
(result.decoded_dump.find("s_lshl4_add_u32 s12, s11, s1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_LSHL4_ADD_U32");
|
|
Check((result.decoded_dump.find("s_mul_hi_u32 s13, s12, s1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_MUL_HI_U32");
|
|
Check((result.decoded_dump.find("s_mul_hi_i32 s15, s12, s1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_MUL_HI_I32");
|
|
Check((result.decoded_dump.find("v_add_f32 v1") != std::string::npos),
|
|
"new decoder did not decode VOP2 float add");
|
|
Check((result.decoded_dump.find("v_cndmask_b32 v5") != std::string::npos),
|
|
"new decoder did not decode VOP2 conditional mask select");
|
|
Check((result.decoded_dump.find("v_mul_u32_u24 v6") != std::string::npos),
|
|
"new decoder did not decode VOP2 24-bit multiply");
|
|
Check((result.decoded_dump.find("v_mul_i32_i24 v8") != std::string::npos),
|
|
"new decoder did not decode VOP2 signed 24-bit multiply");
|
|
Check((result.decoded_dump.find("v_bcnt_u32_b32 v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_BCNT_U32_B32");
|
|
Check((result.ir_dump.find("ScalarAddCarryU32 s2, s0, s1, 0x00000000") != std::string::npos),
|
|
"SOP2 add did not lower to scalar carry-writing IR");
|
|
Check((result.ir_dump.find("ScalarAddCarryU32 s14, s13, 0x00000001, scc") != std::string::npos),
|
|
"S_ADD_C_U32 did not lower to scalar carry IR");
|
|
Check(
|
|
(result.ir_dump.find("ScalarShiftLeftAddCarryU32 s9, s2, 0x00000001, s1") != std::string::npos),
|
|
"S_LSHL1_ADD_U32 did not lower through carry-writing shift-left-add IR");
|
|
Check(
|
|
(result.ir_dump.find("ScalarShiftLeftAddCarryU32 s10, s9, 0x00000002, s1") != std::string::npos),
|
|
"S_LSHL2_ADD_U32 did not lower through carry-writing shift-left-add IR");
|
|
Check(
|
|
(result.ir_dump.find("ScalarShiftLeftAddCarryU32 s11, s10, 0x00000003, s1") != std::string::npos),
|
|
"S_LSHL3_ADD_U32 did not lower through carry-writing shift-left-add IR");
|
|
Check(
|
|
(result.ir_dump.find("ScalarShiftLeftAddCarryU32 s12, s11, 0x00000004, s1") != std::string::npos),
|
|
"S_LSHL4_ADD_U32 did not lower through carry-writing shift-left-add IR");
|
|
Check((result.ir_dump.find("UMulHighU32 s13, s12, s1") != std::string::npos),
|
|
"S_MUL_HI_U32 did not lower to unsigned high-multiply IR");
|
|
Check((result.ir_dump.find("SMulHighI32 s15, s12, s1") != std::string::npos),
|
|
"S_MUL_HI_I32 did not lower to signed high-multiply IR");
|
|
Check((result.ir_dump.find("CompareGtU32") != std::string::npos),
|
|
"SOPC compare did not lower to IR");
|
|
Check((result.ir_dump.find("FAddF32 v1") != std::string::npos),
|
|
"VOP2 float add did not lower to IR");
|
|
Check((result.ir_dump.find("BitwiseAndU32 v4") != std::string::npos),
|
|
"VOP2 bitwise op did not lower to IR");
|
|
Check((result.ir_dump.find("SelectMaskU32 v5, vcc_lo, v2, v1") != std::string::npos),
|
|
"VOP2 conditional mask did not lower through lane-mask select IR");
|
|
Check((result.ir_dump.find("UMulU24U32 v6") != std::string::npos),
|
|
"VOP2 24-bit multiply did not lower to IR");
|
|
Check((result.ir_dump.find("IMulI24U32 v8") != std::string::npos),
|
|
"VOP2 signed 24-bit multiply did not lower to IR");
|
|
Check((result.ir_dump.find("BitCountAddU32 v7, v4, v6") != std::string::npos),
|
|
"V_BCNT_U32_B32 did not lower to bit-count-add IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 128),
|
|
"SPIR-V binary does not contain OpIAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 149),
|
|
"SPIR-V binary does not contain OpIAddCarry");
|
|
Check(SpirvContainsOpcode(result.spirv, 129),
|
|
"SPIR-V binary does not contain OpFAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 132),
|
|
"SPIR-V binary does not contain OpIMul");
|
|
Check(SpirvContainsOpcode(result.spirv, 151),
|
|
"SPIR-V binary does not contain OpUMulExtended");
|
|
Check(SpirvContainsOpcode(result.spirv, 205),
|
|
"SPIR-V binary does not contain OpBitCount");
|
|
Check(SpirvContainsOpcode(result.spirv, 169),
|
|
"SPIR-V binary does not contain OpSelect");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerVop3LaneReadDestinationEncoding() {
|
|
// Lane-read instructions have scalar results, but their VOP3A destination is
|
|
// encoded in VDST [7:0], not the VOP3B SDST field [14:8].
|
|
const uint32_t shader[] = {
|
|
EncodeVop3Word0(0x182, 25),
|
|
EncodeVop3Word1(5 + 256, 0, 0), // v_readfirstlane_b32 s25, v5
|
|
EncodeVop3Word0(0x360, 26),
|
|
EncodeVop3Word1(5 + 256, 130, 0), // v_readlane_b32 s26, v5, 2
|
|
EncodeSopp(0x01, 0),
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("V_READFIRSTLANE_B32 s25, v5") != std::string::npos),
|
|
"VOP3 V_READFIRSTLANE_B32 destination was not decoded from VDST");
|
|
Check((result.decoded_dump.find("V_READLANE_B32 s26, v5, 2") != std::string::npos),
|
|
"VOP3 V_READLANE_B32 destination was not decoded from VDST");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerMoreAluFamilies() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7
|
|
EncodeSopk(0x0b, 9, 3), // s_cmp_gt_u32 s9, 3
|
|
EncodeVop1(0x00, 0, 0), // v_nop
|
|
EncodeVop1(0x01, 5, 9), // v_mov_b32 v5, s9
|
|
EncodeVop1(0x01, 103, 249),
|
|
EncodeVop1Sdwa(5, 6, 0, 4), // v_mov_b32 v103, v5 word0
|
|
EncodeVop1(0x06, 104, 249),
|
|
EncodeVop1Sdwa(103, 6, 0, 4), // v_cvt_f32_u32 v104, v103 word0
|
|
EncodeVop1(0x0a, 105, 249),
|
|
EncodeVop1Sdwa(6, 5, 2, 6), // v_cvt_f16_f32 v105.hi, v6
|
|
EncodeVop1(0x01, 106, 250),
|
|
EncodeVop1Dpp(5), // v_mov_b32 v106, v5 dpp
|
|
EncodeVop1(0x02, 24, 5 + 256), // v_readfirstlane_b32 s24, v5
|
|
EncodeVop1(0x06, 6, 5 + 256), // v_cvt_f32_u32 v6, v5
|
|
EncodeVop1(0x07, 7, 6 + 256), // v_cvt_u32_f32 v7, v6
|
|
EncodeVop1(0x05, 9, 5 + 256), // v_cvt_f32_i32 v9, v5
|
|
EncodeVop1(0x08, 10, 6 + 256), // v_cvt_i32_f32 v10, v6
|
|
EncodeVop1(0x0a, 99, 6 + 256), // v_cvt_f16_f32 v99, v6
|
|
EncodeVop1(0x0b, 100, 99 + 256), // v_cvt_f32_f16 v100, v99
|
|
0x7e1016f9u,
|
|
0x00250602u, // v_cvt_f32_f16 v8, abs(v2.hi)
|
|
EncodeVop1(0x0d, 64, 6 + 256), // v_cvt_flr_i32_f32 v64, v6
|
|
EncodeVop1(0x0e, 81, 5 + 256), // v_cvt_off_f32_i4 v81, v5
|
|
EncodeVop1(0x11, 65, 5 + 256), // v_cvt_f32_ubyte0 v65, v5
|
|
EncodeVop1(0x12, 66, 5 + 256), // v_cvt_f32_ubyte1 v66, v5
|
|
EncodeVop1(0x13, 67, 5 + 256), // v_cvt_f32_ubyte2 v67, v5
|
|
EncodeVop1(0x14, 68, 5 + 256), // v_cvt_f32_ubyte3 v68, v5
|
|
EncodeVop1(0x2a, 11, 6 + 256), // v_rcp_f32 v11, v6
|
|
EncodeVop1(0x20, 12, 6 + 256), // v_fract_f32 v12, v6
|
|
EncodeVop1(0x21, 13, 6 + 256), // v_trunc_f32 v13, v6
|
|
EncodeVop1(0x22, 14, 6 + 256), // v_ceil_f32 v14, v6
|
|
EncodeVop1(0x23, 15, 6 + 256), // v_rndne_f32 v15, v6
|
|
EncodeVop1(0x24, 16, 6 + 256), // v_floor_f32 v16, v6
|
|
EncodeVop1(0x25, 17, 6 + 256), // v_exp_f32 v17, v6
|
|
EncodeVop1(0x27, 18, 6 + 256), // v_log_f32 v18, v6
|
|
EncodeVop1(0x2e, 19, 6 + 256), // v_rsq_f32 v19, v6
|
|
EncodeVop1(0x33, 20, 6 + 256), // v_sqrt_f32 v20, v6
|
|
EncodeVop1(0x35, 21, 6 + 256), // v_sin_f32 v21, v6
|
|
EncodeVop1(0x36, 22, 6 + 256), // v_cos_f32 v22, v6
|
|
EncodeVop1(0x37, 27, 5 + 256), // v_not_b32 v27, v5
|
|
EncodeVop1(0x38, 28, 5 + 256), // v_bfrev_b32 v28, v5
|
|
EncodeVop1(0x39, 31, 5 + 256), // v_ffbh_u32 v31, v5
|
|
EncodeVop1(0x3a, 32, 5 + 256), // v_ffbl_b32 v32, v5
|
|
0x7e6e870cu, // v_movrels_b32 v55, v12
|
|
EncodeVop2(0x1e, 83, 5 + 256, 6), // v_xnor_b32 v83, v5, v6
|
|
EncodeVop2(0x23, 84, 5 + 256, 6), // v_mbcnt_lo_u32_b32 v84, v5, v6
|
|
EncodeVop2(0x24, 85, 5 + 256, 6), // v_mbcnt_hi_u32_b32 v85, v5, v6
|
|
EncodeVop2(0x1f, 89, 6 + 256, 6), // v_mac_f32 v89, v6, v6
|
|
EncodeVop2(0x20, 90, 6 + 256, 5),
|
|
0x3f800000u, // v_madmk_f32 v90, v6, 1.0, v5
|
|
EncodeVop2(0x21, 91, 6 + 256, 5),
|
|
0x40000000u, // v_madak_f32 v91, v6, v5, 2.0
|
|
EncodeVop2(0x2b, 92, 6 + 256, 6), // v_mac_f32 v92, v6, v6
|
|
EncodeVop2(0x2c, 93, 6 + 256, 5),
|
|
0x3f000000u, // v_madmk_f32 v93, v6, 0.5, v5
|
|
EncodeVop2(0x2d, 94, 6 + 256, 5),
|
|
0x40400000u, // v_madak_f32 v94, v6, v5, 3.0
|
|
EncodeVop2(0x2f, 95, 6 + 256, 6), // v_cvt_pkrtz_f16_f32 v95, v6, v6
|
|
EncodeVop2(0x02, 102, 95 + 256, 95), // v_dot2c_f32_f16 v102, v95, v95
|
|
EncodeVop2(0x28, 97, 5 + 256, 6), // v_addc_u32 v97, vcc, v5, v6, vcc
|
|
EncodeVop2(0x25, 123, 249, 6),
|
|
EncodeVop2Sdwa(5, 6, 0, 4, 6),
|
|
EncodeVop2(0x1b, 124, 249, 6),
|
|
EncodeVop2Sdwa(5, 6, 0, 4, 5),
|
|
0x025e6af9u,
|
|
0x16060635u, // v_cndmask_b32 v47, v53, -v53
|
|
0x020e02f9u,
|
|
0x06040600u, // v_cndmask_b32 v7, v0.lo, v1; full-width destination
|
|
0x100490f9u,
|
|
0x86860600u, // v_mul_f32 v2, s0, s72 (SDWA full)
|
|
EncodeVop2(0x35, 9, 249, 1),
|
|
EncodeVop2Sdwa(0, 4, 2, 4, 4),
|
|
0x660000f9u,
|
|
EncodeVop2Sdwa(0, 4, 2, 4, 4),
|
|
EncodeVop2(0x03, 125, 250, 6),
|
|
EncodeVop2Dpp(5),
|
|
EncodeVop3Word0(0x103, 35),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x10b, 36),
|
|
EncodeVop3Word1(5 + 256, 5 + 256, 0),
|
|
EncodeVop3Word0(0x11e, 86),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x11f, 96),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0Sdst(0x128, 98, 30),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 106),
|
|
EncodeVop3Word0(0x123, 87),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x124, 88),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x180, 0),
|
|
EncodeVop3Word1(0, 0, 0),
|
|
EncodeVop3Word0(0x181, 23),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVop3Word0(0x182, 25),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVop3Word0(0x185, 24),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVop3Word0(0x18a, 101),
|
|
EncodeVop3Word1(6 + 256, 0, 0),
|
|
EncodeVop3Word0(0x18d, 74),
|
|
EncodeVop3Word1(6 + 256, 0, 0),
|
|
EncodeVop3Word0(0x18e, 82),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVop3Word0(0x1a0, 25),
|
|
EncodeVop3Word1(6 + 256, 0, 0),
|
|
EncodeVop3Word0(0x1aa, 26),
|
|
EncodeVop3Word1(6 + 256, 0, 0),
|
|
EncodeVop3Word0(0x1b7, 29),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVop3Word0(0x1b8, 30),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVop3Word0(0x1b9, 33),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVop3Word0(0x1ba, 34),
|
|
EncodeVop3Word1(5 + 256, 0, 0),
|
|
EncodeVopc(0x04, 6 + 256, 6), // v_cmp_gt_f32 v6, v6
|
|
EncodeVopc(0xc4, 7 + 256, 5), // v_cmp_gt_u32 v7, v5
|
|
EncodeVopc(0x14, 6 + 256, 6), // v_cmpx_gt_f32 v6, v6
|
|
EncodeVopc(0xd4, 7 + 256, 5), // v_cmpx_gt_u32 v7, v5
|
|
EncodeVopc(0x00, 6 + 256, 6), // v_cmp_f_f32 v6, v6
|
|
EncodeVopc(0x0f, 6 + 256, 6), // v_cmp_tru_f32 v6, v6
|
|
EncodeVopc(0x07, 6 + 256, 6), // v_cmp_o_f32 v6, v6
|
|
EncodeVopc(0x08, 6 + 256, 6), // v_cmp_u_f32 v6, v6
|
|
EncodeVopc(0x09, 6 + 256, 6), // v_cmp_nge_f32 v6, v6
|
|
EncodeVopc(0x0a, 6 + 256, 6), // v_cmp_nlg_f32 v6, v6
|
|
EncodeVopc(0x0b, 6 + 256, 6), // v_cmp_ngt_f32 v6, v6
|
|
EncodeVopc(0x0c, 6 + 256, 6), // v_cmp_nle_f32 v6, v6
|
|
EncodeVopc(0x0d, 6 + 256, 6), // v_cmp_neq_f32 v6, v6
|
|
0x7c1a02f9u,
|
|
0x068680f0u, // v_cmp_neq_f32 s0, 0.5, v1 (SDWA)
|
|
EncodeVopc(0xd1, 249, 6),
|
|
EncodeVopcSdwa(5, 0, 0, 4), // v_cmpx_lt_u32 exec, v5, v6 (SDWA)
|
|
EncodeVopc(0x0e, 6 + 256, 6), // v_cmp_nlt_f32 v6, v6
|
|
EncodeVopc(0x19, 6 + 256, 6), // v_cmpx_nge_f32 v6, v6
|
|
EncodeVopc(0x1a, 6 + 256, 6), // v_cmpx_nlg_f32 v6, v6
|
|
EncodeVopc(0x1b, 6 + 256, 6), // v_cmpx_ngt_f32 v6, v6
|
|
EncodeVopc(0x1c, 6 + 256, 6), // v_cmpx_nle_f32 v6, v6
|
|
EncodeVopc(0x1d, 6 + 256, 6), // v_cmpx_neq_f32 v6, v6
|
|
EncodeVopc(0x1e, 6 + 256, 6), // v_cmpx_nlt_f32 v6, v6
|
|
EncodeVopc(0x80, 5 + 256, 5), // v_cmp_f_i32 v5, v5
|
|
EncodeVopc(0x87, 5 + 256, 5), // v_cmp_t_i32 v5, v5
|
|
EncodeVopc(0xc0, 5 + 256, 5), // v_cmp_f_u32 v5, v5
|
|
EncodeVopc(0xc7, 5 + 256, 5), // v_cmp_t_u32 v5, v5
|
|
0xd4e5006au,
|
|
0x0000d47eu, // v_cmp_ne_u64 vcc, exec, vcc
|
|
EncodeVop3Word0(0x141, 8),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
|
|
0xd5410004u,
|
|
0x20121301u, // v_mad_f32 v4, -v1, v9, s4
|
|
EncodeVop3Word0(0x144, 110),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x145, 111),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x146, 112),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x147, 113),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
0xd5470005u,
|
|
0x841a0102u, // v_cubema_f32 v5, v2, v0, -v6
|
|
EncodeVop3Word0(0x14b, 37),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
|
|
EncodeVop3Word0(0x151, 38),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
|
|
EncodeVop3Word0(0x154, 39),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
|
|
EncodeVop3Word0(0x157, 40),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
|
|
EncodeVop3Word0(0x152, 41),
|
|
EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
|
|
EncodeVop3Word0(0x153, 42),
|
|
EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
|
|
EncodeVop3Word0(0x155, 43),
|
|
EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
|
|
EncodeVop3Word0(0x156, 44),
|
|
EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
|
|
EncodeVop3Word0(0x158, 45),
|
|
EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
|
|
EncodeVop3Word0(0x159, 46),
|
|
EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
|
|
EncodeVop3Word0(0x148, 47),
|
|
EncodeVop3Word1(5 + 256, 129, 132),
|
|
EncodeVop3Word0(0x149, 48),
|
|
EncodeVop3Word1(5 + 256, 129, 132),
|
|
EncodeVop3Word0(0x14a, 49),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x14e, 50),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 129),
|
|
EncodeVop3Word0(0x14f, 115),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 129),
|
|
EncodeVop3Word0(0x36d, 51),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x169, 52),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x16a, 53),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x16c, 114),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x371, 54),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x372, 55),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x178, 56),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x346, 57),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x347, 58),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x345, 59),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x36f, 60),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x15d, 61),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x142, 62),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x143, 63),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
|
|
EncodeVop3Word0(0x16b, 69),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x30f, 70),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x310, 71),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x319, 72),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
0xd70a1007u,
|
|
0x00020affu,
|
|
0x0000ffffu, // v_max_i16 v7, 0xffff, v5.hi
|
|
0xd70c5007u,
|
|
0x00020882u, // v_min_i16 v7, 2, v4.hi
|
|
EncodeVop3Word0(0x363, 73),
|
|
EncodeVop3Word1(129, 132, 0),
|
|
EncodeVop3Word0(0x360, 26),
|
|
EncodeVop3Word1(5 + 256, 130, 0),
|
|
EncodeVop3Word0(0x361, 107),
|
|
EncodeVop3Word1(5 + 256, 130, 0),
|
|
EncodeVop3Word0(0x377, 108),
|
|
EncodeVop3Word1(5 + 256, 128, 128),
|
|
EncodeVop3Word0(0x378, 109),
|
|
EncodeVop3Word1(5 + 256, 128, 128),
|
|
EncodeVop3Word0(0x12f, 75),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 0),
|
|
0xd52f0000u,
|
|
0x60020300u, // v_cvt_pkrtz_f16_f32 v0, -v0, -v1
|
|
EncodeVop3Word0(0x362, 76),
|
|
EncodeVop3Word1(6 + 256, 129, 0),
|
|
0xd7620107u,
|
|
0x00018509u, // v_ldexp_f32 v7, abs(v9), -2
|
|
0xd762800du,
|
|
0x00018906u, // v_ldexp_f32 clamp v13, v6, -4
|
|
EncodeVop3Word0(0x364, 77),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x368, 78),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x369, 79),
|
|
EncodeVop3Word1(6 + 256, 6 + 256, 0),
|
|
EncodeVop3Word0(0x36a, 80),
|
|
EncodeVop3Word1(5 + 256, 7 + 256, 0),
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_movk_i32 s9") != std::string::npos),
|
|
"new decoder did not decode SOPK mov");
|
|
Check((result.decoded_dump.find("s_cmp_gt_u32") != std::string::npos),
|
|
"new decoder did not decode SOPK compare");
|
|
Check((result.decoded_dump.find("v_nop") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP1 no-op");
|
|
Check((result.decoded_dump.find("v_mov_b32 v5") != std::string::npos),
|
|
"new decoder did not decode VOP1 mov");
|
|
Check((result.decoded_dump.find("v_movrels_b32 v55, v12") != std::string::npos),
|
|
"new decoder did not decode VOP1 V_MOVRELS_B32");
|
|
Check(
|
|
(result.decoded_dump.find("v_mov_b32 v103, v5.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode VOP1 SDWA source selector");
|
|
Check(
|
|
(result.decoded_dump.find("v_cvt_f16_f32 v105.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode VOP1 SDWA destination selector");
|
|
Check((result.decoded_dump.find("v_mov_b32 v106, v5.dpp") != std::string::npos),
|
|
"new decoder did not decode VOP1 DPP source metadata");
|
|
Check((result.decoded_dump.find("VOP1 SDWA/DPP modifiers are not implemented") == std::string::npos),
|
|
"new decoder still reports blanket VOP1 SDWA/DPP unsupported reason");
|
|
Check((result.decoded_dump.find("v_add_nc_u32 v123, v5.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode VOP2 SDWA source selector");
|
|
Check(
|
|
(result.decoded_dump.find("v_and_b32 v124, v5.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode VOP2 SDWA first source selector");
|
|
Check((result.decoded_dump.find("v6.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode VOP2 SDWA second source selector");
|
|
Check((result.decoded_dump.find("v_cndmask_b32 v47, v53,") != std::string::npos) &&
|
|
(result.decoded_dump.find("v53.neg") != std::string::npos),
|
|
"new decoder did not decode V_CNDMASK_B32 SDWA source modifier");
|
|
Check((result.decoded_dump.find("v_cndmask_b32 v7, v0.sdwa(sel=4") != std::string::npos) &&
|
|
(result.decoded_dump.find("v1") != std::string::npos),
|
|
"new decoder did not decode full-destination V_CNDMASK_B32 with SDWA "
|
|
"source");
|
|
Check((result.decoded_dump.find("v_add_f32 v125, v5.dpp") != std::string::npos),
|
|
"new decoder did not decode VOP2 DPP source metadata");
|
|
Check((result.decoded_dump.find("VOP2 SDWA/DPP modifiers are not implemented") == std::string::npos),
|
|
"new decoder still reports blanket VOP2 SDWA/DPP unsupported reason");
|
|
Check((result.decoded_dump.find("v_readfirstlane_b32 s24, v5") != std::string::npos),
|
|
"new decoder did not decode old-backed V_READFIRSTLANE_B32");
|
|
Check((result.decoded_dump.find("v_cvt_f32_u32 v6") != std::string::npos),
|
|
"new decoder did not decode VOP1 conversion");
|
|
Check((result.decoded_dump.find("v_cvt_f32_i32 v9") != std::string::npos),
|
|
"new decoder did not decode VOP1 signed int-to-float conversion");
|
|
Check((result.decoded_dump.find("v_cvt_i32_f32 v10") != std::string::npos),
|
|
"new decoder did not decode VOP1 float-to-signed-int conversion");
|
|
Check((result.decoded_dump.find("v_cvt_f16_f32 v99") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_F16_F32");
|
|
Check((result.decoded_dump.find("v_cvt_f32_f16 v100") != std::string::npos),
|
|
"new decoder did not decode old-backed native V_CVT_F32_F16");
|
|
Check(
|
|
(result.decoded_dump.find("v_cvt_f32_f16 v8, v2.sdwa(sel=5") != std::string::npos) &&
|
|
(result.decoded_dump.find("v2.sdwa(sel=5,sext=0).abs") != std::string::npos),
|
|
"new decoder did not decode V_CVT_F32_F16 SDWA source selector modifier");
|
|
Check((result.decoded_dump.find("v_cvt_flr_i32_f32 v64") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_FLR_I32_F32");
|
|
Check((result.decoded_dump.find("v_cvt_off_f32_i4 v81") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_OFF_F32_I4");
|
|
Check((result.decoded_dump.find("v_cvt_f32_ubyte0 v65") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_F32_UBYTE0");
|
|
Check((result.decoded_dump.find("v_cvt_f32_ubyte1 v66") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_F32_UBYTE1");
|
|
Check((result.decoded_dump.find("v_cvt_f32_ubyte2 v67") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_F32_UBYTE2");
|
|
Check((result.decoded_dump.find("v_cvt_f32_ubyte3 v68") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_F32_UBYTE3");
|
|
Check((result.decoded_dump.find("v_rcp_f32 v11") != std::string::npos),
|
|
"new decoder did not decode VOP1 reciprocal");
|
|
Check((result.decoded_dump.find("v_fract_f32 v12") != std::string::npos),
|
|
"new decoder did not decode VOP1 fract");
|
|
Check((result.decoded_dump.find("v_cos_f32 v22") != std::string::npos),
|
|
"new decoder did not decode VOP1 cosine");
|
|
Check((result.decoded_dump.find("v_not_b32 v27") != std::string::npos),
|
|
"new decoder did not decode VOP1 not");
|
|
Check((result.decoded_dump.find("v_bfrev_b32 v28") != std::string::npos),
|
|
"new decoder did not decode VOP1 bit reverse");
|
|
Check((result.decoded_dump.find("v_ffbh_u32 v31") != std::string::npos),
|
|
"new decoder did not decode VOP1 find-first-bit-high");
|
|
Check((result.decoded_dump.find("v_ffbl_b32 v32") != std::string::npos),
|
|
"new decoder did not decode VOP1 find-first-bit-low");
|
|
Check((result.decoded_dump.find("v_xnor_b32 v83, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_XNOR_B32");
|
|
Check((result.decoded_dump.find("v_mbcnt_lo_u32_b32 v84, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MBCNT_LO_U32_B32");
|
|
Check((result.decoded_dump.find("v_mbcnt_hi_u32_b32 v85, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MBCNT_HI_U32_B32");
|
|
Check((result.decoded_dump.find("v_mac_f32 v89, v6, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MAC_F32");
|
|
Check((result.decoded_dump.find("v_madmk_f32 v90, v6, 0x3f800000, v5") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MADMK_F32 literal form");
|
|
Check((result.decoded_dump.find("v_madak_f32 v91, v6, v5, 0x40000000") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MADAK_F32 literal form");
|
|
Check((result.decoded_dump.find("v_mac_f32 v92, v6, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed alternate V_MAC_F32");
|
|
Check((result.decoded_dump.find("v_madmk_f32 v93, v6, 0x3f000000, v5") != std::string::npos),
|
|
"new decoder did not decode old-backed alternate V_MADMK_F32");
|
|
Check((result.decoded_dump.find("v_madak_f32 v94, v6, v5, 0x40400000") != std::string::npos),
|
|
"new decoder did not decode old-backed alternate V_MADAK_F32");
|
|
Check((result.decoded_dump.find("v_cvt_pkrtz_f16_f32 v95, v6, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed native V_CVT_PKRTZ_F16_F32");
|
|
Check((result.decoded_dump.find("v_dot2c_f32_f16 v102, v95, v95") != std::string::npos),
|
|
"new decoder did not decode old-backed V_DOT2C_F32_F16");
|
|
Check((result.decoded_dump.find("v_addc_u32 v97, vcc_lo, v5, v6, vcc_lo") != std::string::npos),
|
|
"new decoder did not decode old-backed V_ADD_CO_U32 carry form");
|
|
Check((result.decoded_dump.find("v_add_f32 v35") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP2 float add");
|
|
Check((result.decoded_dump.find("v_mul_u32_u24 v36") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP2 24-bit multiply");
|
|
Check((result.decoded_dump.find("v_xnor_b32 v86, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_XNOR_B32");
|
|
Check((result.decoded_dump.find("v_mac_f32 v96, v6, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_MAC_F32");
|
|
Check((result.decoded_dump.find("v_addc_u32 v98, s30, v5, v6, vcc_lo") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3 V_ADD_CO_U32 carry form");
|
|
Check(
|
|
(result.decoded_dump.find("v_mbcnt_lo_u32_b32 v87, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_MBCNT_LO_U32_B32");
|
|
Check(
|
|
(result.decoded_dump.find("v_mbcnt_hi_u32_b32 v88, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_MBCNT_HI_U32_B32");
|
|
Check((result.decoded_dump.find("v_mov_b32 v23") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 move");
|
|
Check(
|
|
(result.decoded_dump.find("v_readfirstlane_b32 s25, v5") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_READFIRSTLANE_B32");
|
|
Check((result.decoded_dump.find("v_cvt_f32_i32 v24") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 signed conversion");
|
|
Check((result.decoded_dump.find("v_cvt_f16_f32 v101") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_CVT_F16_F32");
|
|
Check((result.decoded_dump.find("v_cvt_flr_i32_f32 v74") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_CVT_FLR_I32_F32");
|
|
Check((result.decoded_dump.find("v_cvt_off_f32_i4 v82") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3-encoded V_CVT_OFF_F32_I4");
|
|
Check((result.decoded_dump.find("v_fract_f32 v25") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 fract");
|
|
Check((result.decoded_dump.find("v_rcp_f32 v26") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 reciprocal");
|
|
Check((result.decoded_dump.find("v_not_b32 v29") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 not");
|
|
Check((result.decoded_dump.find("v_bfrev_b32 v30") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 bit reverse");
|
|
Check((result.decoded_dump.find("v_ffbh_u32 v33") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 find-first-bit-high");
|
|
Check((result.decoded_dump.find("v_ffbl_b32 v34") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded VOP1 find-first-bit-low");
|
|
Check((result.decoded_dump.find("v_cmp_gt_f32") != std::string::npos),
|
|
"new decoder did not decode VOPC float compare");
|
|
Check((result.decoded_dump.find("v_cmpx_gt_f32") != std::string::npos),
|
|
"new decoder did not decode VOPC float compare-and-mask");
|
|
Check((result.decoded_dump.find("v_cmpx_gt_u32") != std::string::npos),
|
|
"new decoder did not decode VOPC uint compare-and-mask");
|
|
Check((result.decoded_dump.find("v_cmp_f_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_F_F32");
|
|
Check((result.decoded_dump.find("v_cmp_tru_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_TRU_F32");
|
|
Check((result.decoded_dump.find("v_cmp_o_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_O_F32");
|
|
Check((result.decoded_dump.find("v_cmp_u_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_U_F32");
|
|
Check((result.decoded_dump.find("v_cmp_nge_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_NGE_F32");
|
|
Check((result.decoded_dump.find("v_cmp_nlg_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_NLG_F32");
|
|
Check((result.decoded_dump.find("v_cmp_ngt_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_NGT_F32");
|
|
Check((result.decoded_dump.find("v_cmp_nle_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_NLE_F32");
|
|
Check((result.decoded_dump.find("v_cmp_neq_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_NEQ_F32");
|
|
Check((result.decoded_dump.find("v_cmp_neq_f32 s0, 0.500000, v1") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_NEQ_F32 SDWA scalar "
|
|
"destination");
|
|
Check((result.decoded_dump.find("v_cmpx_lt_u32 exec_lo, v5.sdwa(sel=4") != std::string::npos) &&
|
|
(result.ir_dump.find("CompareMaskLtU32 exec_lo") != std::string::npos),
|
|
"new decoder did not route V_CMPX SDWA destination to exec");
|
|
Check((result.decoded_dump.find("v_cmp_nlt_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_NLT_F32");
|
|
Check((result.decoded_dump.find("v_cmpx_nge_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMPX_NGE_F32");
|
|
Check((result.decoded_dump.find("v_cmpx_nlg_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMPX_NLG_F32");
|
|
Check((result.decoded_dump.find("v_cmpx_ngt_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMPX_NGT_F32");
|
|
Check((result.decoded_dump.find("v_cmpx_nle_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMPX_NLE_F32");
|
|
Check((result.decoded_dump.find("v_cmpx_neq_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMPX_NEQ_F32");
|
|
Check((result.decoded_dump.find("v_cmpx_nlt_f32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMPX_NLT_F32");
|
|
Check((result.decoded_dump.find("v_cmp_f_i32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_F_I32");
|
|
Check((result.decoded_dump.find("v_cmp_t_i32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_T_I32");
|
|
Check((result.decoded_dump.find("v_cmp_f_u32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_F_U32");
|
|
Check((result.decoded_dump.find("v_cmp_t_u32") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CMP_T_U32");
|
|
Check((result.decoded_dump.find("v_cmp_ne_u64 vcc_lo, exec_lo, vcc_lo") != std::string::npos),
|
|
"new decoder did not decode VOP3-encoded V_CMP_NE_U64");
|
|
Check((result.ir_dump.find("CompareNeU64 vcc_lo, exec_lo, vcc_lo") != std::string::npos),
|
|
"V_CMP_NE_U64 did not lower to 64-bit compare IR");
|
|
Check((result.decoded_dump.find("v_mad_f32 v8") != std::string::npos),
|
|
"new decoder did not decode VOP3 mad");
|
|
Check(
|
|
(result.decoded_dump.find("v_mad_f32 v4, v1.neg, v9, s4") != std::string::npos),
|
|
"new decoder did not decode VOP3 mad source modifiers");
|
|
Check(
|
|
(result.decoded_dump.find("v_cubeid_f32 v110, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CUBEID_F32");
|
|
Check(
|
|
(result.decoded_dump.find("v_cubesc_f32 v111, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CUBESC_F32");
|
|
Check(
|
|
(result.decoded_dump.find("v_cubetc_f32 v112, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CUBETC_F32");
|
|
Check(
|
|
(result.decoded_dump.find("v_cubema_f32 v113, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CUBEMA_F32");
|
|
Check((result.decoded_dump.find("v_cubema_f32 v5, v2, v0, v6.neg") != std::string::npos),
|
|
"new decoder did not decode V_CUBEMA_F32 source modifier");
|
|
Check((result.decoded_dump.find("v_fma_f32 v37") != std::string::npos),
|
|
"new decoder did not decode VOP3 fma");
|
|
Check((result.decoded_dump.find("v_min3_f32 v38") != std::string::npos),
|
|
"new decoder did not decode VOP3 min3");
|
|
Check((result.decoded_dump.find("v_max3_f32 v39") != std::string::npos),
|
|
"new decoder did not decode VOP3 max3");
|
|
Check((result.decoded_dump.find("v_med3_f32 v40") != std::string::npos),
|
|
"new decoder did not decode VOP3 med3");
|
|
Check((result.decoded_dump.find("v_min3_i32 v41") != std::string::npos),
|
|
"new decoder did not decode VOP3 signed min3");
|
|
Check((result.decoded_dump.find("v_min3_u32 v42") != std::string::npos),
|
|
"new decoder did not decode VOP3 unsigned min3");
|
|
Check((result.decoded_dump.find("v_max3_i32 v43") != std::string::npos),
|
|
"new decoder did not decode VOP3 signed max3");
|
|
Check((result.decoded_dump.find("v_max3_u32 v44") != std::string::npos),
|
|
"new decoder did not decode VOP3 unsigned max3");
|
|
Check((result.decoded_dump.find("v_med3_i32 v45") != std::string::npos),
|
|
"new decoder did not decode VOP3 signed med3");
|
|
Check((result.decoded_dump.find("v_med3_u32 v46") != std::string::npos),
|
|
"new decoder did not decode VOP3 unsigned med3");
|
|
Check((result.decoded_dump.find("v_bfe_u32 v47") != std::string::npos),
|
|
"new decoder did not decode VOP3 unsigned bitfield extract");
|
|
Check((result.decoded_dump.find("v_bfe_i32 v48") != std::string::npos),
|
|
"new decoder did not decode VOP3 signed bitfield extract");
|
|
Check((result.decoded_dump.find("v_bfi_b32 v49") != std::string::npos),
|
|
"new decoder did not decode VOP3 bitfield insert-select");
|
|
Check((result.decoded_dump.find("v_alignbit_b32 v50") != std::string::npos),
|
|
"new decoder did not decode VOP3 alignbit");
|
|
Check((result.decoded_dump.find("v_alignbyte_b32 v115") != std::string::npos),
|
|
"new decoder did not decode VOP3 alignbyte");
|
|
Check((result.decoded_dump.find("v_add3_u32 v51") != std::string::npos),
|
|
"new decoder did not decode VOP3 add3");
|
|
Check((result.decoded_dump.find("v_mul_lo_u32 v52, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MUL_LO_U32");
|
|
Check((result.decoded_dump.find("v_mul_hi_u32 v53, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MUL_HI_U32");
|
|
Check((result.decoded_dump.find("v_mul_hi_i32 v114, v5, v6") != std::string::npos),
|
|
"new decoder did not decode RDNA2 V_MUL_HI_I32");
|
|
Check(
|
|
(result.decoded_dump.find("v_and_or_b32 v54, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_AND_OR_B32");
|
|
Check((result.decoded_dump.find("v_or3_b32 v55, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_OR3_B32");
|
|
Check((result.decoded_dump.find("v_xor3_b32 v56, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_XOR3_B32");
|
|
Check((result.decoded_dump.find("v_lshl_add_u32 v57, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_LSHL_ADD_U32");
|
|
Check((result.decoded_dump.find("v_add_lshl_u32 v58, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_ADD_LSHL_U32");
|
|
Check((result.decoded_dump.find("v_xad_u32 v59, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_XAD_U32");
|
|
Check(
|
|
(result.decoded_dump.find("v_lshl_or_b32 v60, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_LSHL_OR_B32");
|
|
Check((result.decoded_dump.find("v_sad_u32 v61, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_SAD_U32");
|
|
Check(
|
|
(result.decoded_dump.find("v_mad_i32_i24 v62, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MAD_I32_I24");
|
|
Check(
|
|
(result.decoded_dump.find("v_mad_u32_u24 v63, v5, v6, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MAD_U32_U24");
|
|
Check((result.decoded_dump.find("v_mul_lo_i32 v69, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MUL_LO_I32");
|
|
Check((result.decoded_dump.find("v_add_i32 v70, s0, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_ADD_I32");
|
|
Check((result.decoded_dump.find("v_sub_i32 v71, s0, v5, v6") != std::string::npos),
|
|
"new decoder did not decode RDNA2 V_SUB_CO_U32");
|
|
Check(
|
|
(result.decoded_dump.find("v_subrev_i32 v72, s0, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_SUBREV_I32");
|
|
Check((result.decoded_dump.find("v_max_i16 v7.sdwa(sel=4,sext=0), 0x0000ffff, "
|
|
"v5.opsel(lo=1,hi=0,neghi=0)") != std::string::npos),
|
|
"new decoder did not decode RDNA2 V_MAX_I16 literal/op_sel form");
|
|
Check((result.decoded_dump.find("v_min_i16 v7.sdwa(sel=5,sext=0), 2, v4.opsel(lo=1,hi=0,neghi=0)") != std::string::npos),
|
|
"new decoder did not decode RDNA2 V_MIN_I16 op_sel form");
|
|
Check((result.decoded_dump.find("v_bfm_b32 v73, 1, 4") != std::string::npos),
|
|
"new decoder did not decode old-backed V_BFM_B32");
|
|
Check((result.decoded_dump.find("v_readlane_b32 s26, v5, 2") != std::string::npos),
|
|
"new decoder did not decode old-backed V_READLANE_B32");
|
|
Check((result.decoded_dump.find("v_writelane_b32 v107, v5, 2") != std::string::npos),
|
|
"new decoder did not decode old-backed V_WRITELANE_B32");
|
|
Check((result.decoded_dump.find("v_permlane16_b32 v108, v5, 0, 0") != std::string::npos),
|
|
"new decoder did not decode old-backed V_PERMLANE16_B32");
|
|
Check((result.decoded_dump.find("v_permlanex16_b32 v109, v5, 0, 0") != std::string::npos),
|
|
"new decoder did not decode old-backed V_PERMLANEX16_B32");
|
|
Check((result.decoded_dump.find("v_cvt_pkrtz_f16_f32 v75, v6, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_PKRTZ_F16_F32");
|
|
Check((result.decoded_dump.find("v_cvt_pkrtz_f16_f32 v0, v0.neg, v1.neg") != std::string::npos),
|
|
"new decoder did not decode V_CVT_PKRTZ_F16_F32 source modifiers");
|
|
Check((result.decoded_dump.find("v_ldexp_f32 v76, v6, 1") != std::string::npos),
|
|
"new decoder did not decode old-backed V_LDEXP_F32");
|
|
Check((result.decoded_dump.find("v_ldexp_f32 v7, v9.abs, -2") != std::string::npos),
|
|
"new decoder did not decode V_LDEXP_F32 source modifier");
|
|
Check(
|
|
(result.decoded_dump.find("v_ldexp_f32 v13.clamp, v6, -4") != std::string::npos),
|
|
"new decoder did not decode V_LDEXP_F32 clamp modifier");
|
|
Check((result.decoded_dump.find("v_bcnt_u32_b32 v77, v5, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3 V_BCNT_U32_B32");
|
|
Check((result.decoded_dump.find("v_cvt_pknorm_i16_f32 v78, v6, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_PKNORM_I16_F32");
|
|
Check((result.decoded_dump.find("v_cvt_pknorm_u16_f32 v79, v6, v6") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_PKNORM_U16_F32");
|
|
Check(
|
|
(result.decoded_dump.find("v_cvt_pk_u16_u32 v80, v5, v7") != std::string::npos),
|
|
"new decoder did not decode old-backed V_CVT_PK_U16_U32");
|
|
Check((result.decoded_dump.find("v_mul_f32 v2, s0, s72") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MUL_F32 SDWA full-width form");
|
|
Check((result.decoded_dump.find("v_mul_f16 v9.sdwa(sel=4") != std::string::npos) &&
|
|
(result.decoded_dump.find("v0.sdwa(sel=4") != std::string::npos) &&
|
|
(result.decoded_dump.find("v1.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode V_MUL_F16 SDWA low-half form");
|
|
Check((result.decoded_dump.find("v_sub_f16 v0.sdwa(sel=4") != std::string::npos) &&
|
|
(result.decoded_dump.find("v0.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode V_SUB_F16 SDWA low-half form");
|
|
Check((result.ir_dump.find("ConvertU32ToF32 v6") != std::string::npos),
|
|
"VOP1 uint-to-float conversion did not lower to IR");
|
|
Check((result.ir_dump.find("MoveU32 v103, v5.sdwa(sel=4") != std::string::npos),
|
|
"VOP1 SDWA source selector did not lower to IR metadata");
|
|
Check((result.ir_dump.find("ConvertF32ToF16 v105.sdwa(sel=5") != std::string::npos),
|
|
"VOP1 SDWA destination selector did not lower to IR metadata");
|
|
Check((result.ir_dump.find("MoveU32 v106.dpp") != std::string::npos) &&
|
|
(result.ir_dump.find("v5.dpp") != std::string::npos),
|
|
"VOP1 DPP source/destination did not lower to IR metadata");
|
|
Check((result.ir_dump.find("MoveRelSourceU32 v55, v12, m0") != std::string::npos),
|
|
"V_MOVRELS_B32 did not lower to indexed VGPR-source IR");
|
|
Check((result.ir_dump.find("IAddU32 v123, v5.sdwa(sel=4") != std::string::npos),
|
|
"VOP2 SDWA did not lower first source metadata to IR");
|
|
Check(
|
|
(result.ir_dump.find("BitwiseAndU32 v124, v5.sdwa(sel=4") != std::string::npos),
|
|
"VOP2 SDWA bitwise op did not lower first source metadata to IR");
|
|
Check((result.ir_dump.find("v6.sdwa(sel=5") != std::string::npos),
|
|
"VOP2 SDWA bitwise op did not lower second source metadata to IR");
|
|
Check((result.ir_dump.find("SelectMaskF32Bits v47, vcc_lo, v53.neg, v53") != std::string::npos) &&
|
|
(result.ir_dump.find("v53.neg") != std::string::npos),
|
|
"V_CNDMASK_B32 SDWA source modifier did not lower to float-bit select "
|
|
"IR");
|
|
Check((result.ir_dump.find("SelectMaskU32 v7, vcc_lo, v1, v0.sdwa(sel=4") != std::string::npos),
|
|
"full-destination V_CNDMASK_B32 with SDWA source did not lower to "
|
|
"integer select IR");
|
|
Check((result.ir_dump.find("FAddF32 v125.dpp") != std::string::npos) &&
|
|
(result.ir_dump.find("v5.dpp") != std::string::npos),
|
|
"VOP2 DPP source/destination did not lower to IR metadata");
|
|
Check((result.ir_dump.find("FMulF32 v2, s0, s72") != std::string::npos),
|
|
"V_MUL_F32 SDWA full-width form did not lower to IR");
|
|
Check((result.ir_dump.find("MulF16 v9.sdwa(sel=4") != std::string::npos) &&
|
|
(result.ir_dump.find("v0.sdwa(sel=4") != std::string::npos) &&
|
|
(result.ir_dump.find("v1.sdwa(sel=4") != std::string::npos),
|
|
"V_MUL_F16 SDWA low-half form did not lower to IR");
|
|
Check((result.ir_dump.find("SubF16 v0.sdwa(sel=4") != std::string::npos) &&
|
|
(result.ir_dump.find("v0.sdwa(sel=4") != std::string::npos),
|
|
"V_SUB_F16 SDWA low-half form did not lower to IR");
|
|
Check((result.ir_dump.find("ConvertF32ToU32 v7") != std::string::npos),
|
|
"VOP1 float-to-uint conversion did not lower to IR");
|
|
Check((result.ir_dump.find("ConvertI32ToF32 v9") != std::string::npos),
|
|
"VOP1 signed int-to-float conversion did not lower to IR");
|
|
Check((result.ir_dump.find("ConvertF32ToI32 v10") != std::string::npos),
|
|
"VOP1 float-to-signed-int conversion did not lower to IR");
|
|
Check((result.ir_dump.find("ConvertF32ToF16 v99, v6") != std::string::npos),
|
|
"V_CVT_F16_F32 did not lower to shared half-pack IR");
|
|
Check((result.ir_dump.find("ConvertF16ToF32 v100, v99") != std::string::npos),
|
|
"V_CVT_F32_F16 did not lower to shared half-unpack IR");
|
|
Check((result.ir_dump.find("ConvertF16ToF32 v8, v2.sdwa(sel=5") != std::string::npos) &&
|
|
(result.ir_dump.find("v2.sdwa(sel=5,sext=0).abs") != std::string::npos),
|
|
"V_CVT_F32_F16 SDWA source selector modifier did not lower to IR");
|
|
Check((result.ir_dump.find("ReadFirstLaneU32 s24, v5") != std::string::npos),
|
|
"V_READFIRSTLANE_B32 did not lower to subgroup IR");
|
|
Check((result.ir_dump.find("ConvertFloorF32ToI32 v64, v6") != std::string::npos),
|
|
"V_CVT_FLR_I32_F32 did not lower to shared IR");
|
|
Check((result.ir_dump.find("ConvertI4ToOffsetF32 v81, v5") != std::string::npos),
|
|
"V_CVT_OFF_F32_I4 did not lower to shared offset-convert IR");
|
|
Check((result.ir_dump.find("ConvertByteU32ToF32 v65, v5, 0x00000000") != std::string::npos),
|
|
"V_CVT_F32_UBYTE0 did not lower to shared byte-convert IR");
|
|
Check((result.ir_dump.find("ConvertByteU32ToF32 v66, v5, 0x00000001") != std::string::npos),
|
|
"V_CVT_F32_UBYTE1 did not lower to shared byte-convert IR");
|
|
Check((result.ir_dump.find("ConvertByteU32ToF32 v67, v5, 0x00000002") != std::string::npos),
|
|
"V_CVT_F32_UBYTE2 did not lower to shared byte-convert IR");
|
|
Check((result.ir_dump.find("ConvertByteU32ToF32 v68, v5, 0x00000003") != std::string::npos),
|
|
"V_CVT_F32_UBYTE3 did not lower to shared byte-convert IR");
|
|
Check((result.ir_dump.find("RcpF32 v11") != std::string::npos),
|
|
"VOP1 reciprocal did not lower to IR");
|
|
Check((result.ir_dump.find("FractF32 v12") != std::string::npos),
|
|
"VOP1 fract did not lower to IR");
|
|
Check((result.ir_dump.find("TruncF32 v13") != std::string::npos),
|
|
"VOP1 trunc did not lower to IR");
|
|
Check((result.ir_dump.find("CeilF32 v14") != std::string::npos),
|
|
"VOP1 ceil did not lower to IR");
|
|
Check((result.ir_dump.find("RoundEvenF32 v15") != std::string::npos),
|
|
"VOP1 round-even did not lower to IR");
|
|
Check((result.ir_dump.find("FloorF32 v16") != std::string::npos),
|
|
"VOP1 floor did not lower to IR");
|
|
Check((result.ir_dump.find("Exp2F32 v17") != std::string::npos),
|
|
"VOP1 exp2 did not lower to IR");
|
|
Check((result.ir_dump.find("Log2F32 v18") != std::string::npos),
|
|
"VOP1 log2 did not lower to IR");
|
|
Check((result.ir_dump.find("InverseSqrtF32 v19") != std::string::npos),
|
|
"VOP1 inverse-sqrt did not lower to IR");
|
|
Check((result.ir_dump.find("SqrtF32 v20") != std::string::npos),
|
|
"VOP1 sqrt did not lower to IR");
|
|
Check((result.ir_dump.find("SinF32 v21") != std::string::npos),
|
|
"VOP1 sin did not lower to IR");
|
|
Check((result.ir_dump.find("CosF32 v22") != std::string::npos),
|
|
"VOP1 cos did not lower to IR");
|
|
Check((result.ir_dump.find("BitwiseNotU32 v27") != std::string::npos),
|
|
"VOP1 not did not lower to IR");
|
|
Check((result.ir_dump.find("BitReverseU32 v28") != std::string::npos),
|
|
"VOP1 bit reverse did not lower to IR");
|
|
Check((result.ir_dump.find("FindMsbFromHighU32 v31") != std::string::npos),
|
|
"VOP1 find-first-bit-high did not lower to IR");
|
|
Check((result.ir_dump.find("FindLsbU32 v32") != std::string::npos),
|
|
"VOP1 find-first-bit-low did not lower to IR");
|
|
Check((result.ir_dump.find("BitwiseXnorU32 v83, v5, v6") != std::string::npos),
|
|
"V_XNOR_B32 did not lower to shared xnor IR");
|
|
Check((result.ir_dump.find("MaskedBitCountLowU32 v84, v5, v6") != std::string::npos),
|
|
"V_MBCNT_LO_U32_B32 did not lower to shared masked bit-count IR");
|
|
Check(
|
|
(result.ir_dump.find("MaskedBitCountHighU32 v85, v5, v6") != std::string::npos),
|
|
"V_MBCNT_HI_U32_B32 did not lower to shared masked bit-count IR");
|
|
Check((result.ir_dump.find("FMadF32 v89, v6, v6, v89") != std::string::npos),
|
|
"V_MAC_F32 did not lower with the destination register as addend");
|
|
Check((result.ir_dump.find("FMadF32 v90, v6, 0x3f800000, v5") != std::string::npos),
|
|
"V_MADMK_F32 did not lower with the literal in source 1");
|
|
Check((result.ir_dump.find("FMadF32 v91, v6, v5, 0x40000000") != std::string::npos),
|
|
"V_MADAK_F32 did not lower with the literal in source 2");
|
|
Check((result.ir_dump.find("FMadF32 v92, v6, v6, v92") != std::string::npos),
|
|
"alternate V_MAC_F32 did not lower with the destination register as "
|
|
"addend");
|
|
Check((result.ir_dump.find("FMadF32 v93, v6, 0x3f000000, v5") != std::string::npos),
|
|
"alternate V_MADMK_F32 did not lower with the literal in source 1");
|
|
Check((result.ir_dump.find("FMadF32 v94, v6, v5, 0x40400000") != std::string::npos),
|
|
"alternate V_MADAK_F32 did not lower with the literal in source 2");
|
|
Check((result.ir_dump.find("PackF32ToF16Rtz v95, v6, v6") != std::string::npos),
|
|
"native V_CVT_PKRTZ_F16_F32 did not lower to shared pack IR");
|
|
Check(
|
|
(result.ir_dump.find("Dot2AccF32F16 v102, v95, v95, v102") != std::string::npos),
|
|
"V_DOT2C_F32_F16 did not lower to explicit dot-accumulate IR");
|
|
Check((result.ir_dump.find("IAddCarryU32 v97, vcc_lo, v5, v6, vcc_lo") != std::string::npos),
|
|
"V_ADD_CO_U32 did not lower to carry-add IR");
|
|
Check((result.ir_dump.find("FAddF32 v35") != std::string::npos),
|
|
"VOP3-encoded VOP2 float add did not lower through shared IR");
|
|
Check((result.ir_dump.find("UMulU24U32 v36") != std::string::npos),
|
|
"VOP3-encoded VOP2 24-bit multiply did not lower through shared IR");
|
|
Check((result.ir_dump.find("BitwiseXnorU32 v86, v5, v6") != std::string::npos),
|
|
"VOP3-encoded V_XNOR_B32 did not lower through shared IR");
|
|
Check((result.ir_dump.find("FMadF32 v96, v6, v6, v96") != std::string::npos),
|
|
"VOP3-encoded V_MAC_F32 did not lower with the destination register as "
|
|
"addend");
|
|
Check((result.ir_dump.find("IAddCarryU32 v98, s30, v5, v6, vcc_lo") != std::string::npos),
|
|
"VOP3-encoded V_ADD_CO_U32 did not lower to carry-add IR");
|
|
Check((result.ir_dump.find("MaskedBitCountLowU32 v87, v5, v6") != std::string::npos),
|
|
"VOP3-encoded V_MBCNT_LO_U32_B32 did not lower through shared IR");
|
|
Check(
|
|
(result.ir_dump.find("MaskedBitCountHighU32 v88, v5, v6") != std::string::npos),
|
|
"VOP3-encoded V_MBCNT_HI_U32_B32 did not lower through shared IR");
|
|
Check((result.ir_dump.find("MoveU32 v23") != std::string::npos),
|
|
"VOP3-encoded VOP1 move did not lower through shared IR");
|
|
Check((result.ir_dump.find("ReadFirstLaneU32 s25, v5") != std::string::npos),
|
|
"VOP3-encoded V_READFIRSTLANE_B32 did not lower through shared IR");
|
|
Check((result.ir_dump.find("ReadLaneU32 s26, v5, 0x00000002") != std::string::npos),
|
|
"V_READLANE_B32 did not lower to subgroup lane IR");
|
|
Check(
|
|
(result.ir_dump.find("WriteLaneU32 v107, v5, 0x00000002") != std::string::npos),
|
|
"V_WRITELANE_B32 did not lower to subgroup lane IR");
|
|
Check((result.ir_dump.find("Permlane16B32 v108, v5, 0x00000000, 0x00000000") != std::string::npos),
|
|
"V_PERMLANE16_B32 did not lower to subgroup perm-lane IR");
|
|
Check((result.ir_dump.find("Permlanex16B32 v109, v5, 0x00000000, 0x00000000") != std::string::npos),
|
|
"V_PERMLANEX16_B32 did not lower to subgroup perm-lane IR");
|
|
Check((result.ir_dump.find("ConvertI32ToF32 v24") != std::string::npos),
|
|
"VOP3-encoded VOP1 signed conversion did not lower through shared IR");
|
|
Check((result.ir_dump.find("ConvertFloorF32ToI32 v74, v6") != std::string::npos),
|
|
"VOP3-encoded V_CVT_FLR_I32_F32 did not lower through shared IR");
|
|
Check((result.ir_dump.find("ConvertI4ToOffsetF32 v82, v5") != std::string::npos),
|
|
"VOP3-encoded V_CVT_OFF_F32_I4 did not lower through shared IR");
|
|
Check((result.ir_dump.find("FractF32 v25") != std::string::npos),
|
|
"VOP3-encoded VOP1 fract did not lower through shared IR");
|
|
Check((result.ir_dump.find("RcpF32 v26") != std::string::npos),
|
|
"VOP3-encoded VOP1 reciprocal did not lower through shared IR");
|
|
Check((result.ir_dump.find("BitwiseNotU32 v29") != std::string::npos),
|
|
"VOP3-encoded VOP1 not did not lower through shared IR");
|
|
Check((result.ir_dump.find("BitReverseU32 v30") != std::string::npos),
|
|
"VOP3-encoded VOP1 bit reverse did not lower through shared IR");
|
|
Check(
|
|
(result.ir_dump.find("FindMsbFromHighU32 v33") != std::string::npos),
|
|
"VOP3-encoded VOP1 find-first-bit-high did not lower through shared IR");
|
|
Check((result.ir_dump.find("FindLsbU32 v34") != std::string::npos),
|
|
"VOP3-encoded VOP1 find-first-bit-low did not lower through shared IR");
|
|
Check((result.ir_dump.find("CompareGtF32") != std::string::npos),
|
|
"VOPC float compare did not lower to IR");
|
|
Check((result.ir_dump.find("CompareMaskGtF32 exec_lo") != std::string::npos),
|
|
"VOPC float compare-and-mask did not lower to exec mask IR");
|
|
Check((result.ir_dump.find("CompareMaskGtU32 exec_lo") != std::string::npos),
|
|
"VOPC uint compare-and-mask did not lower to exec mask IR");
|
|
Check((result.ir_dump.find("CompareFalse vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC false compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareTrue vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC true compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareOrderedF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC ordered compare did not lower to shared IR");
|
|
Check(
|
|
(result.ir_dump.find("CompareUnorderedF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareUnordLtF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-less compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareUnordEqF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-equal compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareUnordLeF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-less-equal compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareUnordGtF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-greater compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareUnordNeF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-not-equal compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareUnordNeF32 s0, 0x3f000000, v1") != std::string::npos),
|
|
"VOPC SDWA unordered-not-equal compare did not lower to "
|
|
"scalar-destination IR");
|
|
Check((result.ir_dump.find("CompareUnordGeF32 vcc_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-greater-equal compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareMaskUnordLtF32 exec_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-less compare-and-mask did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareMaskUnordEqF32 exec_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-equal compare-and-mask did not lower to shared IR");
|
|
Check(
|
|
(result.ir_dump.find("CompareMaskUnordLeF32 exec_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-less-equal compare-and-mask did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareMaskUnordGtF32 exec_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-greater compare-and-mask did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareMaskUnordNeF32 exec_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-not-equal compare-and-mask did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareMaskUnordGeF32 exec_lo, v6, v6") != std::string::npos),
|
|
"VOPC unordered-greater-equal compare-and-mask did not lower to shared "
|
|
"IR");
|
|
Check((result.ir_dump.find("CompareFalse vcc_lo, v5, v5") != std::string::npos),
|
|
"VOPC integer false compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("CompareTrue vcc_lo, v5, v5") != std::string::npos),
|
|
"VOPC integer true compare did not lower to shared IR");
|
|
Check((result.ir_dump.find("FMadF32 v8") != std::string::npos),
|
|
"VOP3 mad did not lower to IR");
|
|
Check((result.ir_dump.find("FMadF32 v4, v1.neg, v9, s4") != std::string::npos),
|
|
"VOP3 mad source modifiers did not lower to IR metadata");
|
|
Check((result.ir_dump.find("CubeIdF32 v110, v5, v6, v7") != std::string::npos),
|
|
"V_CUBEID_F32 did not lower to cube IR");
|
|
Check((result.ir_dump.find("CubeScF32 v111, v5, v6, v7") != std::string::npos),
|
|
"V_CUBESC_F32 did not lower to cube IR");
|
|
Check((result.ir_dump.find("CubeTcF32 v112, v5, v6, v7") != std::string::npos),
|
|
"V_CUBETC_F32 did not lower to cube IR");
|
|
Check((result.ir_dump.find("CubeMaF32 v113, v5, v6, v7") != std::string::npos),
|
|
"V_CUBEMA_F32 did not lower to cube IR");
|
|
Check((result.ir_dump.find("CubeMaF32 v5, v2, v0, v6.neg") != std::string::npos),
|
|
"V_CUBEMA_F32 source modifier did not lower to cube IR");
|
|
Check((result.ir_dump.find("FMadF32 v37") != std::string::npos),
|
|
"VOP3 fma did not lower to IR");
|
|
Check((result.ir_dump.find("FMin3F32 v38") != std::string::npos),
|
|
"VOP3 min3 did not lower to IR");
|
|
Check((result.ir_dump.find("FMax3F32 v39") != std::string::npos),
|
|
"VOP3 max3 did not lower to IR");
|
|
Check((result.ir_dump.find("FMed3F32 v40") != std::string::npos),
|
|
"VOP3 med3 did not lower to IR");
|
|
Check((result.ir_dump.find("IMin3I32 v41") != std::string::npos),
|
|
"VOP3 signed min3 did not lower to IR");
|
|
Check((result.ir_dump.find("UMin3U32 v42") != std::string::npos),
|
|
"VOP3 unsigned min3 did not lower to IR");
|
|
Check((result.ir_dump.find("IMax3I32 v43") != std::string::npos),
|
|
"VOP3 signed max3 did not lower to IR");
|
|
Check((result.ir_dump.find("UMax3U32 v44") != std::string::npos),
|
|
"VOP3 unsigned max3 did not lower to IR");
|
|
Check((result.ir_dump.find("IMed3I32 v45") != std::string::npos),
|
|
"VOP3 signed med3 did not lower to IR");
|
|
Check((result.ir_dump.find("UMed3U32 v46") != std::string::npos),
|
|
"VOP3 unsigned med3 did not lower to IR");
|
|
Check((result.ir_dump.find("BitFieldExtract3U32 v47") != std::string::npos),
|
|
"VOP3 unsigned bitfield extract did not lower to IR");
|
|
Check((result.ir_dump.find("BitFieldExtract3I32 v48") != std::string::npos),
|
|
"VOP3 signed bitfield extract did not lower to IR");
|
|
Check((result.ir_dump.find("BitFieldInsertSelectU32 v49") != std::string::npos),
|
|
"VOP3 bitfield insert-select did not lower to IR");
|
|
Check((result.ir_dump.find("AlignBitU32 v50") != std::string::npos),
|
|
"VOP3 alignbit did not lower to IR");
|
|
Check((result.ir_dump.find("AlignByteU32 v115") != std::string::npos),
|
|
"VOP3 alignbyte did not lower to IR");
|
|
Check((result.ir_dump.find("IAdd3U32 v51") != std::string::npos),
|
|
"VOP3 add3 did not lower to IR");
|
|
Check((result.ir_dump.find("IMulU32 v52, v5, v6") != std::string::npos),
|
|
"V_MUL_LO_U32 did not lower to multiply IR");
|
|
Check((result.ir_dump.find("UMulHighU32 v53, v5, v6") != std::string::npos),
|
|
"V_MUL_HI_U32 did not lower to high-multiply IR");
|
|
Check((result.ir_dump.find("SMulHighI32 v114, v5, v6") != std::string::npos),
|
|
"V_MUL_HI_I32 did not lower to signed high-multiply IR");
|
|
Check((result.ir_dump.find("BitwiseAndOrU32 v54, v5, v6, v7") != std::string::npos),
|
|
"V_AND_OR_B32 did not lower to ternary bitwise IR");
|
|
Check((result.ir_dump.find("BitwiseOr3U32 v55, v5, v6, v7") != std::string::npos),
|
|
"V_OR3_B32 did not lower to ternary bitwise IR");
|
|
Check((result.ir_dump.find("BitwiseXor3U32 v56, v5, v6, v7") != std::string::npos),
|
|
"V_XOR3_B32 did not lower to ternary bitwise IR");
|
|
Check((result.ir_dump.find("ShiftLeftAddU32 v57, v5, v6, v7") != std::string::npos),
|
|
"V_LSHL_ADD_U32 did not lower to shared shift-left-add IR");
|
|
Check((result.ir_dump.find("AddShiftLeftU32 v58, v5, v6, v7") != std::string::npos),
|
|
"V_ADD_LSHL_U32 did not lower to add-shift-left IR");
|
|
Check((result.ir_dump.find("XorAddU32 v59, v5, v6, v7") != std::string::npos),
|
|
"V_XAD_U32 did not lower to xor-add IR");
|
|
Check((result.ir_dump.find("ShiftLeftOrU32 v60, v5, v6, v7") != std::string::npos),
|
|
"V_LSHL_OR_B32 did not lower to shift-left-or IR");
|
|
Check((result.ir_dump.find("SadU32 v61, v5, v6, v7") != std::string::npos),
|
|
"V_SAD_U32 did not lower to sad IR");
|
|
Check((result.ir_dump.find("IMadI24U32 v62, v5, v6, v7") != std::string::npos),
|
|
"V_MAD_I32_I24 did not lower to signed 24-bit mad IR");
|
|
Check((result.ir_dump.find("UMadU24U32 v63, v5, v6, v7") != std::string::npos),
|
|
"V_MAD_U32_U24 did not lower to unsigned 24-bit mad IR");
|
|
Check((result.ir_dump.find("IMulU32 v69, v5, v6") != std::string::npos),
|
|
"V_MUL_LO_I32 did not lower to multiply IR");
|
|
Check((result.ir_dump.find("IAddCarryU32 v70, s0, v5, v6, 0x00000000") != std::string::npos),
|
|
"V_ADD_I32 did not lower to carry-out add IR");
|
|
Check((result.ir_dump.find("ISubBorrowU32 v71, s0, v5, v6") != std::string::npos),
|
|
"V_SUB_I32 did not lower to borrow-out subtract IR");
|
|
Check((result.ir_dump.find("ISubBorrowU32 v72, s0, v6, v5") != std::string::npos),
|
|
"V_SUBREV_I32 did not reverse source order in borrow-out IR");
|
|
Check((result.ir_dump.find("IMaxI16 v7.sdwa(sel=4,sext=0), 0x0000ffff, "
|
|
"v5.opsel(lo=1,hi=0,neghi=0)") != std::string::npos),
|
|
"V_MAX_I16 did not lower to signed halfword max IR");
|
|
Check((result.ir_dump.find("IMinI16 v7.sdwa(sel=5,sext=0), 0x00000002, "
|
|
"v4.opsel(lo=1,hi=0,neghi=0)") != std::string::npos),
|
|
"V_MIN_I16 did not lower to signed halfword min IR");
|
|
Check((result.ir_dump.find("BitFieldMaskU32 v73, 0x00000001, 0x00000004") != std::string::npos),
|
|
"V_BFM_B32 did not lower to shared bitfield-mask IR");
|
|
Check((result.ir_dump.find("PackF32ToF16Rtz v75, v6, v6") != std::string::npos),
|
|
"V_CVT_PKRTZ_F16_F32 did not lower to shared pack IR");
|
|
Check(
|
|
(result.ir_dump.find("PackF32ToF16Rtz v0, v0.neg, v1.neg") != std::string::npos),
|
|
"V_CVT_PKRTZ_F16_F32 source modifiers did not lower to shared pack IR");
|
|
Check((result.ir_dump.find("LdexpF32 v76, v6, 0x00000001") != std::string::npos),
|
|
"V_LDEXP_F32 did not lower to ldexp IR");
|
|
Check((result.ir_dump.find("LdexpF32 v7, v9.abs, 0xfffffffe") != std::string::npos),
|
|
"V_LDEXP_F32 source modifier did not lower to IR");
|
|
Check(
|
|
(result.ir_dump.find("LdexpF32 v13.clamp, v6, 0xfffffffc") != std::string::npos),
|
|
"V_LDEXP_F32 clamp modifier did not lower to IR");
|
|
Check((result.ir_dump.find("BitCountAddU32 v77, v5, v6") != std::string::npos),
|
|
"VOP3 V_BCNT_U32_B32 did not lower to bit-count-add IR");
|
|
Check((result.ir_dump.find("PackSnorm2x16F32 v78, v6, v6") != std::string::npos),
|
|
"V_CVT_PKNORM_I16_F32 did not lower to shared pack IR");
|
|
Check((result.ir_dump.find("PackUnorm2x16F32 v79, v6, v6") != std::string::npos),
|
|
"V_CVT_PKNORM_U16_F32 did not lower to shared pack IR");
|
|
Check((result.ir_dump.find("PackU16U32 v80, v5, v7") != std::string::npos),
|
|
"V_CVT_PK_U16_U32 did not lower to shared pack IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 112),
|
|
"SPIR-V binary does not contain OpConvertUToF");
|
|
Check(SpirvContainsOpcode(result.spirv, 109),
|
|
"SPIR-V binary does not contain OpConvertFToU");
|
|
Check(SpirvContainsOpcode(result.spirv, 111),
|
|
"SPIR-V binary does not contain OpConvertSToF");
|
|
Check(SpirvContainsOpcode(result.spirv, 110),
|
|
"SPIR-V binary does not contain OpConvertFToS");
|
|
Check(SpirvContainsOpcode(result.spirv, 136),
|
|
"SPIR-V binary does not contain OpFDiv");
|
|
Check(SpirvContainsOpcode(result.spirv, 127),
|
|
"SPIR-V binary does not contain OpFNegate");
|
|
Check(SpirvContainsOpcode(result.spirv, 12),
|
|
"SPIR-V binary does not contain OpExtInst");
|
|
Check(SpirvContainsExtInst(result.spirv, 58),
|
|
"SPIR-V binary does not contain GLSL.std.450 PackHalf2x16");
|
|
Check(SpirvContainsExtInst(result.spirv, 62),
|
|
"SPIR-V binary does not contain GLSL.std.450 UnpackHalf2x16");
|
|
Check(SpirvContainsExtInst(result.spirv, 50),
|
|
"SPIR-V binary does not contain GLSL.std.450 Fma");
|
|
Check(SpirvContainsExtInst(result.spirv, 43),
|
|
"SPIR-V binary does not contain GLSL.std.450 FClamp");
|
|
Check(SpirvContainsExtInst(result.spirv, 53),
|
|
"SPIR-V binary does not contain GLSL.std.450 Ldexp");
|
|
Check(SpirvContainsOpcode(result.spirv, 128),
|
|
"SPIR-V binary does not contain OpIAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 149),
|
|
"SPIR-V binary does not contain OpIAddCarry");
|
|
Check(SpirvContainsOpcode(result.spirv, 130),
|
|
"SPIR-V binary does not contain OpISub");
|
|
Check(SpirvContainsOpcode(result.spirv, 132),
|
|
"SPIR-V binary does not contain OpIMul");
|
|
Check(SpirvContainsOpcode(result.spirv, 171),
|
|
"SPIR-V binary does not contain OpINotEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 200),
|
|
"SPIR-V binary does not contain OpNot");
|
|
Check(SpirvContainsOpcode(result.spirv, 204),
|
|
"SPIR-V binary does not contain OpBitReverse");
|
|
Check(SpirvContainsOpcode(result.spirv, 205),
|
|
"SPIR-V binary does not contain OpBitCount");
|
|
Check(SpirvContainsOpcode(result.spirv, 166),
|
|
"SPIR-V binary does not contain OpLogicalOr");
|
|
Check(SpirvContainsOpcode(result.spirv, 167),
|
|
"SPIR-V binary does not contain OpLogicalAnd");
|
|
Check(SpirvContainsOpcode(result.spirv, 186),
|
|
"SPIR-V binary does not contain OpFOrdGreaterThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 181),
|
|
"SPIR-V binary does not contain OpFUnordEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 183),
|
|
"SPIR-V binary does not contain OpFUnordNotEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 185),
|
|
"SPIR-V binary does not contain OpFUnordLessThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 187),
|
|
"SPIR-V binary does not contain OpFUnordGreaterThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 189),
|
|
"SPIR-V binary does not contain OpFUnordLessThanEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 191),
|
|
"SPIR-V binary does not contain OpFUnordGreaterThanEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 190),
|
|
"SPIR-V binary does not contain OpFOrdGreaterThanEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 184),
|
|
"SPIR-V binary does not contain OpFOrdLessThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 173),
|
|
"SPIR-V binary does not contain OpSGreaterThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 174),
|
|
"SPIR-V binary does not contain OpUGreaterThanEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 177),
|
|
"SPIR-V binary does not contain OpSLessThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 202),
|
|
"SPIR-V binary does not contain OpBitFieldSExtract");
|
|
Check(SpirvContainsOpcode(result.spirv, 203),
|
|
"SPIR-V binary does not contain OpBitFieldUExtract");
|
|
Check(SpirvContainsOpcode(result.spirv, 194),
|
|
"SPIR-V binary does not contain OpShiftRightLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad for cmpx old exec mask");
|
|
Check(SpirvContainsOpcode(result.spirv, 133),
|
|
"SPIR-V binary does not contain OpFMul");
|
|
Check(SpirvContainsOpcode(result.spirv, 129),
|
|
"SPIR-V binary does not contain OpFAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 151),
|
|
"SPIR-V binary does not contain OpUMulExtended");
|
|
Check(SpirvContainsOpcode(result.spirv, 152),
|
|
"SPIR-V binary does not contain OpSMulExtended");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 197),
|
|
"SPIR-V binary does not contain OpBitwiseOr");
|
|
Check(SpirvContainsOpcode(result.spirv, 198),
|
|
"SPIR-V binary does not contain OpBitwiseXor");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
Check(SpirvContainsOpcode(result.spirv, 80),
|
|
"SPIR-V binary does not contain OpCompositeConstruct");
|
|
Check(SpirvContainsOpcode(result.spirv, 81),
|
|
"SPIR-V binary does not contain OpCompositeExtract");
|
|
Check(SpirvContainsOpcode(result.spirv, 339),
|
|
"SPIR-V binary does not contain OpGroupNonUniformBallot");
|
|
Check(SpirvContainsOpcode(result.spirv, 343),
|
|
"SPIR-V binary does not contain OpGroupNonUniformBallotFindLSB");
|
|
Check(SpirvContainsOpcode(result.spirv, 345),
|
|
"SPIR-V binary does not contain OpGroupNonUniformShuffle");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerExpandedAluBatch() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7
|
|
EncodeSopk(0x0f, 9, 2), // s_add_i32 s9, s9, 2
|
|
EncodeSopk(0x10, 9, 3), // s_mulk_i32 s9, s9, 3
|
|
EncodeSop2(0x07, 10, 9, 128), // s_min_u32 s10, s9, 0
|
|
EncodeSop2(0x09, 11, 10, 130), // s_max_u32 s11, s10, 2
|
|
EncodeSop2(0x26, 12, 11, 130), // s_mul_i32 s12, s11, 2
|
|
EncodeVop2(0x05, 1, 242, 0), // v_subrev_f32 v1, 1.0, v0
|
|
EncodeVop2(0x0f, 2, 1 + 256, 0), // v_min_f32 v2, v1, v0
|
|
EncodeVop2(0x10, 3, 1 + 256, 2), // v_max_f32 v3, v1, v2
|
|
EncodeVop2(0x27, 4, 130, 3), // v_subrev_nc_u32 v4, 2, v3
|
|
EncodeVop2(0x16, 5, 129, 4), // v_lshrrev_b32 v5, 1, v4
|
|
EncodeVop2(0x1a, 6, 129, 5), // v_lshlrev_b32 v6, 1, v5
|
|
EncodeVop2(0x13, 7, 6 + 256, 5), // v_min_u32 v7, v6, v5
|
|
EncodeVop2(0x14, 8, 7 + 256, 6), // v_max_u32 v8, v7, v6
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_min_u32") != std::string::npos),
|
|
"new decoder did not decode S_MIN_U32");
|
|
Check((result.decoded_dump.find("s_mulk_i32") != std::string::npos),
|
|
"new decoder did not decode S_MULK_I32");
|
|
Check((result.decoded_dump.find("v_subrev_f32") != std::string::npos),
|
|
"new decoder did not decode V_SUBREV_F32");
|
|
Check((result.decoded_dump.find("v_lshlrev_b32") != std::string::npos),
|
|
"new decoder did not decode V_LSHLREV_B32");
|
|
Check((result.ir_dump.find("IMulU32 s9, s9, 0x00000003") != std::string::npos),
|
|
"SOPK multiply did not lower to self-multiply IR");
|
|
Check((result.ir_dump.find("UMinU32 s10") != std::string::npos),
|
|
"S_MIN_U32 did not lower to IR");
|
|
Check((result.ir_dump.find("UMaxU32 s11") != std::string::npos),
|
|
"S_MAX_U32 did not lower to IR");
|
|
Check((result.ir_dump.find("FSubF32 v1, v0, 0x3f800000") != std::string::npos),
|
|
"V_SUBREV_F32 did not reverse source order in IR");
|
|
Check((result.ir_dump.find("FMinF32 v2") != std::string::npos),
|
|
"V_MIN_F32 did not lower to IR");
|
|
Check((result.ir_dump.find("FMaxF32 v3") != std::string::npos),
|
|
"V_MAX_F32 did not lower to IR");
|
|
Check((result.ir_dump.find("ISubU32 v4, v3, 0x00000002") != std::string::npos),
|
|
"V_SUBREV_NC_U32 did not reverse source order in IR");
|
|
Check((result.ir_dump.find("ShiftLeftLogicalU32 v6, v5, 0x00000001") != std::string::npos),
|
|
"V_LSHLREV_B32 did not reverse source order in IR");
|
|
Check((result.ir_dump.find("UMinU32 v7") != std::string::npos),
|
|
"V_MIN_U32 did not lower to IR");
|
|
Check((result.ir_dump.find("UMaxU32 v8") != std::string::npos),
|
|
"V_MAX_U32 did not lower to IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 132),
|
|
"SPIR-V binary does not contain OpIMul");
|
|
Check(SpirvContainsOpcode(result.spirv, 169),
|
|
"SPIR-V binary does not contain OpSelect");
|
|
Check(SpirvContainsOpcode(result.spirv, 172),
|
|
"SPIR-V binary does not contain OpUGreaterThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 176),
|
|
"SPIR-V binary does not contain OpULessThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 184),
|
|
"SPIR-V binary does not contain OpFOrdLessThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 186),
|
|
"SPIR-V binary does not contain OpFOrdGreaterThan");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerVop3pPackedF16() {
|
|
const uint32_t shader[] = {
|
|
EncodeVop3pWord0(0x0f, 114, 0, 7),
|
|
EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_add_f16 v114, v5, v6
|
|
EncodeVop3pWord0(0x10, 115, 0, 7),
|
|
EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_mul_f16 v115, v5, v6
|
|
EncodeVop3pWord0(0x11, 116, 0, 7),
|
|
EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_min_f16 v116, v5, v6
|
|
EncodeVop3pWord0(0x12, 117, 0, 7),
|
|
EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_max_f16 v117, v5, v6
|
|
EncodeVop3pWord0(0x0e, 118, 0, 7, 2),
|
|
EncodeVop3pWord1(5 + 256, 6 + 256, 7 + 256, 7, 1),
|
|
EncodeVop3pWord0(0x20, 119),
|
|
EncodeVop3pWord1(6 + 256, 6 + 256, 6 + 256), // v_fma_f32 via VOP3P
|
|
0xcc204044u,
|
|
0x1a022110u, // v_fma_mix_f32 v68, v16.lo, v16.lo, 0.lo
|
|
EncodeVop3pWord0(0x21, 120, 0, 7),
|
|
EncodeVop3pWord1(5 + 256, 6 + 256, 7 + 256, 7), // v_mad_mixlo_f16
|
|
EncodeVop3pWord0(0x22, 121, 0, 7, 1),
|
|
EncodeVop3pWord1(5 + 256, 6 + 256, 7 + 256, 7, 2), // v_mad_mixhi_f16
|
|
EncodeVop3Word0(0x34b, 122, 0x0f),
|
|
EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256), // native v_fma_f16
|
|
EncodeVop3pWord0(0x22, 126, 0, 1, 0, true),
|
|
EncodeVop3pWord1(242, 243, 3 + 256, 1), // clamped v_mad_mixhi_f16
|
|
EncodeVop3Word0(0x34b, 127, 0x8, 0, true),
|
|
EncodeVop3Word1(240, 4 + 256, 241), // clamped native high-half v_fma_f16
|
|
0xd711001au,
|
|
0x0002170au, // v_pack_b32_f16 v26, v10, v11
|
|
0xd711182bu,
|
|
0x0002170au, // v_pack_b32_f16 v43, v10.hi, v11.hi
|
|
0x78765714u, // v_pk_fmac_f16 from boot shader 0x0000001c00f5c000
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("v_pk_add_f16 v114") != std::string::npos),
|
|
"new decoder did not decode old-backed V_PK_ADD_F16");
|
|
Check((result.decoded_dump.find("v_pk_mul_f16 v115") != std::string::npos),
|
|
"new decoder did not decode old-backed V_PK_MUL_F16");
|
|
Check((result.decoded_dump.find("v_pk_min_f16 v116") != std::string::npos),
|
|
"new decoder did not decode old-backed V_PK_MIN_F16");
|
|
Check((result.decoded_dump.find("v_pk_max_f16 v117") != std::string::npos),
|
|
"new decoder did not decode old-backed V_PK_MAX_F16");
|
|
Check((result.decoded_dump.find("v_pk_fma_f16 v118") != std::string::npos),
|
|
"new decoder did not decode old-backed V_PK_FMA_F16");
|
|
Check((result.decoded_dump.find("v_fma_f32 v119") != std::string::npos),
|
|
"new decoder did not decode old-backed VOP3P V_FMA_F32");
|
|
Check((result.decoded_dump.find("v_fma_f32 v68, v16.opsel(lo=0,hi=1,neghi=0)") != std::string::npos),
|
|
"new decoder did not decode VOP3P V_FMA_MIX_F32 source selectors");
|
|
Check((result.decoded_dump.find("v_mad_mixlo_f16 v120") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MAD_MIXLO_F16");
|
|
Check((result.decoded_dump.find("v_mad_mixhi_f16 v121") != std::string::npos),
|
|
"new decoder did not decode old-backed V_MAD_MIXHI_F16");
|
|
Check((result.decoded_dump.find("v_fma_f16 v122") != std::string::npos),
|
|
"new decoder did not decode native VOP3 V_FMA_F16");
|
|
Check((result.decoded_dump.find("v_mad_mixhi_f16 v126.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode clamped V_MAD_MIXHI_F16 high-half "
|
|
"destination");
|
|
Check((result.decoded_dump.find("v_fma_f16 v127.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode clamped native high-half V_FMA_F16");
|
|
Check((result.decoded_dump.find("v_pack_b32_f16 v26") != std::string::npos),
|
|
"new decoder did not decode native V_PACK_B32_F16");
|
|
Check((result.decoded_dump.find("v_pack_b32_f16 v43") != std::string::npos) &&
|
|
(result.decoded_dump.find("v10.opsel(lo=1") != std::string::npos),
|
|
"new decoder did not decode V_PACK_B32_F16 source lane selectors");
|
|
Check((result.decoded_dump.find("v_pk_fmac_f16 v59") != std::string::npos),
|
|
"new decoder did not decode VOP2 V_PK_FMAC_F16");
|
|
Check((result.decoded_dump.find("v6.opsel(lo=0,hi=1,neghi=1)") != std::string::npos),
|
|
"VOP3P high-lane source modifier was not dumped");
|
|
Check((result.decoded_dump.find("v120.sdwa(sel=4") != std::string::npos),
|
|
"V_MAD_MIXLO_F16 did not expose low-half destination merge");
|
|
Check((result.decoded_dump.find("v121.sdwa(sel=5") != std::string::npos),
|
|
"V_MAD_MIXHI_F16 did not expose high-half destination merge");
|
|
Check((result.ir_dump.find("PackedAddF16 v114") != std::string::npos),
|
|
"V_PK_ADD_F16 did not lower to packed f16 IR");
|
|
Check((result.ir_dump.find("PackedMulF16 v115") != std::string::npos),
|
|
"V_PK_MUL_F16 did not lower to packed f16 IR");
|
|
Check((result.ir_dump.find("PackedMinF16 v116") != std::string::npos),
|
|
"V_PK_MIN_F16 did not lower to packed f16 IR");
|
|
Check((result.ir_dump.find("PackedMaxF16 v117") != std::string::npos),
|
|
"V_PK_MAX_F16 did not lower to packed f16 IR");
|
|
Check((result.ir_dump.find("PackedFmaF16 v118") != std::string::npos),
|
|
"V_PK_FMA_F16 did not lower to packed f16 IR");
|
|
Check((result.ir_dump.find("FMadF32 v119") != std::string::npos),
|
|
"VOP3P V_FMA_F32 did not lower through shared fma IR");
|
|
Check((result.ir_dump.find("FMadF32 v68, v16.opsel(lo=0,hi=1,neghi=0)") != std::string::npos),
|
|
"VOP3P V_FMA_MIX_F32 source selectors did not lower to IR");
|
|
Check((result.ir_dump.find("MadMixF16 v120.sdwa(sel=4") != std::string::npos),
|
|
"V_MAD_MIXLO_F16 did not lower to shared mad-mix IR");
|
|
Check((result.ir_dump.find("MadMixF16 v121.sdwa(sel=5") != std::string::npos),
|
|
"V_MAD_MIXHI_F16 did not lower to shared mad-mix IR");
|
|
Check((result.ir_dump.find("FmaF16 v122") != std::string::npos),
|
|
"native VOP3 V_FMA_F16 did not lower to native f16 fma IR");
|
|
Check((result.ir_dump.find("MadMixF16 v126.sdwa(sel=5") != std::string::npos),
|
|
"clamped V_MAD_MIXHI_F16 did not lower to shared mad-mix IR");
|
|
Check((result.ir_dump.find("FmaF16 v127.sdwa(sel=5") != std::string::npos),
|
|
"clamped native V_FMA_F16 did not lower to native f16 fma IR");
|
|
Check((result.ir_dump.find("PackB32F16 v26") != std::string::npos),
|
|
"V_PACK_B32_F16 did not lower to packed-bit IR");
|
|
Check((result.ir_dump.find("PackB32F16 v43") != std::string::npos) &&
|
|
(result.ir_dump.find("v10.opsel(lo=1") != std::string::npos),
|
|
"V_PACK_B32_F16 source selectors did not lower to IR");
|
|
Check((result.ir_dump.find("PackedFmaF16 v59, v20, v43, v59") != std::string::npos),
|
|
"V_PK_FMAC_F16 did not lower using destination as packed FMA "
|
|
"accumulator");
|
|
Check(SpirvContainsExtInst(result.spirv, 62),
|
|
"SPIR-V binary does not contain GLSL.std.450 UnpackHalf2x16 for VOP3P");
|
|
Check(SpirvContainsExtInst(result.spirv, 58),
|
|
"SPIR-V binary does not contain GLSL.std.450 PackHalf2x16 for VOP3P");
|
|
Check(SpirvContainsExtInst(result.spirv, 50),
|
|
"SPIR-V binary does not contain GLSL.std.450 Fma for VOP3P");
|
|
Check(
|
|
SpirvContainsExtInst(result.spirv, 43),
|
|
"SPIR-V binary does not contain GLSL.std.450 FClamp for clamped f16 ops");
|
|
Check(!SpirvContainsExtInst(result.spirv, 37),
|
|
"SPIR-V binary still contains GLSL.std.450 FMin for VOP3P packed min");
|
|
Check(!SpirvContainsExtInst(result.spirv, 40),
|
|
"SPIR-V binary still contains GLSL.std.450 FMax for VOP3P packed max");
|
|
Check(SpirvContainsOpcode(result.spirv, 169),
|
|
"SPIR-V binary does not contain OpSelect for VOP3P packed min/max");
|
|
Check(SpirvContainsOpcode(result.spirv, 184),
|
|
"SPIR-V binary does not contain OpFOrdLessThan for VOP3P packed min");
|
|
Check(SpirvContainsOpcode(result.spirv, 190),
|
|
"SPIR-V binary does not contain OpFOrdGreaterThanEqual for VOP3P "
|
|
"packed max");
|
|
Check(SpirvContainsOpcode(result.spirv, 197),
|
|
"SPIR-V binary does not contain OpBitwiseOr for VOP3P packed min/max");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd for VOP3P packed min/max");
|
|
Check(SpirvContainsOpcode(result.spirv, 129),
|
|
"SPIR-V binary does not contain OpFAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 133),
|
|
"SPIR-V binary does not contain OpFMul");
|
|
Check(SpirvContainsOpcode(result.spirv, 81),
|
|
"SPIR-V binary does not contain OpCompositeExtract");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerStagedShaderOps() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 132), // s0 = 4
|
|
EncodeSMovB32(1, 129), // s1 = 1
|
|
EncodeSop2(0x05, 2, 0, 1), // s_subb_u32 s2, s0, s1
|
|
EncodeSop1(0x1b, 3, 1), // s_bitset0_b32 s3, s1
|
|
EncodeVop2(0x36, 70, 5 + 256, 6), // v_fmac_f16 v70, v5, v6
|
|
EncodeVop2(0x37, 71, 7 + 256, 8),
|
|
0x3c003c00u, // v_fmamk_f16
|
|
EncodeVop2(0x38, 72, 9 + 256, 10),
|
|
0x40004000u, // v_fmaak_f16
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_subb_u32 s2, s0, s1") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_SUBB_U32");
|
|
Check((result.decoded_dump.find("s_bitset0_b32 s3, s1") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_BITSET0_B32");
|
|
Check((result.decoded_dump.find("v_fmac_f16 v70, v5, v6") != std::string::npos),
|
|
"new decoder did not decode RDNA2 V_FMAC_F16");
|
|
Check(
|
|
(result.decoded_dump.find("v_fmamk_f16 v71, v7, 0x3c003c00, v8") != std::string::npos),
|
|
"new decoder did not consume V_FMAMK_F16 literal as the multiply source");
|
|
Check((result.decoded_dump.find("v_fmaak_f16 v72, v9, v10, 0x40004000") != std::string::npos),
|
|
"new decoder did not consume V_FMAAK_F16 literal as the add source");
|
|
Check((result.ir_dump.find("ScalarSubBorrowCarryU32 s2, s0, s1, scc") != std::string::npos),
|
|
"S_SUBB_U32 did not lower to scalar subtract-with-borrow IR");
|
|
Check((result.ir_dump.find("BitClearU32 s3, s3, s1") != std::string::npos),
|
|
"S_BITSET0_B32 did not lower to bit-clear IR using the destination as "
|
|
"input");
|
|
Check(
|
|
(result.ir_dump.find("FmaF16 v70, v5, v6, v70") != std::string::npos),
|
|
"V_FMAC_F16 did not lower using the destination as the FMA accumulator");
|
|
Check(
|
|
(result.ir_dump.find("FmaF16 v71, v7, 0x3c003c00, v8") != std::string::npos),
|
|
"V_FMAMK_F16 did not lower with the literal in source 1");
|
|
Check(
|
|
(result.ir_dump.find("FmaF16 v72, v9, v10, 0x40004000") != std::string::npos),
|
|
"V_FMAAK_F16 did not lower with the literal in source 2");
|
|
Check(SpirvContainsOpcode(result.spirv, 130),
|
|
"SPIR-V binary does not contain OpISub for S_SUBB_U32");
|
|
Check(SpirvContainsOpcode(result.spirv, 166),
|
|
"SPIR-V binary does not contain OpLogicalOr for S_SUBB_U32 borrow-out");
|
|
Check(SpirvContainsOpcode(result.spirv, 172),
|
|
"SPIR-V binary does not contain OpUGreaterThan for S_SUBB_U32 "
|
|
"borrow-out");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical for S_BITSET0_B32");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd for S_BITSET0_B32");
|
|
Check(SpirvContainsOpcode(result.spirv, 200),
|
|
"SPIR-V binary does not contain OpNot for S_BITSET0_B32");
|
|
Check(SpirvContainsExtInst(result.spirv, 50),
|
|
"SPIR-V binary does not contain GLSL.std.450 Fma for VOP2 F16 FMA ops");
|
|
Check(SpirvContainsExtInst(result.spirv, 58),
|
|
"SPIR-V binary does not contain GLSL.std.450 PackHalf2x16 for VOP2 F16 "
|
|
"FMA ops");
|
|
Check(SpirvContainsExtInst(result.spirv, 62),
|
|
"SPIR-V binary does not contain GLSL.std.450 UnpackHalf2x16 for VOP2 "
|
|
"F16 FMA ops");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerBootF16UnaryOpcodes() {
|
|
const uint32_t shader[] = {
|
|
EncodeVop1(0x55, 4, 249),
|
|
EncodeVop1Sdwa(3, 5, 2, 6),
|
|
EncodeVop1(0x5b, 5, 4 + 256),
|
|
0x7e12b8f9u,
|
|
0x0006150bu,
|
|
0x7e12b90cu,
|
|
EncodeVop1(0x5d, 6, 5 + 256),
|
|
0x7e0e02f9u,
|
|
0x008616c1u,
|
|
0x7e1002f9u,
|
|
0x008616c1u,
|
|
EncodeVop1(0x5e, 3, 3 + 256),
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("v_sqrt_f16 v4.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode V_SQRT_F16 SDWA high-half destination");
|
|
Check((result.decoded_dump.find("v_rndne_f16 v3") != std::string::npos),
|
|
"new decoder did not decode V_RNDNE_F16");
|
|
Check((result.decoded_dump.find("v_floor_f16 v5") != std::string::npos),
|
|
"new decoder did not decode V_FLOOR_F16");
|
|
Check(
|
|
(result.decoded_dump.find("v_ceil_f16 v9.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode boot V_CEIL_F16 SDWA high-half destination");
|
|
Check((result.decoded_dump.find("v_ceil_f16 v9") != std::string::npos),
|
|
"new decoder did not decode boot V_CEIL_F16");
|
|
Check((result.decoded_dump.find("v_trunc_f16 v6") != std::string::npos),
|
|
"new decoder did not decode V_TRUNC_F16");
|
|
Check((result.decoded_dump.find("v_mov_b32 v7, -1") != std::string::npos) &&
|
|
(result.decoded_dump.find("v_mov_b32 v8, -1") != std::string::npos),
|
|
"new decoder did not accept full-width V_MOV_B32 SDWA with "
|
|
"DST_U=PRESERVE");
|
|
Check((result.decoded_dump.find("VOP1 SDWA destination selector is not supported") == std::string::npos),
|
|
"full-width V_MOV_B32 SDWA with DST_U=PRESERVE was rejected");
|
|
Check((result.decoded_dump.find("unsupported family=VOP2 opcode=0x00") == std::string::npos),
|
|
"VOP1 SDWA extension word was decoded as a phantom VOP2 instruction");
|
|
Check((result.ir_dump.find("SqrtF16 v4.sdwa(sel=5") != std::string::npos),
|
|
"V_SQRT_F16 did not lower to f16 sqrt IR");
|
|
Check((result.ir_dump.find("FloorF16 v5") != std::string::npos),
|
|
"V_FLOOR_F16 did not lower to f16 floor IR");
|
|
Check((result.ir_dump.find("CeilF16 v9.sdwa(sel=5") != std::string::npos),
|
|
"boot V_CEIL_F16 SDWA did not lower to f16 ceil IR");
|
|
Check((result.ir_dump.find("CeilF16 v9") != std::string::npos),
|
|
"V_CEIL_F16 did not lower to f16 ceil IR");
|
|
Check((result.ir_dump.find("TruncF16 v6") != std::string::npos),
|
|
"V_TRUNC_F16 did not lower to f16 trunc IR");
|
|
Check(
|
|
(result.ir_dump.find("MoveU32 v7, 0xffffffff") != std::string::npos) &&
|
|
(result.ir_dump.find("MoveU32 v8, 0xffffffff") != std::string::npos),
|
|
"full-width V_MOV_B32 SDWA with DST_U=PRESERVE did not lower to move IR");
|
|
Check((result.ir_dump.find("RoundEvenF16 v3") != std::string::npos),
|
|
"V_RNDNE_F16 did not lower to f16 round-even IR");
|
|
Check(SpirvContainsExtInst(result.spirv, 31),
|
|
"SPIR-V binary does not contain GLSL.std.450 Sqrt for V_SQRT_F16");
|
|
Check(SpirvContainsExtInst(result.spirv, 8),
|
|
"SPIR-V binary does not contain GLSL.std.450 Floor for V_FLOOR_F16");
|
|
Check(SpirvContainsExtInst(result.spirv, 9),
|
|
"SPIR-V binary does not contain GLSL.std.450 Ceil for V_CEIL_F16");
|
|
Check(SpirvContainsExtInst(result.spirv, 3),
|
|
"SPIR-V binary does not contain GLSL.std.450 Trunc for V_TRUNC_F16");
|
|
Check(
|
|
SpirvContainsExtInst(result.spirv, 2),
|
|
"SPIR-V binary does not contain GLSL.std.450 RoundEven for V_RNDNE_F16");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCapturedVop1SdwaByteConvert() {
|
|
const uint32_t shader[] = {
|
|
0x7e0822f9u,
|
|
0x00040609u, // v_cvt_f32_ubyte0 v4, v9.word0 (PS 9ebba6b9)
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(4, 0, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
|
|
ShaderPixelInputInfo ps_info{};
|
|
ps_info.ps_system_input_base = 9;
|
|
ps_info.ps_pos_x = true;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
options.input_info.pixel = &ps_info;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("V_CVT_F32_UBYTE0 v4, v9.sdwa(sel=4,sext=0)") != std::string::npos),
|
|
"captured V_CVT_F32_UBYTE0 SDWA instruction was not decoded");
|
|
|
|
size_t extracts = 0;
|
|
size_t converts = 0;
|
|
for (const auto *block : result.program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
extracts += inst.GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::BitFieldUExtract;
|
|
converts +=
|
|
inst.GetOpcode() == ShaderRecompiler::IR::ValueOpcode::ConvertF32U32;
|
|
}
|
|
}
|
|
Check(extracts != 0u && converts != 0u,
|
|
"captured SDWA byte conversion did not remain live in typed IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 203),
|
|
"captured SDWA byte conversion did not emit OpBitFieldUExtract");
|
|
Check(SpirvContainsOpcode(result.spirv, 112),
|
|
"captured SDWA byte conversion did not emit OpConvertUToF");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t byte_sext[] = {0x7e20a0f9u, 0x000b1412u};
|
|
ShaderRecompiler::Decoder::Instruction decoded;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(byte_sext, 0, decoded);
|
|
Check(decoded.word_count == 2u &&
|
|
decoded.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED,
|
|
"V_CVT_F16_U16 accepted unimplemented SDWA byte sign extension");
|
|
}
|
|
|
|
void TestNewShaderRecompilerCapturedFractF64() {
|
|
using namespace ShaderRecompiler::Decoder;
|
|
const uint32_t captured[] = {0x7e087d04u};
|
|
Instruction decoded;
|
|
DecodeInstruction(captured, 0, decoded);
|
|
Check(decoded.family == Family::VOP1 && decoded.opcode == Opcode::V_FRACT_F64 &&
|
|
decoded.opcode_id == 0x3e && decoded.word_count == 1 &&
|
|
decoded.dst.kind == OperandKind::Vgpr && decoded.dst.reg == 4 &&
|
|
decoded.src0.kind == OperandKind::Vgpr && decoded.src0.reg == 4,
|
|
"captured V_FRACT_F64 did not decode its in-place register pair");
|
|
for (const uint32_t opcode : {0x3eu, 0x2fu}) {
|
|
for (const uint32_t source : {249u, 250u}) {
|
|
const uint32_t invalid[] = {EncodeVop1(opcode, 4, source),
|
|
source == 249u ? 0x00060604u : EncodeVop1Dpp(4, 0xe4)};
|
|
DecodeInstruction(invalid, 0, decoded);
|
|
Check(decoded.opcode == Opcode::UNSUPPORTED,
|
|
"FP64 VOP1 accepted an unsupported SDWA or DPP encoding");
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerF64AluEncodings() {
|
|
using namespace ShaderRecompiler::Decoder;
|
|
for (const bool maximum : {false, true}) {
|
|
const std::array<uint32_t, 2> captured = maximum
|
|
? std::array<uint32_t, 2>{0xd5670006u, 0x00020480u}
|
|
: std::array<uint32_t, 2>{0xd5660006u, 0x00020cf2u};
|
|
Instruction decoded;
|
|
DecodeInstruction(captured, 0, decoded);
|
|
Check(decoded.family == Family::VOP3 &&
|
|
decoded.opcode == (maximum ? Opcode::V_MAX_F64 : Opcode::V_MIN_F64) &&
|
|
decoded.opcode_id == (maximum ? 0x167u : 0x166u) && decoded.word_count == 2 &&
|
|
decoded.dst.kind == OperandKind::Vgpr && decoded.dst.reg == 6 &&
|
|
decoded.src0.kind == (maximum ? OperandKind::IntegerInlineConstant
|
|
: OperandKind::FloatInlineConstant) &&
|
|
decoded.src0.value == (maximum ? 0u : 0x3f800000u) &&
|
|
decoded.src1.kind == OperandKind::Vgpr && decoded.src1.reg == (maximum ? 2u : 6u) &&
|
|
!decoded.src0.absolute && !decoded.src0.negate &&
|
|
!decoded.src1.absolute && !decoded.src1.negate &&
|
|
!decoded.dst.clamp && decoded.dst.omod == 0,
|
|
"captured V_MIN/MAX_F64 lost its inline source, register pair, or modifiers");
|
|
}
|
|
for (const auto [opcode, expected] : {
|
|
std::pair{0x17u, Opcode::V_TRUNC_F64}, std::pair{0x18u, Opcode::V_CEIL_F64},
|
|
std::pair{0x1au, Opcode::V_FLOOR_F64}}) {
|
|
for (const bool vop3 : {false, true}) {
|
|
const std::array<uint32_t, 2> words = vop3
|
|
? std::array<uint32_t, 2>{EncodeVop3Word0(0x180 + opcode, 1, 0, 1),
|
|
EncodeVop3Word1(8, 0, 0) | (1u << 29)}
|
|
: std::array<uint32_t, 2>{EncodeVop1(opcode, 1, 258), 0};
|
|
Instruction decoded;
|
|
DecodeInstruction(words, 0, decoded);
|
|
Check(decoded.opcode == expected && decoded.word_count == (vop3 ? 2u : 1u) &&
|
|
decoded.dst.reg == 1 && decoded.src0.reg == (vop3 ? 8u : 2u) &&
|
|
decoded.src0.absolute == vop3 && decoded.src0.negate == vop3,
|
|
"FP64 rounding lost its VOP1/VOP3 encoding, odd pair, or source modifiers");
|
|
}
|
|
for (const uint32_t source : {249u, 250u}) {
|
|
const uint32_t invalid[] = {EncodeVop1(opcode, 1, source),
|
|
source == 249u ? 0x00060604u : EncodeVop1Dpp(4, 0xe4)};
|
|
Instruction decoded;
|
|
DecodeInstruction(invalid, 0, decoded);
|
|
Check(decoded.opcode == Opcode::UNSUPPORTED,
|
|
"FP64 rounding accepted unsupported SDWA or DPP encoding");
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerVop1SdwaBfrev() {
|
|
using namespace ShaderRecompiler::Decoder;
|
|
const uint32_t captured[] = {0x7e0070f9u, 0x00040600u};
|
|
Instruction decoded;
|
|
DecodeInstruction(captured, 0, decoded);
|
|
Check(decoded.opcode == Opcode::V_BFREV_B32 && decoded.word_count == 2 &&
|
|
decoded.dst.kind == OperandKind::Vgpr && decoded.dst.reg == 0 &&
|
|
decoded.dst.sdwa_sel == 6 && decoded.src0.kind == OperandKind::Vgpr &&
|
|
decoded.src0.reg == 0 && decoded.src0.sdwa_sel == 4 &&
|
|
!decoded.src0.sdwa_sext,
|
|
"captured V_BFREV_B32 SDWA source metadata is incorrect");
|
|
for (const uint32_t modifier : {0x00070600u, 0x00140600u, 0x00240600u,
|
|
0x00042600u, 0x00044600u, 0x00040400u,
|
|
0x00041e00u}) {
|
|
const uint32_t invalid[] = {captured[0], modifier};
|
|
DecodeInstruction(invalid, 0, decoded);
|
|
Check(decoded.word_count == 2 && decoded.opcode == Opcode::UNSUPPORTED,
|
|
"V_BFREV_B32 SDWA accepted unsupported selectors or modifiers");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerVop1SdwaNotDestination() {
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
|
|
const auto check_destination = [&](uint32_t modifier, uint32_t dst_sel,
|
|
uint32_t dst_unused) {
|
|
const uint32_t shader[] = {
|
|
0x7e066ef9u, modifier, EncodeExp0(0x00, 0x1),
|
|
EncodeExp1(3, 0, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Instruction decoded;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(shader, 0, decoded);
|
|
Check(decoded.opcode == ShaderRecompiler::Decoder::Opcode::V_NOT_B32,
|
|
"V_NOT_B32 SDWA opcode did not decode");
|
|
Check(decoded.word_count == 2u,
|
|
"V_NOT_B32 SDWA instruction has the wrong word count");
|
|
Check(decoded.dst.kind == ShaderRecompiler::Decoder::OperandKind::Vgpr &&
|
|
decoded.dst.reg == 3u && decoded.dst.sdwa_sel == dst_sel &&
|
|
decoded.dst.sdwa_dst_unused == dst_unused &&
|
|
decoded.dst.explicit_sdwa_dst,
|
|
"V_NOT_B32 SDWA destination metadata is incorrect");
|
|
Check(decoded.src0.kind == ShaderRecompiler::Decoder::OperandKind::Vgpr &&
|
|
decoded.src0.reg == 0u && decoded.src0.sdwa_sel == 6u &&
|
|
!decoded.src0.negate && !decoded.src0.absolute,
|
|
"V_NOT_B32 SDWA source metadata is incorrect");
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
};
|
|
|
|
check_destination(0x00061400u, 4u, 2u);
|
|
check_destination(0x00060500u, 5u, 0u);
|
|
check_destination(0x00060000u, 0u, 0u);
|
|
|
|
const auto check_rejected = [&](uint32_t modifier, const char *message) {
|
|
const uint32_t instruction[] = {0x7e066ef9u, modifier};
|
|
|
|
ShaderRecompiler::Decoder::Instruction decoded;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(instruction, 0, decoded);
|
|
Check(decoded.word_count == 2u &&
|
|
decoded.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED,
|
|
message);
|
|
};
|
|
|
|
check_rejected(0x00060700u, "V_NOT_B32 SDWA accepted destination selector 7");
|
|
check_rejected(0x00070600u, "V_NOT_B32 SDWA accepted source selector 7");
|
|
check_rejected(0x00061c00u,
|
|
"V_NOT_B32 SDWA accepted destination unused value 3");
|
|
check_rejected(0x00160400u, "V_NOT_B32 SDWA accepted source negation");
|
|
check_rejected(0x00260400u, "V_NOT_B32 SDWA accepted source absolute");
|
|
check_rejected(0x00062400u, "V_NOT_B32 SDWA accepted clamp");
|
|
check_rejected(0x00064400u, "V_NOT_B32 SDWA accepted output modifier");
|
|
}
|
|
|
|
void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() {
|
|
const uint32_t shader[] = {
|
|
0xd7070001u,
|
|
0x00020081u, // v_lshrrev_b16 v1.lo, 1, v0.lo
|
|
0xd7071008u,
|
|
0x0002128fu, // v_lshrrev_b16 with selected high source lane
|
|
0xd7144005u,
|
|
0x00020481u, // v_lshlrev_b16 v5.hi, 1, v2.lo
|
|
0xd7034001u,
|
|
0x0002066au, // v_add_nc_u16 v1.hi, v106.lo, v3.lo
|
|
0xd70e4011u,
|
|
0x00020e80u, // v_sub_nc_i16 v17.hi, 0, v7.lo
|
|
0xcc03400eu,
|
|
0x10020e80u, // v_pk_sub_i16 from boot shader 0x0000001980eb0000
|
|
0xcc0a4104u,
|
|
0x300202ffu,
|
|
0x00007fffu, // v_pk_add_u16 with literal
|
|
0x360e04f9u,
|
|
0x04861482u, // v_and_b32 SDWA partial low-half destination
|
|
0x381212f9u,
|
|
0x04041508u, // v_or_b32 SDWA partial high-half destination
|
|
0xcc10c00cu,
|
|
0x18021d0du, // clamped v_pk_mul_f16
|
|
0x7e08a0f9u,
|
|
0x00061500u, // v_cvt_f16_u16 v4.hi, v0 from boot shader
|
|
EncodeVop1(0x50, 16, 10 + 256), // v_cvt_f16_u16 v16, v10
|
|
0x7e1ca307u, // v_cvt_f16_i16 v14, v7
|
|
0x7e1ca2f9u,
|
|
0x00051511u, // v_cvt_f16_i16 v14.hi, v17.hi
|
|
0x7e08a704u, // v_cvt_i16_f16 v4, v4 from boot shader
|
|
EncodeVop1(0x52, 15, 4 + 256), // v_cvt_u16_f16 v15, v4
|
|
0x4a0a08f9u,
|
|
0x0c860688u, // v_add_nc_u32 v5, 8, sign-extended v4.lo
|
|
0x4a0c0cf9u,
|
|
0x0d860688u, // v_add_nc_u32 v6, 8, sign-extended v6.hi
|
|
0x4a08c2f9u,
|
|
0x0686156au, // captured v_add_nc_u32 v4.word1, vcc_lo, v97; preserve
|
|
// word0
|
|
0x4c1616f9u,
|
|
0x0686128du, // v_sub_nc_u32 v11.byte2, 13, v11; preserve other
|
|
// destination bytes
|
|
0x261418f9u,
|
|
0x0686149fu, // v_min_u32 v10.word0, 31, v12; preserve upper destination
|
|
// word
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("v_lshrrev_b16 v1.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode low-half V_LSHRREV_B16");
|
|
Check((result.decoded_dump.find("v_lshlrev_b16 v5.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode high-half V_LSHLREV_B16");
|
|
Check((result.decoded_dump.find("v_add_nc_u16 v1.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode V_ADD_NC_U16 op_sel destination");
|
|
Check((result.decoded_dump.find("v_sub_nc_i16 v17.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode V_SUB_NC_I16 op_sel destination");
|
|
Check((result.decoded_dump.find("v_pk_sub_i16 v14") != std::string::npos),
|
|
"new decoder did not decode V_PK_SUB_I16");
|
|
Check((result.decoded_dump.find("v_pk_add_u16 v4") != std::string::npos),
|
|
"new decoder did not decode V_PK_ADD_U16");
|
|
Check((result.decoded_dump.find("v_pk_add_u16 v4, 0x00007fff") != std::string::npos),
|
|
"V_PK_ADD_U16 literal was not consumed as a source operand");
|
|
Check((result.decoded_dump.find("v_and_b32 v7.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode partial-destination V_AND_B32 SDWA");
|
|
Check((result.decoded_dump.find("v_or_b32 v9.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode partial-destination V_OR_B32 SDWA");
|
|
Check((result.decoded_dump.find("v_pk_mul_f16 v12") != std::string::npos),
|
|
"new decoder did not decode clamped V_PK_MUL_F16");
|
|
Check((result.decoded_dump.find("v_cvt_f16_u16 v4.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode/consume SDWA V_CVT_F16_U16");
|
|
Check((result.decoded_dump.find("v_cvt_f16_u16 v16") != std::string::npos),
|
|
"new decoder did not decode plain V_CVT_F16_U16");
|
|
Check((result.decoded_dump.find("v_cvt_f16_i16 v14") != std::string::npos),
|
|
"new decoder did not decode plain V_CVT_F16_I16");
|
|
Check(
|
|
(result.decoded_dump.find("v_cvt_f16_i16 v14.sdwa(sel=5") != std::string::npos),
|
|
"new decoder did not decode V_CVT_F16_I16 SDWA high-half destination");
|
|
Check((result.decoded_dump.find("v_cvt_i16_f16 v4") != std::string::npos),
|
|
"new decoder did not decode plain V_CVT_I16_F16");
|
|
Check((result.decoded_dump.find("v_cvt_u16_f16 v15") != std::string::npos),
|
|
"new decoder still rejects plain V_CVT_U16_F16");
|
|
Check((result.decoded_dump.find("v_add_nc_u32 v5") != std::string::npos) &&
|
|
(result.decoded_dump.find("v4.sdwa(sel=4,sext=1") != std::string::npos),
|
|
"new decoder did not decode V_ADD_NC_U32 SDWA sign-extended low word");
|
|
Check((result.decoded_dump.find("v_add_nc_u32 v6") != std::string::npos) &&
|
|
(result.decoded_dump.find("v6.sdwa(sel=5,sext=1") != std::string::npos),
|
|
"new decoder did not decode V_ADD_NC_U32 SDWA sign-extended high word");
|
|
Check(
|
|
(result.decoded_dump.find("v_add_nc_u32 v4.sdwa(sel=5,sext=0), vcc_lo, v97") != std::string::npos),
|
|
"new decoder did not decode captured V_ADD_NC_U32 high-word destination");
|
|
Check((result.decoded_dump.find("v_sub_nc_u32 v11.sdwa(sel=2") != std::string::npos),
|
|
"new decoder did not decode V_SUB_NC_U32 SDWA byte-2 destination");
|
|
Check((result.decoded_dump.find("v_min_u32 v10.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode V_MIN_U32 SDWA low-word destination");
|
|
Check(
|
|
(result.decoded_dump.find("unsupported family=VOP2 opcode=0x00") == std::string::npos),
|
|
"literal/SDWA extension words were decoded as phantom VOP2 instructions");
|
|
Check((result.decoded_dump.find("VOP2 SDWA destination selector is not supported") == std::string::npos),
|
|
"partial bitwise SDWA destination is still rejected");
|
|
Check((result.ir_dump.find("ShiftRightLogicalU16") != std::string::npos),
|
|
"V_LSHRREV_B16 did not lower to 16-bit logical right shift IR");
|
|
Check((result.ir_dump.find("ShiftLeftLogicalU16") != std::string::npos),
|
|
"V_LSHLREV_B16 did not lower to 16-bit logical left shift IR");
|
|
Check((result.ir_dump.find("IAddU16 v1.sdwa(sel=5") != std::string::npos),
|
|
"V_ADD_NC_U16 did not lower to 16-bit add IR");
|
|
Check((result.ir_dump.find("ISubI16 v17.sdwa(sel=5") != std::string::npos),
|
|
"V_SUB_NC_I16 did not lower to 16-bit subtract IR");
|
|
Check((result.ir_dump.find("PackedSubI16 v14") != std::string::npos),
|
|
"V_PK_SUB_I16 did not lower to packed I16 subtract IR");
|
|
Check((result.ir_dump.find("PackedAddU16 v4") != std::string::npos),
|
|
"V_PK_ADD_U16 did not lower to packed U16 add IR");
|
|
Check((result.ir_dump.find("ConvertI16ToF16 v14") != std::string::npos),
|
|
"V_CVT_F16_I16 did not lower to signed I16-to-F16 conversion IR");
|
|
Check(
|
|
(result.ir_dump.find("ConvertU16ToF16 v4.sdwa(sel=5") != std::string::npos),
|
|
"V_CVT_F16_U16 SDWA did not lower to unsigned U16-to-F16 conversion IR");
|
|
Check((result.ir_dump.find("ConvertU16ToF16 v16") != std::string::npos),
|
|
"V_CVT_F16_U16 plain form did not lower to unsigned U16-to-F16 "
|
|
"conversion IR");
|
|
Check((result.ir_dump.find("ConvertF16ToI16 v4") != std::string::npos),
|
|
"V_CVT_I16_F16 did not lower to signed F16-to-I16 conversion IR");
|
|
Check((result.ir_dump.find("ConvertF16ToU16 v15") != std::string::npos),
|
|
"V_CVT_U16_F16 did not lower to unsigned F16-to-U16 conversion IR");
|
|
Check((result.ir_dump.find("IAddU32 v5") != std::string::npos) &&
|
|
(result.ir_dump.find("v4.sdwa(sel=4,sext=1") != std::string::npos),
|
|
"V_ADD_NC_U32 SDWA sign-extended low word did not lower to IR");
|
|
Check((result.ir_dump.find("IAddU32 v6") != std::string::npos) &&
|
|
(result.ir_dump.find("v6.sdwa(sel=5,sext=1") != std::string::npos),
|
|
"V_ADD_NC_U32 SDWA sign-extended high word did not lower to IR");
|
|
Check((result.ir_dump.find("IAddU32 v4.sdwa(sel=5,sext=0), vcc_lo, v97") != std::string::npos),
|
|
"captured V_ADD_NC_U32 high-word destination did not lower to IR");
|
|
Check((result.ir_dump.find("ISubU32 v11.sdwa(sel=2") != std::string::npos),
|
|
"V_SUB_NC_U32 SDWA byte-2 destination did not lower to IR");
|
|
Check((result.ir_dump.find("UMinU32 v10.sdwa(sel=4") != std::string::npos),
|
|
"V_MIN_U32 SDWA low-word destination did not lower to IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 128),
|
|
"SPIR-V binary does not contain OpIAdd for U16 operations");
|
|
Check(SpirvContainsOpcode(result.spirv, 202),
|
|
"SPIR-V binary does not contain OpBitFieldSExtract for SDWA sign "
|
|
"extension");
|
|
Check(SpirvContainsOpcode(result.spirv, 130),
|
|
"SPIR-V binary does not contain OpISub for I16 operations");
|
|
Check(SpirvContainsOpcode(result.spirv, 194),
|
|
"SPIR-V binary does not contain OpShiftRightLogical for B16 shift");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical for B16 shift");
|
|
Check(SpirvContainsOpcode(result.spirv, 197),
|
|
"SPIR-V binary does not contain OpBitwiseOr for packed U16 result");
|
|
Check(SpirvContainsOpcode(result.spirv, 124),
|
|
"SPIR-V binary does not contain OpConvertSToF for V_CVT_F16_I16");
|
|
Check(SpirvContainsOpcode(result.spirv, 109),
|
|
"SPIR-V binary does not contain OpConvertFToU for V_CVT_U16_F16");
|
|
Check(SpirvContainsOpcode(result.spirv, 110),
|
|
"SPIR-V binary does not contain OpConvertFToS for V_CVT_I16_F16");
|
|
Check(SpirvContainsOpcode(result.spirv, 112),
|
|
"SPIR-V binary does not contain OpConvertUToF for V_CVT_F16_U16");
|
|
Check(SpirvContainsExtInst(result.spirv, 43),
|
|
"SPIR-V binary does not contain GLSL.std.450 FClamp for clamped "
|
|
"V_PK_MUL_F16");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerScalarB64Alu() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 129), // s0 = 1
|
|
EncodeSMovB32(1, 130), // s1 = 2
|
|
EncodeSop1(0x07, 41, 0), // s_not_b32 s41, s0
|
|
EncodeSop1(0x04, 2, 0), // s_mov_b64 s[2:3], s[0:1]
|
|
EncodeSop1(0x08, 4, 2), // s_not_b64 s[4:5], s[2:3]
|
|
EncodeSop2(0x0f, 6, 2, 4), // s_and_b64 s[6:7], s[2:3], s[4:5]
|
|
EncodeSop2(0x11, 8, 6, 2), // s_or_b64 s[8:9], s[6:7], s[2:3]
|
|
EncodeSop2(0x13, 10, 8, 4), // s_xor_b64 s[10:11], s[8:9], s[4:5]
|
|
EncodeSop2(0x14, 36, 0, 1), // s_andn2_b32 s36, s0, s1
|
|
EncodeSop2(0x16, 37, 0, 1), // s_orn2_b32 s37, s0, s1
|
|
EncodeSop2(0x18, 38, 0, 1), // s_nand_b32 s38, s0, s1
|
|
EncodeSop2(0x1a, 39, 0, 1), // s_nor_b32 s39, s0, s1
|
|
EncodeSop2(0x1c, 40, 0, 1), // s_xnor_b32 s40, s0, s1
|
|
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
|
EncodeSop2(0x0b, 12, 10, 2), // s_cselect_b64 s[12:13], s[10:11], s[2:3]
|
|
EncodeSop2(0x15, 14, 10, 4), // s_andn2_b64 s[14:15], s[10:11], s[4:5]
|
|
EncodeSop2(0x17, 16, 14, 6), // s_orn2_b64 s[16:17], s[14:15], s[6:7]
|
|
EncodeSop2(0x19, 18, 16, 8), // s_nand_b64 s[18:19], s[16:17], s[8:9]
|
|
EncodeSop2(0x1b, 20, 18, 10), // s_nor_b64 s[20:21], s[18:19], s[10:11]
|
|
EncodeSop2(0x1d, 22, 20, 12), // s_xnor_b64 s[22:23], s[20:21], s[12:13]
|
|
EncodeSop2(0x1f, 24, 22, 1), // s_lshl_b64 s[24:25], s[22:23], s1
|
|
EncodeSop2(0x21, 26, 24, 0), // s_lshr_b64 s[26:27], s[24:25], s0
|
|
EncodeSop2(0x25, 28, 132, 130), // s_bfm_b64 s[28:29], 4, 2
|
|
EncodeSop1(0x10, 30, 26), // s_bcnt1_i32_b64 s30, s[26:27]
|
|
EncodeSop1(0x14, 31, 26), // s_ff1_i32_b64 s31, s[26:27]
|
|
EncodeSop1(0x16, 106, 26), // s_flbit_i32_b64 vcc_lo, s[26:27]
|
|
EncodeSop1(0x3b, 32, 30), // s_bitreplicate_b64_b32 s[32:33], s30
|
|
EncodeSop2(0x29, 34, 32, 255), // s_bfe_u64 s[34:35], s[32:33], 0x00040002
|
|
0x00040002u,
|
|
EncodeSop1(0x0a, 126, 126), // s_wqm_b64 exec, exec
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_mov_b64 s2, s0") != std::string::npos),
|
|
"new decoder did not decode old-backed S_MOV_B64");
|
|
Check((result.decoded_dump.find("s_not_b32 s41, s0") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_NOT_B32");
|
|
Check((result.decoded_dump.find("s_not_b64 s4, s2") != std::string::npos),
|
|
"new decoder did not decode old-backed S_NOT_B64");
|
|
Check((result.decoded_dump.find("s_and_b64 s6, s2, s4") != std::string::npos),
|
|
"new decoder did not decode old-backed S_AND_B64");
|
|
Check((result.decoded_dump.find("s_or_b64 s8, s6, s2") != std::string::npos),
|
|
"new decoder did not decode old-backed S_OR_B64");
|
|
Check((result.decoded_dump.find("s_xor_b64 s10, s8, s4") != std::string::npos),
|
|
"new decoder did not decode old-backed S_XOR_B64");
|
|
Check((result.decoded_dump.find("s_andn2_b32 s36, s0, s1") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_ANDN2_B32");
|
|
Check((result.decoded_dump.find("s_orn2_b32 s37, s0, s1") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_ORN2_B32");
|
|
Check((result.decoded_dump.find("s_nand_b32 s38, s0, s1") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_NAND_B32");
|
|
Check((result.decoded_dump.find("s_nor_b32 s39, s0, s1") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_NOR_B32");
|
|
Check((result.decoded_dump.find("s_xnor_b32 s40, s0, s1") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_XNOR_B32");
|
|
Check((result.decoded_dump.find("s_cselect_b64 s12, s10, s2") != std::string::npos),
|
|
"new decoder did not decode old-backed S_CSELECT_B64");
|
|
Check((result.decoded_dump.find("s_andn2_b64 s14, s10, s4") != std::string::npos),
|
|
"new decoder did not decode old-backed S_ANDN2_B64");
|
|
Check((result.decoded_dump.find("s_orn2_b64 s16, s14, s6") != std::string::npos),
|
|
"new decoder did not decode old-backed S_ORN2_B64");
|
|
Check((result.decoded_dump.find("s_nand_b64 s18, s16, s8") != std::string::npos),
|
|
"new decoder did not decode old-backed S_NAND_B64");
|
|
Check((result.decoded_dump.find("s_nor_b64 s20, s18, s10") != std::string::npos),
|
|
"new decoder did not decode old-backed S_NOR_B64");
|
|
Check((result.decoded_dump.find("s_xnor_b64 s22, s20, s12") != std::string::npos),
|
|
"new decoder did not decode old-backed S_XNOR_B64");
|
|
Check((result.decoded_dump.find("s_lshl_b64 s24, s22, s1") != std::string::npos),
|
|
"new decoder did not decode old-backed S_LSHL_B64");
|
|
Check((result.decoded_dump.find("s_lshr_b64 s26, s24, s0") != std::string::npos),
|
|
"new decoder did not decode old-backed S_LSHR_B64");
|
|
Check((result.decoded_dump.find("s_bfm_b64 s28, 4, 2") != std::string::npos),
|
|
"new decoder did not decode old-backed S_BFM_B64");
|
|
Check((result.decoded_dump.find("s_bcnt1_i32_b64 s30, s26") != std::string::npos),
|
|
"new decoder did not decode old-backed S_BCNT1_I32_B64");
|
|
Check((result.decoded_dump.find("s_ff1_i32_b64 s31, s26") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_FF1_I32_B64");
|
|
Check((result.decoded_dump.find("s_flbit_i32_b64 vcc_lo, s26") != std::string::npos),
|
|
"new decoder did not decode RDNA2 S_FLBIT_I32_B64");
|
|
Check((result.decoded_dump.find("s_bitreplicate_b64_b32 s32, s30") != std::string::npos),
|
|
"new decoder did not decode old-backed S_BITREPLICATE_B64_B32");
|
|
Check((result.decoded_dump.find("s_bfe_u64 s34, s32, 0x00040002") != std::string::npos),
|
|
"new decoder did not decode old-backed S_BFE_U64");
|
|
Check((result.decoded_dump.find("s_wqm_b64 exec_lo, exec_lo") != std::string::npos),
|
|
"new decoder did not decode old-backed S_WQM_B64");
|
|
Check((result.ir_dump.find("MoveU64 s2, s0") != std::string::npos),
|
|
"S_MOV_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseNotU32 s41, s0") != std::string::npos),
|
|
"S_NOT_B32 did not lower to scalar bitwise-not IR");
|
|
Check((result.ir_dump.find("BitwiseNotU64 s4, s2") != std::string::npos),
|
|
"S_NOT_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseAndU64 s6, s2, s4") != std::string::npos),
|
|
"S_AND_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseOrU64 s8, s6, s2") != std::string::npos),
|
|
"S_OR_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseXorU64 s10, s8, s4") != std::string::npos),
|
|
"S_XOR_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseAndNotU32 s36, s0, s1") != std::string::npos),
|
|
"S_ANDN2_B32 did not lower to scalar bitwise-and-not IR");
|
|
Check((result.ir_dump.find("BitwiseOrNotU32 s37, s0, s1") != std::string::npos),
|
|
"S_ORN2_B32 did not lower to scalar bitwise-or-not IR");
|
|
Check((result.ir_dump.find("BitwiseNandU32 s38, s0, s1") != std::string::npos),
|
|
"S_NAND_B32 did not lower to scalar bitwise-nand IR");
|
|
Check((result.ir_dump.find("BitwiseNorU32 s39, s0, s1") != std::string::npos),
|
|
"S_NOR_B32 did not lower to scalar bitwise-nor IR");
|
|
Check((result.ir_dump.find("BitwiseXnorU32 s40, s0, s1") != std::string::npos),
|
|
"S_XNOR_B32 did not lower to scalar bitwise-xnor IR");
|
|
Check((result.ir_dump.find("SelectU64 s12") != std::string::npos),
|
|
"S_CSELECT_B64 did not lower to paired-dword select IR");
|
|
Check((result.ir_dump.find("BitwiseAndNotU64 s14, s10, s4") != std::string::npos),
|
|
"S_ANDN2_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseOrNotU64 s16, s14, s6") != std::string::npos),
|
|
"S_ORN2_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseNandU64 s18, s16, s8") != std::string::npos),
|
|
"S_NAND_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseNorU64 s20, s18, s10") != std::string::npos),
|
|
"S_NOR_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitwiseXnorU64 s22, s20, s12") != std::string::npos),
|
|
"S_XNOR_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("ShiftLeftLogicalU64 s24, s22, s1") != std::string::npos),
|
|
"S_LSHL_B64 did not lower to paired-dword IR");
|
|
Check(
|
|
(result.ir_dump.find("ShiftRightLogicalU64 s26, s24, s0") != std::string::npos),
|
|
"S_LSHR_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitFieldMaskU64 s28") != std::string::npos),
|
|
"S_BFM_B64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitCountU64 s30, s26") != std::string::npos),
|
|
"S_BCNT1_I32_B64 did not lower to paired-source bit count IR");
|
|
Check((result.ir_dump.find("FindLsbU64 s31, s26") != std::string::npos),
|
|
"S_FF1_I32_B64 did not lower to paired-source bit search IR");
|
|
Check((result.ir_dump.find("FindMsbFromHighU64 vcc_lo, s26") != std::string::npos),
|
|
"S_FLBIT_I32_B64 did not lower to paired-source leading-zero IR");
|
|
Check((result.ir_dump.find("BitReplicateB64B32 s32, s30") != std::string::npos),
|
|
"S_BITREPLICATE_B64_B32 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("BitFieldExtractU64 s34, s32, 0x00040002") != std::string::npos),
|
|
"S_BFE_U64 did not lower to paired-dword IR");
|
|
Check((result.ir_dump.find("WqmB64 exec_lo, exec_lo") != std::string::npos),
|
|
"S_WQM_B64 did not lower to whole-quad-mask IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad for scalar B64 ops");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore for scalar B64 ops");
|
|
Check(SpirvContainsOpcode(result.spirv, 197),
|
|
"SPIR-V binary does not contain OpBitwiseOr for scalar B64 ops");
|
|
Check(SpirvContainsOpcode(result.spirv, 198),
|
|
"SPIR-V binary does not contain OpBitwiseXor for scalar B64 ops");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd for scalar B64 ops");
|
|
Check(SpirvContainsOpcode(result.spirv, 200),
|
|
"SPIR-V binary does not contain OpNot for scalar B64 ops");
|
|
Check(SpirvContainsOpcode(result.spirv, 169),
|
|
"SPIR-V binary does not contain OpSelect for scalar B64 select");
|
|
Check(SpirvContainsOpcode(result.spirv, 171),
|
|
"SPIR-V binary does not contain OpINotEqual for scalar B64 whole-quad "
|
|
"mask");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical for scalar B64 "
|
|
"shifts");
|
|
Check(SpirvContainsOpcode(result.spirv, 194),
|
|
"SPIR-V binary does not contain OpShiftRightLogical for scalar B64 "
|
|
"shifts");
|
|
Check(SpirvContainsOpcode(result.spirv, 201),
|
|
"SPIR-V binary does not contain OpBitFieldInsert for scalar B64 mask");
|
|
Check(SpirvContainsOpcode(result.spirv, 203),
|
|
"SPIR-V binary does not contain OpBitFieldUExtract for scalar B64 "
|
|
"extract");
|
|
Check(SpirvContainsOpcode(result.spirv, 205),
|
|
"SPIR-V binary does not contain OpBitCount for scalar B64 bit count");
|
|
uint32_t componentwise_u64_values = 0;
|
|
for (const auto *block : result.program.blocks) {
|
|
for (const auto &value : *block) {
|
|
switch (value.GetOpcode()) {
|
|
case ShaderRecompiler::IR::ValueOpcode::BitwiseAnd64:
|
|
componentwise_u64_values++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Check(
|
|
componentwise_u64_values == 0u,
|
|
"architectural B64 lane operations retained vector pack/unpack traffic");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerScalarB64LaneTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeSop1(0x04, 2, 0), // s_mov_b64 s[2:3], s[0:1]
|
|
EncodeSop1(0x08, 4, 2), // s_not_b64 s[4:5], s[2:3]
|
|
EncodeSop2(0x0f, 6, 2, 4), // s_and_b64 s[6:7], s[2:3], s[4:5]
|
|
EncodeSop2(0x11, 8, 6, 2), // s_or_b64 s[8:9], s[6:7], s[2:3]
|
|
EncodeSop2(0x13, 10, 8, 4), // s_xor_b64 s[10:11], s[8:9], s[4:5]
|
|
EncodeSopc(0x06, 0, 1), // s_cmp_eq_u32 s0, s1
|
|
EncodeSop2(0x0b, 12, 10, 2), // s_cselect_b64 s[12:13], s[10:11], s[2:3]
|
|
EncodeSop2(0x15, 14, 12, 4), // s_andn2_b64 s[14:15], s[12:13], s[4:5]
|
|
EncodeVop1(0x01, 0, 14), // v_mov_b32 v0, s14
|
|
EncodeExp0(0x0c, 0x1), EncodeExp1(0, 0, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
const uint32_t user_data[] = {0x01234567u, 0x89abcdefu};
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.user_data = user_data;
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
uint32_t componentwise_u64_values = 0;
|
|
for (const auto *block : result.program.blocks) {
|
|
for (const auto &value : *block) {
|
|
switch (value.GetOpcode()) {
|
|
case ShaderRecompiler::IR::ValueOpcode::BitwiseAnd64:
|
|
componentwise_u64_values++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Check(
|
|
componentwise_u64_values == 0u,
|
|
"architectural B64 lane operations retained vector pack/unpack traffic");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestFloatComparisonInputModes() {
|
|
using namespace ShaderRecompiler;
|
|
const uint32_t shader[] = {
|
|
EncodeVopc(0x02, 256, 1), // v_cmp_eq_f32 v0, v1
|
|
EncodeVopc(0xca, 256, 1), // v_cmp_eq_f16 v0, v1
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(shader, decoded);
|
|
for (const uint8_t mode : {0xc0, 0xd0, 0xe0, 0xf0}) {
|
|
ShaderComputeInputInfo compute{};
|
|
compute.float_mode = mode;
|
|
Frontend::TranslateOptions options{.stage = ShaderType::Compute};
|
|
options.input_info.compute = &compute;
|
|
const auto program = Frontend::TranslateProgram(decoded, CFG::BuildGraph(decoded), options);
|
|
uint32_t comparisons = 0;
|
|
for (const auto* block : program.blocks) {
|
|
for (const auto& inst : *block) {
|
|
if (inst.GetOpcode() != IR::ValueOpcode::FPOrdEqual32) continue;
|
|
Check(inst.Flags<IR::FPCompareFlags>().flush_input_denorms ==
|
|
(comparisons == 0 && (mode & 0x10u) == 0),
|
|
"compute input mode leaked into preserved or promoted F16 comparison");
|
|
comparisons++;
|
|
}
|
|
}
|
|
Check(comparisons == 2, "comparison input-mode fixture did not translate both precisions");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerSignedCompareAlu() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 193), // s0 = -1
|
|
EncodeSMovB32(1, 129), // s1 = 1
|
|
EncodeSopc(0x02, 0, 1), // s_cmp_gt_i32 s0, s1
|
|
EncodeSopk(0x07, 0, -2), // s_cmp_lt_i32 s0, -2
|
|
EncodeVopc(0x84, 0, 1), // v_cmp_gt_i32 s0, v1
|
|
EncodeVopc(0x81, 0, 1), // v_cmp_lt_i32 s0, v1
|
|
0x7d130ef9u,
|
|
0x86068413u, // v_cmp_lt_i16 vcc_lo.sdwa, v19.word0, v7.word1
|
|
0x7d1d02f9u,
|
|
0x86068213u, // v_cmp_ge_i16 vcc_lo.sdwa, v19.word0, v1.word1
|
|
EncodeVopc(0xa9, 0, 1), // v_cmp_lt_u16 s0, v1
|
|
EncodeVopc(0x94, 0, 1), // v_cmpx_gt_i32 s0, v1
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_cmp_gt_i32") != std::string::npos),
|
|
"new decoder did not decode SOPC signed compare");
|
|
Check((result.decoded_dump.find("s_cmp_lt_i32") != std::string::npos),
|
|
"new decoder did not decode SOPK signed compare");
|
|
Check((result.decoded_dump.find("v_cmp_gt_i32") != std::string::npos),
|
|
"new decoder did not decode VOPC signed greater-than compare");
|
|
Check((result.decoded_dump.find("v_cmp_lt_i32") != std::string::npos),
|
|
"new decoder did not decode VOPC signed less-than compare");
|
|
Check((result.decoded_dump.find("v_cmp_lt_i16") != std::string::npos),
|
|
"new decoder did not decode VOPC signed halfword less-than compare");
|
|
Check((result.decoded_dump.find("v_cmp_ge_i16") != std::string::npos),
|
|
"new decoder did not decode VOPC signed halfword greater-or-equal "
|
|
"compare");
|
|
Check((result.decoded_dump.find("v_cmp_lt_u16") != std::string::npos),
|
|
"new decoder did not decode VOPC unsigned halfword less-than compare");
|
|
Check((result.decoded_dump.find("v_cmpx_gt_i32") != std::string::npos),
|
|
"new decoder did not decode VOPC signed compare-and-mask");
|
|
Check((result.ir_dump.find("CompareGtI32") != std::string::npos),
|
|
"signed greater-than compare did not lower to IR");
|
|
Check((result.ir_dump.find("CompareLtI32") != std::string::npos),
|
|
"signed less-than compare did not lower to IR");
|
|
Check((result.ir_dump.find("CompareLtI16") != std::string::npos),
|
|
"signed halfword less-than compare did not lower to IR");
|
|
Check((result.ir_dump.find("CompareGeI16") != std::string::npos),
|
|
"signed halfword greater-or-equal compare did not lower to IR");
|
|
Check((result.ir_dump.find("CompareLtU16") != std::string::npos),
|
|
"unsigned halfword less-than compare did not lower to IR");
|
|
Check((result.ir_dump.find("CompareMaskGtI32 exec_lo") != std::string::npos),
|
|
"signed compare-and-mask did not lower to exec mask IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 173),
|
|
"SPIR-V binary does not contain OpSGreaterThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 177),
|
|
"SPIR-V binary does not contain OpSLessThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 202),
|
|
"SPIR-V binary does not contain OpBitFieldSExtract for I16 compare");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerSignedMinShiftAlu() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 193), // s0 = -1
|
|
EncodeSMovB32(1, 130), // s1 = 2
|
|
EncodeSop2(0x06, 2, 0, 1), // s_min_i32 s2, s0, s1
|
|
EncodeSop2(0x08, 3, 0, 1), // s_max_i32 s3, s0, s1
|
|
EncodeSop2(0x22, 4, 0, 129), // s_ashr_i32 s4, s0, 1
|
|
EncodeVop2(0x11, 1, 0, 0), // v_min_i32 v1, s0, v0
|
|
EncodeVop2(0x12, 2, 1, 1), // v_max_i32 v2, s1, v1
|
|
EncodeVop2(0x17, 3, 1 + 256, 1), // v_ashr_i32 v3, v1, v1
|
|
EncodeVop2(0x18, 4, 129, 3), // v_ashrrev_i32 v4, 1, v3
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_min_i32") != std::string::npos),
|
|
"new decoder did not decode S_MIN_I32");
|
|
Check((result.decoded_dump.find("s_ashr_i32") != std::string::npos),
|
|
"new decoder did not decode S_ASHR_I32");
|
|
Check((result.decoded_dump.find("v_min_i32") != std::string::npos),
|
|
"new decoder did not decode V_MIN_I32");
|
|
Check((result.decoded_dump.find("v_ashrrev_i32") != std::string::npos),
|
|
"new decoder did not decode V_ASHRREV_I32");
|
|
Check((result.ir_dump.find("IMinI32 s2") != std::string::npos),
|
|
"S_MIN_I32 did not lower to IR");
|
|
Check((result.ir_dump.find("IMaxI32 s3") != std::string::npos),
|
|
"S_MAX_I32 did not lower to IR");
|
|
Check((result.ir_dump.find("ShiftRightArithmeticI32 s4") != std::string::npos),
|
|
"S_ASHR_I32 did not lower to IR");
|
|
Check((result.ir_dump.find("IMinI32 v1") != std::string::npos),
|
|
"V_MIN_I32 did not lower to IR");
|
|
Check((result.ir_dump.find("IMaxI32 v2") != std::string::npos),
|
|
"V_MAX_I32 did not lower to IR");
|
|
Check((result.ir_dump.find("ShiftRightArithmeticI32 v4, v3, 0x00000001") != std::string::npos),
|
|
"V_ASHRREV_I32 did not reverse source order in IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 173),
|
|
"SPIR-V binary does not contain OpSGreaterThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 177),
|
|
"SPIR-V binary does not contain OpSLessThan");
|
|
Check(SpirvContainsOpcode(result.spirv, 195),
|
|
"SPIR-V binary does not contain OpShiftRightArithmetic");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerScalarBitfieldAlu() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 193), // s0 = -1
|
|
EncodeSMovB32(1, 129), // s1 = 1
|
|
EncodeSopc(0x02, 1, 0), // s_cmp_gt_i32 s1, s0
|
|
EncodeSop2(0x0a, 2, 1, 0), // s_cselect_b32 s2, s1, s0
|
|
EncodeSop1(0x34, 3, 0), // s_abs_i32 s3, s0
|
|
EncodeSop1(0x0b, 4, 1), // s_brev_b32 s4, s1
|
|
EncodeSop2(0x24, 5, 132, 129), // s_bfm_b32 s5, 4, 1
|
|
EncodeSop2(0x27, 6, 4, 255), // s_bfe_u32 s6, s4, literal
|
|
0x00080004u,
|
|
EncodeSop2(0x32, 7, 0, 1), // s_pack_ll_b32_b16 s7, s0, s1
|
|
EncodeSop2(0x33, 8, 0, 1), // s_pack_lh_b32_b16 s8, s0, s1
|
|
EncodeSop2(0x34, 9, 0, 1), // s_pack_hh_b32_b16 s9, s0, s1
|
|
EncodeSopc(0x0c, 0, 1), // s_bitcmp0_b32 s0, s1
|
|
EncodeSopc(0x0d, 1, 0), // s_bitcmp1_b32 s1, s0
|
|
EncodeSopc(0x13, 0, 2), // s_cmp_lg_u64 s[0:1], s[2:3]
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_cselect_b32") != std::string::npos),
|
|
"new decoder did not decode S_CSELECT_B32");
|
|
Check((result.decoded_dump.find("s_abs_i32") != std::string::npos),
|
|
"new decoder did not decode S_ABS_I32");
|
|
Check((result.decoded_dump.find("s_brev_b32") != std::string::npos),
|
|
"new decoder did not decode S_BREV_B32");
|
|
Check((result.decoded_dump.find("s_bfm_b32") != std::string::npos),
|
|
"new decoder did not decode S_BFM_B32");
|
|
Check((result.decoded_dump.find("s_bfe_u32") != std::string::npos),
|
|
"new decoder did not decode S_BFE_U32");
|
|
Check((result.decoded_dump.find("s_pack_hh_b32_b16") != std::string::npos),
|
|
"new decoder did not decode S_PACK_HH_B32_B16");
|
|
Check((result.decoded_dump.find("s_bitcmp0_b32") != std::string::npos),
|
|
"new decoder did not decode S_BITCMP0_B32");
|
|
Check((result.decoded_dump.find("s_bitcmp1_b32") != std::string::npos),
|
|
"new decoder did not decode S_BITCMP1_B32");
|
|
Check((result.decoded_dump.find("s_cmp_lg_u64") != std::string::npos),
|
|
"new decoder did not decode S_CMP_LG_U64");
|
|
Check((result.ir_dump.find("SelectU32 s2") != std::string::npos),
|
|
"S_CSELECT_B32 did not lower to IR");
|
|
Check((result.ir_dump.find("AbsI32 s3") != std::string::npos),
|
|
"S_ABS_I32 did not lower to IR");
|
|
Check((result.ir_dump.find("BitReverseU32 s4") != std::string::npos),
|
|
"S_BREV_B32 did not lower to IR");
|
|
Check((result.ir_dump.find("BitFieldMaskU32 s5") != std::string::npos),
|
|
"S_BFM_B32 did not lower to IR");
|
|
Check((result.ir_dump.find("BitFieldExtractU32 s6") != std::string::npos),
|
|
"S_BFE_U32 did not lower to IR");
|
|
Check((result.ir_dump.find("PackLowLowU16 s7") != std::string::npos),
|
|
"S_PACK_LL_B32_B16 did not lower to IR");
|
|
Check((result.ir_dump.find("PackLowHighU16 s8") != std::string::npos),
|
|
"S_PACK_LH_B32_B16 did not lower to IR");
|
|
Check((result.ir_dump.find("PackHighHighU16 s9") != std::string::npos),
|
|
"S_PACK_HH_B32_B16 did not lower to IR");
|
|
Check((result.ir_dump.find("BitCompare0B32") != std::string::npos),
|
|
"S_BITCMP0_B32 did not lower to IR");
|
|
Check((result.ir_dump.find("BitCompare1B32") != std::string::npos),
|
|
"S_BITCMP1_B32 did not lower to IR");
|
|
Check((result.ir_dump.find("CompareNeU64") != std::string::npos),
|
|
"S_CMP_LG_U64 did not lower to IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 126),
|
|
"SPIR-V binary does not contain OpSNegate");
|
|
Check(SpirvContainsOpcode(result.spirv, 169),
|
|
"SPIR-V binary does not contain OpSelect");
|
|
Check(SpirvContainsOpcode(result.spirv, 201),
|
|
"SPIR-V binary does not contain OpBitFieldInsert");
|
|
Check(SpirvContainsOpcode(result.spirv, 203),
|
|
"SPIR-V binary does not contain OpBitFieldUExtract");
|
|
Check(SpirvContainsOpcode(result.spirv, 171),
|
|
"SPIR-V binary does not contain OpINotEqual");
|
|
Check(SpirvContainsOpcode(result.spirv, 166),
|
|
"SPIR-V binary does not contain OpLogicalOr");
|
|
Check(SpirvContainsOpcode(result.spirv, 204),
|
|
"SPIR-V binary does not contain OpBitReverse");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void CheckNewDecoderUnsupported(const uint32_t *shader, uint32_t words,
|
|
const char *family, const char *opcode_name) {
|
|
ShaderRecompiler::Decoder::Program program;
|
|
const std::span code{shader, words};
|
|
ShaderRecompiler::Decoder::DecodeProgram(code, program);
|
|
Check(program.instructions.size() >= 2,
|
|
"decoder did not return instruction plus endpgm");
|
|
const auto text = ShaderRecompiler::Decoder::ProgramToString(program);
|
|
Check((text.find(family) != std::string::npos),
|
|
"decoder unsupported text did not include opcode family");
|
|
Check((text.find(opcode_name) != std::string::npos),
|
|
"decoder unsupported text did not include opcode name");
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
ExpectFatal([&] { (void)RecompileForTest(code, options); },
|
|
"unsupported opcode did not terminate shader compilation");
|
|
#endif
|
|
}
|
|
|
|
void TestScalarAshrI64Decoder() {
|
|
using namespace ShaderRecompiler::Decoder;
|
|
|
|
const uint32_t shader[] = {
|
|
0x91860204u, // s_ashr_i64 s[6:7], s[4:5], s2
|
|
EncodeSop2(0x23, 8, 255, 129), 0xfffffffeu,
|
|
EncodeSop2(0x23, 10, 8, 255), 0xffffffffu,
|
|
EncodeSop2(0x23, 12, 255, 255), 0xffffffc0u,
|
|
EncodeSopp(0x01),
|
|
};
|
|
Program program;
|
|
ShaderRecompiler::Decoder::DecodeProgram(shader, program);
|
|
Check(program.instructions.size() == 5u,
|
|
"S_ASHR_I64 decoder lost instruction boundaries around literals");
|
|
const auto ® = program.instructions[0];
|
|
Check(reg.family == Family::SOP2 && reg.opcode == Opcode::S_ASHR_I64 &&
|
|
reg.opcode_id == 0x23u && reg.pc == 0u && reg.word_count == 1u &&
|
|
reg.src_count == 2u && reg.dst.kind == OperandKind::Sgpr &&
|
|
reg.dst.reg == 6u && reg.src0.kind == OperandKind::Sgpr &&
|
|
reg.src0.reg == 4u && reg.src1.kind == OperandKind::Sgpr &&
|
|
reg.src1.reg == 2u,
|
|
"S_ASHR_I64 register encoding was decoded incorrectly");
|
|
const auto &source_literal = program.instructions[1];
|
|
const auto &count_literal = program.instructions[2];
|
|
const auto &shared_literal = program.instructions[3];
|
|
for (const auto *inst : {&source_literal, &count_literal, &shared_literal}) {
|
|
Check(inst->opcode == Opcode::S_ASHR_I64 && inst->word_count == 2u &&
|
|
inst->src_count == 2u,
|
|
"S_ASHR_I64 literal encoding was decoded incorrectly");
|
|
}
|
|
Check(source_literal.pc == 4u &&
|
|
source_literal.src0.kind == OperandKind::LiteralConstant &&
|
|
source_literal.src0.value == 0xfffffffeu &&
|
|
source_literal.src1.kind == OperandKind::IntegerInlineConstant &&
|
|
source_literal.src1.value == 1u && count_literal.pc == 12u &&
|
|
count_literal.src1.kind == OperandKind::LiteralConstant &&
|
|
count_literal.src1.value == 0xffffffffu &&
|
|
shared_literal.pc == 20u &&
|
|
shared_literal.src0.kind == OperandKind::LiteralConstant &&
|
|
shared_literal.src1.kind == OperandKind::LiteralConstant &&
|
|
shared_literal.src0.value == 0xffffffc0u &&
|
|
shared_literal.src1.value == 0xffffffc0u &&
|
|
program.instructions[4].pc == 28u &&
|
|
program.instructions[4].opcode == Opcode::S_ENDPGM,
|
|
"S_ASHR_I64 decoder mishandled source, count, or shared literals");
|
|
Check((ProgramToString(program).find("S_ASHR_I64 s6, s4, s2") != std::string::npos),
|
|
"S_ASHR_I64 is missing from the decoded dump");
|
|
}
|
|
|
|
void TestImageAtomicWidthDecoder() {
|
|
using namespace ShaderRecompiler::Decoder;
|
|
// Captured PPSA29343 pixel shader: image_atomic_umax v[2:3], v[0:1], s[0:7].
|
|
const uint32_t captured[] = {0xf05c0308u, 0x00000200u};
|
|
Instruction decoded;
|
|
DecodeInstruction(captured, 0, decoded);
|
|
Check(decoded.opcode == Opcode::IMAGE_ATOMIC_UMAX &&
|
|
decoded.data_bits == 64u && decoded.data_dwords == 2u &&
|
|
decoded.dst.reg == 2u && decoded.dmask == 3u && !decoded.glc,
|
|
"captured image atomic lost its 64-bit data width");
|
|
|
|
for (const auto opcode : {0x0fu, 0x10u, 0x11u, 0x14u, 0x15u, 0x16u,
|
|
0x17u, 0x18u, 0x19u, 0x1au, 0x1eu, 0x1fu}) {
|
|
const bool compare_swap = opcode == 0x10u;
|
|
const bool supports_64 = opcode != 0x10u && opcode != 0x14u &&
|
|
opcode != 0x16u && opcode != 0x1eu && opcode != 0x1fu;
|
|
for (uint32_t mask = 0; mask < 16u; ++mask) {
|
|
for (const bool glc : {false, true}) {
|
|
const uint32_t words[] = {EncodeMimg0(opcode, mask, glc), captured[1]};
|
|
DecodeInstruction(words, 0, decoded);
|
|
const bool supported = mask == (compare_swap ? 3u : 1u) ||
|
|
(supports_64 && mask == 3u);
|
|
Check((decoded.opcode != Opcode::UNSUPPORTED) == supported,
|
|
"image atomic accepted an invalid or unsupported width mask");
|
|
if (supported) {
|
|
const auto bits = !compare_swap && mask == 3u ? 64u : 32u;
|
|
Check(decoded.data_bits == bits && decoded.glc == glc &&
|
|
decoded.data_dwords == (compare_swap ? 2u : bits / 32u),
|
|
"image atomic DMASK selected the wrong data width or return mode");
|
|
}
|
|
const uint32_t d16_words[] = {words[0], words[1] | (1u << 31u)};
|
|
DecodeInstruction(d16_words, 0, decoded);
|
|
Check(decoded.opcode == Opcode::UNSUPPORTED,
|
|
"image atomic incorrectly accepted D16 data");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestNewShaderDecoderArchitecture() {
|
|
using namespace ShaderRecompiler::Decoder;
|
|
|
|
Check(GetInstructionFamily(EncodeVop1(0x01, 2, 3)) == Family::VOP1,
|
|
"decoder did not classify compact VOP1 directly");
|
|
Check(GetInstructionFamily(EncodeVopc(0x02, 2, 3)) == Family::VOPC,
|
|
"decoder did not classify compact VOPC directly");
|
|
Check(GetInstructionFamily(EncodeDs0(0x36)) == Family::DS,
|
|
"decoder did not classify DS directly");
|
|
|
|
const uint32_t orn2_saveexec_code[] = {0xbeea407eu};
|
|
Instruction orn2_saveexec;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(orn2_saveexec_code, 0u, orn2_saveexec);
|
|
Check(orn2_saveexec.family == Family::SOP1 &&
|
|
orn2_saveexec.opcode == Opcode::S_ORN2_SAVEEXEC_B32 &&
|
|
orn2_saveexec.word_count == 1u && orn2_saveexec.src_count == 1u &&
|
|
orn2_saveexec.dst.kind == OperandKind::VccLo &&
|
|
orn2_saveexec.src0.kind == OperandKind::ExecLo,
|
|
"decoder rejected or misdecoded captured S_ORN2_SAVEEXEC_B32");
|
|
|
|
const uint32_t offset_code[] = {0u, EncodeVop1(0x01, 2, 3)};
|
|
Instruction direct;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(offset_code, 1u, direct);
|
|
Check(direct.pc == 4u && direct.family == Family::VOP1 &&
|
|
direct.opcode == Opcode::V_MOV_B32 && direct.dst.reg == 2u &&
|
|
direct.src0.reg == 3u,
|
|
"single-instruction decoder failed at a nonzero offset");
|
|
|
|
const uint32_t program_code[] = {EncodeVop1(0x01, 2, 3), EncodeSopp(0x01, 0)};
|
|
Instruction program_direct;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(program_code, 0u,
|
|
program_direct);
|
|
Program program;
|
|
ShaderRecompiler::Decoder::DecodeProgram(program_code, program);
|
|
Check(program.instructions.size() == 2u &&
|
|
program.instructions.front().family == program_direct.family &&
|
|
program.instructions.front().opcode == program_direct.opcode &&
|
|
program.instructions.front().word_count ==
|
|
program_direct.word_count,
|
|
"program decoder diverged from the single-instruction decoder");
|
|
|
|
const uint32_t cmp_eq_u64_code[] = {0xd4e2006au, 0x0000d47eu};
|
|
Instruction cmp_eq_u64;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(cmp_eq_u64_code, 0u, cmp_eq_u64);
|
|
Check(cmp_eq_u64.family == Family::VOP3 &&
|
|
cmp_eq_u64.opcode == Opcode::V_CMP_EQ_U64 &&
|
|
cmp_eq_u64.dst.kind == OperandKind::VccLo &&
|
|
cmp_eq_u64.src_count == 2u &&
|
|
cmp_eq_u64.src0.kind == OperandKind::ExecLo &&
|
|
cmp_eq_u64.src1.kind == OperandKind::VccLo,
|
|
"decoder rejected the captured VOP3 V_CMP_EQ_U64 instruction");
|
|
|
|
const uint32_t completed_pr_opcodes[] = {
|
|
EncodeSop1(0x15, 0, 1), // s_flbit_i32_b32 s0, s1
|
|
EncodeVopc(0xe1, 128, 0), // v_cmp_lt_u64 vcc, 0, v[0:1]
|
|
EncodeSopp(0x01, 0),
|
|
};
|
|
Instruction flbit_i32_b32;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(completed_pr_opcodes, 0u,
|
|
flbit_i32_b32);
|
|
Instruction cmp_lt_u64;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(completed_pr_opcodes, 1u,
|
|
cmp_lt_u64);
|
|
Check(flbit_i32_b32.family == Family::SOP1 &&
|
|
flbit_i32_b32.opcode == Opcode::S_FLBIT_I32_B32 &&
|
|
flbit_i32_b32.dst.kind == OperandKind::Sgpr &&
|
|
flbit_i32_b32.dst.reg == 0u &&
|
|
flbit_i32_b32.src0.kind == OperandKind::Sgpr &&
|
|
flbit_i32_b32.src0.reg == 1u &&
|
|
cmp_lt_u64.family == Family::VOPC &&
|
|
cmp_lt_u64.opcode == Opcode::V_CMP_LT_U64 &&
|
|
cmp_lt_u64.dst.kind == OperandKind::VccLo,
|
|
"decoder rejected completed PR #427 scalar/vector opcodes");
|
|
|
|
const auto completed_options = MakeCompileOptions(ShaderType::Compute);
|
|
const auto completed_result =
|
|
RecompileForTest(completed_pr_opcodes, completed_options);
|
|
Check((completed_result.decoded_dump.find("S_FLBIT_I32_B32 s0, s1") != std::string::npos) &&
|
|
(completed_result.decoded_dump.find("V_CMP_LT_U64 vcc_lo, 0, v0") != std::string::npos),
|
|
"completed PR #427 opcodes were absent from the decoded shader");
|
|
|
|
const uint32_t ffbh_i32_code[] = {0x7e1c770eu};
|
|
Instruction ffbh_i32;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(ffbh_i32_code, 0u, ffbh_i32);
|
|
Check(ffbh_i32.family == Family::VOP1 &&
|
|
ffbh_i32.opcode == Opcode::V_FFBH_I32 &&
|
|
ffbh_i32.dst.kind == OperandKind::Vgpr && ffbh_i32.dst.reg == 14u &&
|
|
ffbh_i32.src_count == 1u &&
|
|
ffbh_i32.src0.kind == OperandKind::Vgpr &&
|
|
ffbh_i32.src0.reg == 14u,
|
|
"decoder rejected the captured VOP1 V_FFBH_I32 instruction");
|
|
|
|
const uint32_t cos_f16_code[] = {0x7e08c2f9u, 0x00051504u};
|
|
Instruction cos_f16;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(cos_f16_code, 0u, cos_f16);
|
|
Check(cos_f16.family == Family::VOP1 &&
|
|
cos_f16.opcode == Opcode::V_COS_F16 &&
|
|
cos_f16.word_count == 2u && cos_f16.src_count == 1u &&
|
|
cos_f16.dst.kind == OperandKind::Vgpr && cos_f16.dst.reg == 4u &&
|
|
cos_f16.dst.sdwa_sel == 5u &&
|
|
cos_f16.dst.sdwa_dst_unused == 2u && !cos_f16.dst.clamp &&
|
|
cos_f16.dst.omod == 0u &&
|
|
cos_f16.src0.kind == OperandKind::Vgpr &&
|
|
cos_f16.src0.reg == 4u && cos_f16.src0.sdwa_sel == 5u &&
|
|
!cos_f16.src0.sdwa_sext && !cos_f16.src0.negate &&
|
|
!cos_f16.src0.absolute,
|
|
"decoder rejected the captured VOP1 SDWA V_COS_F16 instruction");
|
|
|
|
const uint32_t sin_f16_code[] = {0x7e08c0f9u, 0x00051504u};
|
|
Instruction sin_f16;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(sin_f16_code, 0u, sin_f16);
|
|
Check(sin_f16.family == Family::VOP1 &&
|
|
sin_f16.opcode == Opcode::V_SIN_F16 &&
|
|
sin_f16.word_count == 2u && sin_f16.src_count == 1u &&
|
|
sin_f16.dst.kind == OperandKind::Vgpr && sin_f16.dst.reg == 4u &&
|
|
sin_f16.dst.sdwa_sel == 5u &&
|
|
sin_f16.dst.sdwa_dst_unused == 2u && !sin_f16.dst.clamp &&
|
|
sin_f16.dst.omod == 0u &&
|
|
sin_f16.src0.kind == OperandKind::Vgpr &&
|
|
sin_f16.src0.reg == 4u && sin_f16.src0.sdwa_sel == 5u &&
|
|
!sin_f16.src0.sdwa_sext && !sin_f16.src0.negate &&
|
|
!sin_f16.src0.absolute,
|
|
"decoder rejected the VOP1 SDWA V_SIN_F16 instruction");
|
|
|
|
std::vector<uint32_t> fract_f16_code(0x9b0u / 4u, EncodeSopp(0x00));
|
|
fract_f16_code.push_back(0x7e12bf07u);
|
|
fract_f16_code.push_back(EncodeSopp(0x01));
|
|
Instruction fract_f16;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(fract_f16_code, 0x9b0u / 4u,
|
|
fract_f16);
|
|
Check(fract_f16.pc == 0x9b0u && fract_f16.family == Family::VOP1 &&
|
|
fract_f16.opcode == Opcode::V_FRACT_F16 &&
|
|
fract_f16.opcode_id == 0x5fu && fract_f16.word_count == 1u &&
|
|
fract_f16.dst.kind == OperandKind::Vgpr && fract_f16.dst.reg == 9u &&
|
|
fract_f16.src_count == 1u &&
|
|
fract_f16.src0.kind == OperandKind::Vgpr && fract_f16.src0.reg == 7u,
|
|
"decoder rejected captured V_FRACT_F16 at pc 0x9b0");
|
|
ShaderRecompiler::Decoder::Program fract_program;
|
|
ShaderRecompiler::Decoder::DecodeProgram(fract_f16_code, fract_program);
|
|
Check(!ShaderRecompiler::CFG::BuildGraph(fract_program).unsupported,
|
|
"captured V_FRACT_F16 still fails CFG construction");
|
|
|
|
const uint32_t literal_code[] = {EncodeVop1(0x01, 2, 255u), 0x12345678u};
|
|
Instruction literal;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(literal_code, 0u, literal);
|
|
Check(literal.word_count == 2u && literal.src0.value == 0x12345678u,
|
|
"single-instruction decoder lost a compact literal extension");
|
|
|
|
const uint32_t scalar_signed_bfe[] = {
|
|
0x946aff14u, // s_bfe_i32 vcc_lo, s20, 0x0004001c
|
|
0x0004001cu,
|
|
};
|
|
Instruction signed_bfe;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(scalar_signed_bfe, 0u,
|
|
signed_bfe);
|
|
Check(signed_bfe.family == Family::SOP2 &&
|
|
signed_bfe.opcode == Opcode::S_BFE_I32 &&
|
|
signed_bfe.word_count == 2u &&
|
|
signed_bfe.dst.kind == OperandKind::VccLo &&
|
|
signed_bfe.src0.reg == 20u && signed_bfe.src1.value == 0x0004001cu,
|
|
"decoder rejected or misdecoded captured S_BFE_I32 instruction");
|
|
|
|
const uint32_t vop2_sdwa_partial_dst[] = {
|
|
0x4a08c2f9u,
|
|
0x0686156au, // v_add_nc_u32 v4.word1, vcc_lo, v97; preserve word0
|
|
};
|
|
Instruction sdwa_add;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(vop2_sdwa_partial_dst, 0u,
|
|
sdwa_add);
|
|
Check(sdwa_add.family == Family::VOP2 &&
|
|
sdwa_add.opcode == Opcode::V_ADD_NC_U32 &&
|
|
sdwa_add.word_count == 2u && sdwa_add.dst.reg == 4u &&
|
|
sdwa_add.dst.sdwa_sel == 5u && sdwa_add.dst.sdwa_dst_unused == 2u &&
|
|
sdwa_add.src0.kind == OperandKind::VccLo &&
|
|
sdwa_add.src1.kind == OperandKind::Vgpr && sdwa_add.src1.reg == 97u,
|
|
"decoder rejected or misdecoded captured partial-destination "
|
|
"V_ADD_NC_U32");
|
|
|
|
const uint32_t vop2_sdwa_byte_shift[] = {
|
|
0x2c0910f9u,
|
|
0x86000613u, // v_lshrrev_b32 v4, v19.byte0, 8
|
|
};
|
|
Instruction byte_shift;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(vop2_sdwa_byte_shift, 0u,
|
|
byte_shift);
|
|
Check(byte_shift.family == Family::VOP2 &&
|
|
byte_shift.opcode == Opcode::V_LSHRREV_B32 &&
|
|
byte_shift.word_count == 2u && byte_shift.dst.reg == 4u &&
|
|
byte_shift.src0.kind == OperandKind::Vgpr &&
|
|
byte_shift.src0.reg == 19u && byte_shift.src0.sdwa_sel == 0u &&
|
|
byte_shift.src1.kind == OperandKind::IntegerInlineConstant &&
|
|
byte_shift.src1.value == 8u,
|
|
"decoder rejected or misdecoded captured V_LSHRREV_B32 byte shift");
|
|
|
|
const uint32_t vop2_sdwa_byte_left_shift[] = {
|
|
0x340d02f9u,
|
|
0x86030607u, // v_lshlrev_b32 v6, v7.byte3, 1
|
|
};
|
|
Instruction byte_left_shift;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(vop2_sdwa_byte_left_shift, 0u,
|
|
byte_left_shift);
|
|
Check(byte_left_shift.family == Family::VOP2 &&
|
|
byte_left_shift.opcode == Opcode::V_LSHLREV_B32 &&
|
|
byte_left_shift.word_count == 2u && byte_left_shift.dst.reg == 6u &&
|
|
byte_left_shift.src0.kind == OperandKind::Vgpr &&
|
|
byte_left_shift.src0.reg == 7u &&
|
|
byte_left_shift.src0.sdwa_sel == 3u &&
|
|
byte_left_shift.src1.kind == OperandKind::IntegerInlineConstant &&
|
|
byte_left_shift.src1.value == 1u,
|
|
"decoder rejected or misdecoded captured V_LSHLREV_B32 byte shift");
|
|
|
|
const uint32_t mimg_nsa[] = {EncodeMimg0(0x20, 0xf) | (3u << 1u),
|
|
EncodeMimg1(4, 0, 1, 8), 0x03020100u,
|
|
0x07060504u, 0x0b0a0908u};
|
|
Instruction image;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(mimg_nsa, 0u, image);
|
|
Check(image.family == Family::MIMG && image.word_count == 5u &&
|
|
image.image_nsa_dwords == 3u,
|
|
"single-instruction decoder lost the MIMG NSA length");
|
|
|
|
const uint32_t ds_code[] = {EncodeDs0(0x36) | (1u << 17u),
|
|
EncodeDs1(2, 0, 1)};
|
|
Instruction ds;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(ds_code, 0u, ds);
|
|
Check(ds.opcode == Opcode::DS_READ_B32 && ds.gds,
|
|
"DS decoder lost the GFX10 opcode or GDS fields");
|
|
|
|
const uint32_t masked_or_code[] = {0xd8300144u, 0x000a0802u};
|
|
Instruction masked_or;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(masked_or_code, 0u, masked_or);
|
|
Check(masked_or.opcode == Opcode::DS_MSKOR_B32 && !masked_or.gds &&
|
|
masked_or.word_count == 2u && masked_or.src_count == 3u &&
|
|
masked_or.offset == 0x144u && masked_or.src0.reg == 2u &&
|
|
masked_or.src1.reg == 8u && masked_or.src2.reg == 10u &&
|
|
masked_or.data_dwords == 1u && masked_or.data_bits == 32u,
|
|
"DS decoder misdecoded captured masked OR operands");
|
|
|
|
for (bool decrement : {false, true}) {
|
|
const uint32_t words[] = {decrement ? 0xd8920004u : 0xd88e0004u, 0x03000302u};
|
|
Instruction atomic;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(words, 0u, atomic);
|
|
Check(atomic.family == Family::DS &&
|
|
atomic.opcode == (decrement ? Opcode::DS_DEC_RTN_U32 : Opcode::DS_INC_RTN_U32) &&
|
|
atomic.word_count == 2u && atomic.src_count == 2u &&
|
|
atomic.data_dwords == 1u && atomic.data_bits == 32u &&
|
|
atomic.gds && atomic.offset == 4u && atomic.dst.reg == 3u &&
|
|
atomic.src0.reg == 2u && atomic.src1.reg == 3u,
|
|
"DS decoder misdecoded bounded atomic return fields");
|
|
}
|
|
|
|
const uint32_t boot_ds[] = {0xd8d4c480u, 0x45000045u};
|
|
Instruction boot;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(boot_ds, 0u, boot);
|
|
Check(boot.opcode == Opcode::DS_SWIZZLE_B32 && boot.offset == 0xc480u,
|
|
"DS decoder rejected a captured boot-shader instruction");
|
|
|
|
const uint32_t bpermute_ds[] = {0xdacc0000u, 0x01000311u};
|
|
Instruction bpermute;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(bpermute_ds, 0u, bpermute);
|
|
Check(bpermute.family == Family::DS &&
|
|
bpermute.opcode == Opcode::DS_BPERMUTE_B32 &&
|
|
bpermute.word_count == 2u && bpermute.src_count == 2u &&
|
|
bpermute.data_dwords == 1u && bpermute.data_bits == 32u &&
|
|
bpermute.offset == 0u && !bpermute.gds &&
|
|
bpermute.dst.reg == 1u && bpermute.src0.reg == 17u &&
|
|
bpermute.src1.reg == 3u,
|
|
"DS decoder rejected or misdecoded captured DS_BPERMUTE_B32");
|
|
|
|
const uint32_t d16_hi_ds[] = {0xda9c0000u, 0x05000006u};
|
|
Instruction d16_hi;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(d16_hi_ds, 0u, d16_hi);
|
|
Check(d16_hi.opcode == Opcode::DS_READ_U16_D16_HI &&
|
|
d16_hi.src_count == 1u && d16_hi.data_bits == 16u &&
|
|
d16_hi.dst.reg == 5u && d16_hi.dst.sdwa_sel == 5u &&
|
|
d16_hi.dst.sdwa_dst_unused == 2u && d16_hi.src0.reg == 6u,
|
|
"DS decoder rejected the captured high-half D16 read");
|
|
|
|
const uint32_t d16_hi_write_ds[] = {0xda840000u, 0x00000214u};
|
|
Instruction d16_hi_write;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(d16_hi_write_ds, 0u,
|
|
d16_hi_write);
|
|
Check(d16_hi_write.family == Family::DS &&
|
|
d16_hi_write.opcode == Opcode::DS_WRITE_B16_D16_HI &&
|
|
d16_hi_write.word_count == 2u && d16_hi_write.src_count == 2u &&
|
|
d16_hi_write.data_dwords == 1u && d16_hi_write.data_bits == 16u &&
|
|
d16_hi_write.offset == 0u && !d16_hi_write.gds &&
|
|
d16_hi_write.src0.reg == 20u && d16_hi_write.src1.reg == 2u &&
|
|
d16_hi_write.src1.sdwa_sel == 5u,
|
|
"DS decoder rejected the captured high-half D16 write");
|
|
|
|
// Captured from GTA V (PPSA04264) compute shader, pc 0x1598.
|
|
const uint32_t d16_hi_byte_write_ds[] = {0xda800200u, 0x00001413u};
|
|
Instruction d16_hi_byte_write;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(d16_hi_byte_write_ds, 0u,
|
|
d16_hi_byte_write);
|
|
Check(d16_hi_byte_write.family == Family::DS &&
|
|
d16_hi_byte_write.opcode == Opcode::DS_WRITE_B8_D16_HI &&
|
|
d16_hi_byte_write.word_count == 2u &&
|
|
d16_hi_byte_write.src_count == 2u &&
|
|
d16_hi_byte_write.data_dwords == 1u &&
|
|
d16_hi_byte_write.data_bits == 8u &&
|
|
d16_hi_byte_write.offset == 0x200u && !d16_hi_byte_write.gds &&
|
|
d16_hi_byte_write.src0.reg == 19u &&
|
|
d16_hi_byte_write.src1.reg == 20u &&
|
|
d16_hi_byte_write.src1.sdwa_sel == 2u,
|
|
"DS decoder rejected the captured high-half byte write");
|
|
|
|
constexpr uint32_t packed_source_selectors[][2] = {
|
|
{0xcc0e0000u, 0x0c0a0300u}, // Source 0: instruction bit 59.
|
|
{0xcc0e0000u, 0x140a0300u}, // Source 1: instruction bit 60.
|
|
{0xcc0e4000u, 0x040a0300u}, // Source 2: instruction bit 14.
|
|
};
|
|
for (uint32_t source = 0; source < 3u; source++) {
|
|
const auto &packed = packed_source_selectors[source];
|
|
Instruction packed_inst;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(packed, 0u, packed_inst);
|
|
Check(packed_inst.src0.op_sel_hi == (source == 0u) &&
|
|
packed_inst.src1.op_sel_hi == (source == 1u) &&
|
|
packed_inst.src2.op_sel_hi == (source == 2u),
|
|
"VOP3P OPSEL_HI source bit mapping is incorrect");
|
|
}
|
|
|
|
const uint32_t packed_fmac_dpp[] = {0x780402fau, 0xff500000u};
|
|
Instruction packed_fmac;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(packed_fmac_dpp, 0u,
|
|
packed_fmac);
|
|
Check(packed_fmac.opcode == Opcode::V_PK_FMAC_F16 &&
|
|
packed_fmac.src0.dpp && packed_fmac.src0.op_sel_hi &&
|
|
packed_fmac.src1.op_sel_hi && packed_fmac.dst.op_sel_hi &&
|
|
packed_fmac.src0.negate && packed_fmac.src0.negate_hi &&
|
|
packed_fmac.src1.negate && packed_fmac.src1.negate_hi,
|
|
"VOP2 DPP V_PK_FMAC_F16 lost its implicit packed modifiers");
|
|
}
|
|
|
|
void TestNewShaderRecompilerRejectsDppOn64BitCompares() {
|
|
const uint32_t opcodes[] = {
|
|
0xa2u, 0xa5u, 0xb5u, 0xe2u, 0xe4u, 0xe5u,
|
|
0xf5u}; // eq_i64, ne_i64, cmpx_ne_i64, eq_u64, gt_u64, ne_u64, cmpx_ne_u64
|
|
for (const auto opcode : opcodes) {
|
|
const uint32_t shader[] = {
|
|
EncodeVopc(opcode, 250u, 0u), // DPP escape in SRC0
|
|
EncodeVop2Dpp(0u),
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program program;
|
|
ShaderRecompiler::Decoder::DecodeProgram(shader, program);
|
|
Check(program.instructions.size() == 2u,
|
|
"64-bit VOPC DPP decode did not consume its modifier word");
|
|
const auto &compare = program.instructions.front();
|
|
Check(compare.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED,
|
|
"64-bit VOPC illegally accepted a DPP modifier");
|
|
Check((compare.unsupported_reason.find("VOPC DPP modifier is not supported for opcode") != std::string::npos),
|
|
"64-bit VOPC DPP rejection reason was not explicit");
|
|
}
|
|
const uint32_t sdwa[] = {EncodeVopc(0xa5u, 249u, 0u), 0x06060000u};
|
|
ShaderRecompiler::Decoder::Instruction compare;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(sdwa, 0u, compare);
|
|
Check(compare.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED &&
|
|
compare.word_count == 2u &&
|
|
compare.unsupported_reason.find("VOPC SDWA modifier is not supported") != std::string::npos,
|
|
"V_CMP_NE_I64 accepted an illegal SDWA encoding");
|
|
}
|
|
|
|
void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() {
|
|
using namespace ShaderRecompiler;
|
|
|
|
const uint32_t shader[] = {
|
|
0x7d30d4f9u, 0x8606000du, // v_cmpx_class_f32 exec, v13, vcc_lo (SDWA)
|
|
EncodeSopp(0x01),
|
|
};
|
|
|
|
Decoder::Instruction decoded;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(shader, 0u, decoded);
|
|
Check(decoded.family == Decoder::Family::VOPC &&
|
|
decoded.opcode == Decoder::Opcode::V_CMPX_CLASS_F32 &&
|
|
decoded.opcode_id == 0x98u && decoded.word_count == 2u &&
|
|
decoded.raw[0] == shader[0] && decoded.raw[1] == shader[1] &&
|
|
decoded.dst.kind == Decoder::OperandKind::ExecLo &&
|
|
decoded.src_count == 2u &&
|
|
decoded.src0.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded.src0.reg == 13u && decoded.src0.sdwa_sel == 6u &&
|
|
decoded.src1.kind == Decoder::OperandKind::VccLo &&
|
|
decoded.src1.sdwa_sel == 6u,
|
|
"decoder rejected captured SDWA V_CMPX_CLASS_F32 fields");
|
|
|
|
Decoder::Program program;
|
|
CFG::Graph graph;
|
|
IR::Program ir;
|
|
ShaderPixelInputInfo pixel{};
|
|
Frontend::TranslateOptions translate_options{};
|
|
translate_options.stage = ShaderType::Pixel;
|
|
translate_options.wave_size = 64u;
|
|
translate_options.input_info.pixel = &pixel;
|
|
ShaderRecompiler::Decoder::DecodeProgram(shader, program);
|
|
graph = ShaderRecompiler::CFG::BuildGraph(program);
|
|
ir = ShaderRecompiler::Frontend::TranslateProgram(program, graph,
|
|
translate_options);
|
|
uint32_t class_compares = 0u;
|
|
uint32_t dynamic_exec_writes = 0u;
|
|
for (const auto *block : ir.blocks) {
|
|
for (const auto &inst : *block) {
|
|
class_compares +=
|
|
inst.GetOpcode() == IR::ValueOpcode::FPCmpClass32 ? 1u : 0u;
|
|
if (inst.GetOpcode() == IR::ValueOpcode::SetExec &&
|
|
!inst.Arg(0).Resolve().IsImmediate()) {
|
|
dynamic_exec_writes++;
|
|
}
|
|
}
|
|
}
|
|
Check(class_compares == 1u && dynamic_exec_writes == 1u,
|
|
"captured V_CMPX_CLASS_F32 did not lower to class compare plus EXEC update");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("V_CMPX_CLASS_F32 exec_lo, v13, vcc_lo") != std::string::npos),
|
|
"captured SDWA V_CMPX_CLASS_F32 was not present in the decoded dump");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16() {
|
|
using namespace ShaderRecompiler;
|
|
|
|
const uint32_t shader[] = {
|
|
0x7d72d4f9u, 0x86060000u, // v_cmpx_lt_u16 v0, vcc_lo (SDWA)
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Instruction decoded;
|
|
Decoder::DecodeInstruction(shader, 0u, decoded);
|
|
Check(decoded.family == Decoder::Family::VOPC &&
|
|
decoded.opcode == Decoder::Opcode::V_CMPX_LT_U16 &&
|
|
decoded.opcode_id == 0xb9u && decoded.word_count == 2u &&
|
|
decoded.raw[0] == shader[0] && decoded.raw[1] == shader[1] &&
|
|
decoded.dst.kind == Decoder::OperandKind::ExecLo &&
|
|
decoded.src_count == 2u &&
|
|
decoded.src0.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded.src0.reg == 0u && decoded.src0.sdwa_sel == 6u &&
|
|
decoded.src1.kind == Decoder::OperandKind::VccLo &&
|
|
decoded.src1.sdwa_sel == 6u &&
|
|
!decoded.src0.sdwa_sext && !decoded.src1.sdwa_sext &&
|
|
!decoded.src0.negate && !decoded.src1.negate &&
|
|
!decoded.src0.absolute && !decoded.src1.absolute,
|
|
"decoder rejected captured SDWA V_CMPX_LT_U16 fields");
|
|
|
|
Decoder::Program program;
|
|
Decoder::DecodeProgram(shader, program);
|
|
Check(!CFG::BuildGraph(program).unsupported,
|
|
"captured SDWA V_CMPX_LT_U16 still fails CFG construction");
|
|
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Pixel));
|
|
Check((result.decoded_dump.find("V_CMPX_LT_U16 exec_lo, v0, vcc_lo") != std::string::npos),
|
|
"captured SDWA V_CMPX_LT_U16 is missing from decoded dump");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
// Independently assembled gfx1030 compact and VOP3 encodings.
|
|
const uint32_t compact[] = {0x7d720300u};
|
|
const uint32_t vop3[] = {0xd4b9007eu, 0x00020300u};
|
|
for (auto words : {std::span<const uint32_t>(compact),
|
|
std::span<const uint32_t>(vop3)}) {
|
|
Decoder::DecodeInstruction(words, 0u, decoded);
|
|
Check(decoded.opcode == Decoder::Opcode::V_CMPX_LT_U16 &&
|
|
decoded.dst.kind == Decoder::OperandKind::ExecLo &&
|
|
decoded.src0.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded.src0.reg == 0u &&
|
|
decoded.src1.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded.src1.reg == 1u,
|
|
"compact/VOP3 V_CMPX_LT_U16 does not decode to an EXEC compare");
|
|
}
|
|
const uint32_t dpp[] = {EncodeVopc(0xb9u, 250u, 1u), EncodeVop2Dpp(0u)};
|
|
Decoder::DecodeInstruction(dpp, 0u, decoded);
|
|
Check(decoded.opcode == Decoder::Opcode::UNSUPPORTED &&
|
|
(decoded.unsupported_reason.find("VOPC DPP modifier is not supported for opcode") != std::string::npos),
|
|
"V_CMPX_LT_U16 accepted an unsupported DPP encoding");
|
|
}
|
|
|
|
void TestNewShaderRecompilerIrLookupMissFailsExplicitly() {
|
|
namespace Decoder = ShaderRecompiler::Decoder;
|
|
namespace CFG = ShaderRecompiler::CFG;
|
|
namespace Frontend = ShaderRecompiler::Frontend;
|
|
namespace IR = ShaderRecompiler::IR;
|
|
|
|
Decoder::Program decoded;
|
|
decoded.instructions.emplace_back();
|
|
auto &store = decoded.instructions.front();
|
|
|
|
CFG::Graph cfg;
|
|
CFG::BasicBlock block;
|
|
block.inst_end = 1u;
|
|
cfg.blocks.push_back(block);
|
|
cfg.entry_block = 0u;
|
|
|
|
ShaderComputeInputInfo compute{};
|
|
Frontend::TranslateOptions options{};
|
|
options.stage = ShaderType::Compute;
|
|
options.wave_size = 64u;
|
|
options.input_info.compute = &compute;
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
store.pc = 0u;
|
|
store.family = Decoder::Family::VOP1;
|
|
store.opcode = Decoder::Opcode::UNKNOWN;
|
|
ExpectFatal(
|
|
[&] { (void)Frontend::TranslateProgram(decoded, cfg, options); },
|
|
"missing decoder-to-IR mapping did not terminate compilation");
|
|
|
|
options.wave_size = 16u;
|
|
ExpectFatal([&] { (void)Frontend::TranslateProgram(decoded, cfg, options); },
|
|
"invalid translation options did not terminate compilation");
|
|
options.wave_size = 64u;
|
|
|
|
store = {};
|
|
store.family = Decoder::Family::MUBUF;
|
|
store.opcode = Decoder::Opcode::UNSUPPORTED;
|
|
store.unsupported_reason = "focused unsupported MUBUF diagnostic";
|
|
ExpectFatal(
|
|
[&] { (void)Frontend::TranslateProgram(decoded, cfg, options); },
|
|
"unsupported memory instruction did not terminate compilation");
|
|
|
|
store = {};
|
|
store.family = Decoder::Family::VOP1;
|
|
store.opcode = Decoder::Opcode::V_MOVRELS_B32;
|
|
store.dst.kind = Decoder::OperandKind::Sgpr;
|
|
store.src0.kind = Decoder::OperandKind::Vgpr;
|
|
store.src_count = 1u;
|
|
ExpectFatal([&] { (void)Frontend::TranslateProgram(decoded, cfg, options); },
|
|
"invalid V_MOVRELS_B32 operands did not terminate compilation");
|
|
#endif
|
|
|
|
store = {};
|
|
store.family = Decoder::Family::SOP1;
|
|
store.opcode = Decoder::Opcode::S_GETPC_B64;
|
|
store.dst.kind = Decoder::OperandKind::Sgpr;
|
|
store.dst.reg = 105u;
|
|
auto ir = Frontend::TranslateProgram(decoded, cfg, options);
|
|
bool getpc_wrote_vcc_lo = false;
|
|
for (const auto *typed_block : ir.blocks) {
|
|
for (const auto &typed_inst : *typed_block) {
|
|
getpc_wrote_vcc_lo |=
|
|
typed_inst.GetOpcode() == IR::ValueOpcode::SetVccLo;
|
|
}
|
|
}
|
|
Check(getpc_wrote_vcc_lo,
|
|
"S_GETPC_B64 did not advance s105 through the VCC alias");
|
|
|
|
const Decoder::OperandKind canonical_zero_masks[] = {
|
|
Decoder::OperandKind::Null,
|
|
Decoder::OperandKind::PopsExitingWaveId,
|
|
Decoder::OperandKind::VccZ,
|
|
Decoder::OperandKind::ExecZ,
|
|
};
|
|
for (const auto kind : canonical_zero_masks) {
|
|
store = {};
|
|
store.family = Decoder::Family::SOP1;
|
|
store.opcode = Decoder::Opcode::S_NOT_B64;
|
|
store.dst.kind = Decoder::OperandKind::Sgpr;
|
|
store.src0.kind = kind;
|
|
store.src_count = 1u;
|
|
ir = Frontend::TranslateProgram(decoded, cfg, options);
|
|
bool preserved_mask_tag = false;
|
|
for (const auto *typed_block : ir.blocks) {
|
|
for (const auto &typed_inst : *typed_block) {
|
|
if (typed_inst.GetOpcode() != IR::ValueOpcode::SetScalarMaskTag) {
|
|
continue;
|
|
}
|
|
const auto valid = typed_inst.Arg(1).Resolve();
|
|
preserved_mask_tag |= valid.IsImmediate() && valid.U1();
|
|
}
|
|
}
|
|
Check(preserved_mask_tag,
|
|
"canonical zero/condition operand lost scalar mask validity");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerMemoryFamilyTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeSmem0(0x00, 0, 4),
|
|
0u, // s_load_dword s0
|
|
EncodeSmem0(0x08, 1, 4),
|
|
0u, // s_buffer_load_dword s1
|
|
EncodeMubuf0(0x0c, 4),
|
|
EncodeMubuf1(0, 0, 1), // buffer_load_dword v0
|
|
EncodeMubuf0(0x1c, 8),
|
|
EncodeMubuf1(0, 0, 1), // buffer_store_dword v0
|
|
EncodeDs0(0x0d),
|
|
EncodeDs1(0, 0, 1), // ds_write_b32 v0, v1
|
|
EncodeDs0(0x36),
|
|
EncodeDs1(2, 0, 1), // ds_read_b32 v2, v1
|
|
EncodeMimg0(0x0e, 0x1),
|
|
EncodeMimg1(5, 0, 0, 1), // image_get_resinfo v5
|
|
EncodeMimg0(0x00, 0xf),
|
|
EncodeMimg1(4, 0, 0, 1), // image_load v4
|
|
EncodeMimg0(0x20, 0xf),
|
|
EncodeMimg1(3, 0, 0, 1), // image_sample v3
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options, ReadZeroTestMemory);
|
|
Check((result.decoded_dump.find("s_load_dword") != std::string::npos),
|
|
"new decoder did not decode SMEM dword load");
|
|
Check((result.decoded_dump.find("s_buffer_load_dword") != std::string::npos),
|
|
"new decoder did not decode SMEM scalar-buffer dword load");
|
|
Check((result.decoded_dump.find("s_buffer_load_dword s1, s8") != std::string::npos),
|
|
"SMEM scalar-buffer SBASE was not decoded as an SGPR-pair index");
|
|
Check((result.decoded_dump.find("buffer_load_dword") != std::string::npos),
|
|
"new decoder did not decode MUBUF dword load");
|
|
Check((result.decoded_dump.find("buffer_store_dword") != std::string::npos),
|
|
"new decoder did not decode MUBUF dword store");
|
|
Check((result.decoded_dump.find("ds_read_b32") != std::string::npos),
|
|
"new decoder did not decode DS dword read");
|
|
Check((result.decoded_dump.find("image_get_resinfo") != std::string::npos),
|
|
"new decoder did not decode MIMG resinfo query");
|
|
Check((result.decoded_dump.find("image_load") != std::string::npos),
|
|
"new decoder did not decode MIMG load");
|
|
Check((result.decoded_dump.find("image_sample") != std::string::npos),
|
|
"new decoder did not decode MIMG sample");
|
|
Check(
|
|
(result.decoded_dump.find("translation is not implemented") == std::string::npos),
|
|
"implemented memory decode still reports unsupported translation");
|
|
Check((result.ir_dump.find("SLoadDword s0") != std::string::npos),
|
|
"SMEM load did not lower to IR");
|
|
Check((result.ir_dump.find("SBufferLoadDword s1") != std::string::npos),
|
|
"SMEM scalar-buffer load did not lower to IR");
|
|
Check((result.ir_dump.find("BufferLoadDword v0") != std::string::npos),
|
|
"MUBUF load did not lower to IR");
|
|
Check((result.ir_dump.find("BufferStoreDword null, v0") != std::string::npos),
|
|
"MUBUF store did not lower to IR");
|
|
Check((result.ir_dump.find("DsWriteB32 null, v0") != std::string::npos),
|
|
"DS write did not lower to IR");
|
|
Check((result.ir_dump.find("DsReadB32 v2") != std::string::npos),
|
|
"DS read did not lower to IR");
|
|
Check((result.ir_dump.find("ImageGetResinfo v5") != std::string::npos),
|
|
"MIMG resinfo query did not lower to IR");
|
|
Check((result.ir_dump.find("ImageLoad v4") != std::string::npos),
|
|
"MIMG load did not lower to IR");
|
|
Check((result.ir_dump.find("ImageSample v3") != std::string::npos),
|
|
"MIMG sample did not lower to IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 95),
|
|
"SPIR-V binary does not contain OpImageFetch");
|
|
Check(SpirvContainsOpcode(result.spirv, 103),
|
|
"SPIR-V binary does not contain OpImageQuerySizeLod");
|
|
Check(SpirvContainsOpcode(result.spirv, 88),
|
|
"SPIR-V binary does not contain OpImageSampleExplicitLod");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerScalarMemoryBindingDomains() {
|
|
const auto count_live_memory_ops =
|
|
[](const ShaderRecompiler::IR::Program &program,
|
|
ShaderRecompiler::IR::ValueOpcode opcode,
|
|
ShaderRecompiler::IR::ResourceKind kind) {
|
|
size_t count = 0;
|
|
for (const auto *block : program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() != opcode) {
|
|
continue;
|
|
}
|
|
const auto index =
|
|
inst.Flags<ShaderRecompiler::IR::MemoryFlags>().index;
|
|
Check(index < program.memory_info.size(),
|
|
"typed scalar memory operation has invalid metadata");
|
|
const auto &memory = program.memory_info[index];
|
|
Check(memory.kind == kind && memory.resource == 0u &&
|
|
!memory.planning_only,
|
|
"typed scalar memory operation has the wrong live domain");
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
};
|
|
|
|
const uint32_t raw_shader[] = {
|
|
EncodeSmem0(0x01, 12, 4),
|
|
2u, // s_load_dwordx2 s[12:13], s[8:9], s0 offset:2
|
|
EncodeVop1(0x01, 0, 12),
|
|
EncodeVop1(0x01, 1, 13),
|
|
EncodeExp0(0x00, 0x3),
|
|
EncodeExp1(0, 1, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
std::array<uint32_t, 12> raw_user_data{};
|
|
raw_user_data[0] = 2u;
|
|
raw_user_data[8] = 0x1003u;
|
|
|
|
auto raw_options = MakeCompileOptions(ShaderType::Pixel);
|
|
raw_options.user_data = raw_user_data;
|
|
|
|
auto raw = RecompileForTest(raw_shader, raw_options);
|
|
const auto *address_binding = ShaderRecompiler::IR::FindBinding(
|
|
raw.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable);
|
|
const auto *fault_binding = ShaderRecompiler::IR::FindBinding(
|
|
raw.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer);
|
|
Check(raw.program.info.uses_dma && raw.program.info.buffers.empty() &&
|
|
address_binding != nullptr && fault_binding != nullptr &&
|
|
address_binding->resources.empty() &&
|
|
fault_binding->resources.empty() &&
|
|
ShaderRecompiler::IR::FindBinding(
|
|
raw.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::Buffers) ==
|
|
nullptr &&
|
|
ShaderRecompiler::IR::FindBinding(
|
|
raw.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) ==
|
|
nullptr &&
|
|
raw.program.bindings.memory_offset_count == 0u,
|
|
"raw scalar load did not use only the DMA domain");
|
|
Check(count_live_memory_ops(
|
|
raw.program, ShaderRecompiler::IR::ValueOpcode::LoadAddressU32,
|
|
ShaderRecompiler::IR::ResourceKind::ScalarAddress) == 2u,
|
|
"raw scalar load did not remain a live typed address operation");
|
|
Check(SpirvInstructionOpcodeCount(raw.spirv, 57) == 2u,
|
|
"aligned scalar DWORDs must each use one BDA lookup");
|
|
Check(SpirvContainsOpcode(raw.spirv, 199),
|
|
"raw scalar SOFFSET alignment was not emitted");
|
|
CheckSpirvBinaryValidates(raw.spirv);
|
|
|
|
const uint32_t buffer_shader[] = {
|
|
EncodeSmem0(0x08, 12, 4),
|
|
0u, // s_buffer_load_dword s12, s[8:11], s0
|
|
EncodeVop1(0x01, 0, 12),
|
|
EncodeExp0(0x00, 0x1),
|
|
EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
std::array<uint32_t, 12> buffer_user_data{};
|
|
buffer_user_data[8] = 0x2000u;
|
|
buffer_user_data[10] = 4u;
|
|
|
|
auto buffer_options = MakeCompileOptions(ShaderType::Pixel);
|
|
buffer_options.user_data = buffer_user_data;
|
|
|
|
auto buffer = RecompileForTest(buffer_shader, buffer_options);
|
|
const auto *buffer_binding = ShaderRecompiler::IR::FindBinding(
|
|
buffer.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::Buffers);
|
|
Check(buffer.program.info.buffers.size() == 1u &&
|
|
buffer.program.info.buffers[0].scalar &&
|
|
!buffer.program.info.uses_dma &&
|
|
buffer_binding != nullptr &&
|
|
buffer_binding->resources == std::vector<uint32_t>{0u} &&
|
|
ShaderRecompiler::IR::FindBinding(
|
|
buffer.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) ==
|
|
nullptr &&
|
|
ShaderRecompiler::IR::FindBinding(
|
|
buffer.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) ==
|
|
nullptr &&
|
|
buffer.program.bindings.memory_offset_count == 1u,
|
|
"descriptor scalar load did not use only the buffer domain");
|
|
Check(count_live_memory_ops(
|
|
buffer.program, ShaderRecompiler::IR::ValueOpcode::ReadConstBuffer,
|
|
ShaderRecompiler::IR::ResourceKind::ScalarBuffer) == 1u,
|
|
"descriptor scalar load did not remain a live typed buffer operation");
|
|
CheckSpirvBinaryValidates(buffer.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageQueryTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x60, 0x3),
|
|
EncodeMimg1(6, 0, 0, 1), // image_get_lod
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_get_lod") != std::string::npos),
|
|
"new decoder did not decode MIMG image get-lod query");
|
|
Check((result.decoded_dump.find("dmask=0x3") != std::string::npos),
|
|
"image_get_lod decode did not preserve dmask metadata");
|
|
Check((result.ir_dump.find("ImageGetLod v6") != std::string::npos),
|
|
"image_get_lod did not lower to explicit query IR");
|
|
Check((result.ir_dump.find("data_dwords=2") != std::string::npos),
|
|
"image_get_lod did not preserve two-component result metadata");
|
|
Check((result.ir_dump.find("image_addr=2") != std::string::npos),
|
|
"image_get_lod did not preserve address component metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 105),
|
|
"SPIR-V binary does not contain OpImageQueryLod");
|
|
Check(
|
|
SpirvContainsOpcode(result.spirv, 80),
|
|
"SPIR-V binary does not contain coordinate composite for image_get_lod");
|
|
Check(SpirvContainsOpcode(result.spirv, 81),
|
|
"SPIR-V binary does not contain dmask extraction for image_get_lod");
|
|
Check(SpirvContainsOpcode(result.spirv, 124),
|
|
"SPIR-V binary does not contain result bitcast for image_get_lod");
|
|
Check(std::find(result.spirv.begin(), result.spirv.end(), 5288u) !=
|
|
result.spirv.end(),
|
|
"SPIR-V binary does not request compute derivative group capability");
|
|
Check(
|
|
std::find(result.spirv.begin(), result.spirv.end(), 5289u) !=
|
|
result.spirv.end(),
|
|
"SPIR-V binary does not request compute derivative group execution mode");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCubeSampleCoordinates() {
|
|
constexpr uint32_t MimgDimCube = 3;
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x20, 0xf, false, MimgDimCube),
|
|
EncodeMimg1(0, 0, 1, 0), // image_sample cube
|
|
EncodeMimg0(0x60, 0x3, false, MimgDimCube),
|
|
EncodeMimg1(8, 0, 1, 4), // image_get_lod cube
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData(Prospero::ImageType::kCube);
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(result.program.info.images.size() == 1 &&
|
|
result.program.info.images[0].cube,
|
|
"cube descriptor identity was not preserved through compilation");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 131) == 4,
|
|
"cube sample/get-lod did not remove the RDNA2 S/T bias");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 109) == 1 &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 112) == 1 &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 194) == 1 &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 196) == 1 &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 130) == 1,
|
|
"cube sample did not repack its face ID exactly once, or get-lod used "
|
|
"an array layer");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestImageAddressOperands() {
|
|
using namespace ShaderRecompiler;
|
|
struct Case {
|
|
uint32_t opcode;
|
|
uint32_t dimension;
|
|
bool a16;
|
|
bool nsa;
|
|
uint32_t logical_components;
|
|
uint32_t dwords;
|
|
};
|
|
constexpr std::array cases{
|
|
Case{0x27, 1, false, false, 2, 2}, // xy, explicit level zero
|
|
Case{0x22, 2, false, false, 9, 9}, // 3D gradients and xyz
|
|
Case{0x20, 1, true, false, 2, 1}, // packed xy
|
|
Case{0x3d, 1, true, false, 5, 4}, // offset, half bias, full dref, packed xy
|
|
Case{0x20, 2, true, true, 3, 2}, // packed xy/z in nonconsecutive VGPRs
|
|
Case{0x0e, 1, false, false, 1, 1}, // resource query mip
|
|
};
|
|
for (const auto &test : cases) {
|
|
std::vector<uint32_t> shader;
|
|
for (uint32_t reg = 0; reg < 16; reg++) {
|
|
shader.push_back(EncodeVop1(0x01, reg, 129 + reg));
|
|
}
|
|
shader.push_back(EncodeMimg0(test.opcode, 0x1, false, test.dimension) |
|
|
(test.nsa ? 1u << 1u : 0u));
|
|
shader.push_back(EncodeMimg1(48, 0, 4, 0, test.a16));
|
|
if (test.nsa) shader.push_back(0x0f0b0703u);
|
|
shader.push_back(EncodeSopp(0x01));
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(shader, decoded);
|
|
Check(decoded.instructions[16].image_address_components == test.logical_components,
|
|
"MIMG decoded address count does not match its logical operands");
|
|
ShaderComputeInputInfo compute = RegressionComputeInputInfo();
|
|
Frontend::TranslateOptions options{};
|
|
options.stage = ShaderType::Compute;
|
|
options.input_info.compute = &compute;
|
|
auto program = Frontend::TranslateProgram(decoded, CFG::BuildGraph(decoded), options);
|
|
IR::RewriteToSsa(program.blocks);
|
|
IR::ConstantPropagationPass(program.blocks);
|
|
uint32_t addresses = 0;
|
|
for (const auto *block : program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() != IR::ValueOpcode::MakeImageAddress) continue;
|
|
addresses++;
|
|
for (uint32_t index = 0; index < inst.NumArgs(); index++) {
|
|
const auto value = inst.Arg(index).Resolve();
|
|
const auto reg = test.nsa && index > 0 ? 3u + (index - 1u) * 4u : index;
|
|
const auto expected = index < test.dwords ? reg + 1u : 0u;
|
|
Check(value.IsImmediate() && value.GetType() == IR::Type::U32 &&
|
|
value.U32() == expected,
|
|
"MIMG address read an unused VGPR or lost a packed/NSA operand");
|
|
}
|
|
}
|
|
}
|
|
Check(addresses == 1, "MIMG address regression did not reach translation");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageSampleVariants() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x24, 0xf),
|
|
EncodeMimg1(8, 0, 0, 1), // image_sample_l
|
|
EncodeMimg0(0x25, 0xf),
|
|
EncodeMimg1(12, 0, 0, 1), // image_sample_b
|
|
EncodeMimg0(0x27, 0xf),
|
|
EncodeMimg1(16, 0, 0, 1), // image_sample_lz
|
|
EncodeMimg0(0x28, 0xf),
|
|
EncodeMimg1(20, 0, 0, 1), // image_sample_c
|
|
EncodeMimg0(0x30, 0xf),
|
|
EncodeMimg1(24, 0, 0, 1), // image_sample_o
|
|
EncodeMimg0(0x22, 0xf),
|
|
EncodeMimg1(28, 0, 0, 1), // image_sample_d
|
|
EncodeMimg0(0x2f, 0x1),
|
|
EncodeMimg1(32, 0, 0, 1), // image_sample_c_lz
|
|
EncodeMimg0(0x38, 0x1),
|
|
EncodeMimg1(36, 0, 0, 1), // image_sample_c_o
|
|
EncodeMimg0(0x2a, 0x1),
|
|
EncodeMimg1(40, 0, 0, 1), // image_sample_c_d
|
|
EncodeMimg0(0x20, 0x7),
|
|
EncodeMimg1(48, 0, 0, 1, true), // image_sample with A16 bit
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_sample_l") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_L through shared MIMG path");
|
|
Check((result.decoded_dump.find("image_sample_b") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_B through shared MIMG path");
|
|
Check((result.decoded_dump.find("image_sample_lz") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_LZ through shared MIMG path");
|
|
Check((result.decoded_dump.find("image_sample_c") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_C through shared MIMG path");
|
|
Check((result.decoded_dump.find("image_sample_o") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_O through shared MIMG path");
|
|
Check((result.decoded_dump.find("image_sample_d") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_D through shared MIMG path");
|
|
Check(
|
|
(result.decoded_dump.find("image_sample_c_lz") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_C_LZ through shared MIMG path");
|
|
Check((result.decoded_dump.find("image_sample_c_o") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_C_O through shared MIMG path");
|
|
Check((result.decoded_dump.find("image_sample_c_d") != std::string::npos),
|
|
"new decoder did not decode IMAGE_SAMPLE_C_D through shared MIMG path");
|
|
Check((result.decoded_dump.find("sample_flags=a16") != std::string::npos),
|
|
"IMAGE_SAMPLE with MIMG A16 bit did not expose A16 sample flag");
|
|
Check((result.decoded_dump.find("sample_flags=lod") != std::string::npos),
|
|
"IMAGE_SAMPLE_L did not expose lod sample flag");
|
|
Check((result.decoded_dump.find("sample_flags=bias") != std::string::npos),
|
|
"IMAGE_SAMPLE_B did not expose bias sample flag");
|
|
Check((result.decoded_dump.find("sample_flags=level_zero") != std::string::npos),
|
|
"IMAGE_SAMPLE_LZ did not expose level-zero sample flag");
|
|
Check((result.decoded_dump.find("sample_flags=compare") != std::string::npos),
|
|
"IMAGE_SAMPLE_C did not expose compare sample flag");
|
|
Check((result.decoded_dump.find("sample_flags=offset addr_components=3") != std::string::npos),
|
|
"IMAGE_SAMPLE_O did not expose offset sample flag/address width");
|
|
Check((result.decoded_dump.find("sample_flags=derivative addr_components=6") != std::string::npos),
|
|
"IMAGE_SAMPLE_D did not expose derivative sample flag/address width");
|
|
Check((result.decoded_dump.find("sample_flags=compare|level_zero") != std::string::npos),
|
|
"IMAGE_SAMPLE_C_LZ did not expose compare+level-zero flags");
|
|
Check((result.decoded_dump.find("sample_flags=compare|offset addr_components=4") != std::string::npos),
|
|
"IMAGE_SAMPLE_C_O did not expose compare+offset flags/address width");
|
|
Check(
|
|
(result.decoded_dump.find("sample_flags=derivative|compare addr_components=7") != std::string::npos),
|
|
"IMAGE_SAMPLE_C_D did not expose compare+derivative address width");
|
|
Check((result.ir_dump.find("ImageSample v8") != std::string::npos),
|
|
"IMAGE_SAMPLE_L did not lower to shared IR ImageSample");
|
|
Check((result.ir_dump.find("ImageSample v28") != std::string::npos),
|
|
"IMAGE_SAMPLE_D did not lower to shared IR ImageSample");
|
|
Check((result.ir_dump.find("image_flags=0x4") != std::string::npos),
|
|
"derivative sample flag did not survive into IR memory metadata");
|
|
Check((result.ir_dump.find("image_addr=6") != std::string::npos),
|
|
"derivative sample address width did not survive into IR memory "
|
|
"metadata");
|
|
Check((result.ir_dump.find("image_addr=7") != std::string::npos),
|
|
"compare+derivative sample address width did not survive into IR "
|
|
"memory metadata");
|
|
Check((result.ir_dump.find("image_flags=0x80") != std::string::npos),
|
|
"MIMG A16 bit did not survive into IR memory metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 88),
|
|
"SPIR-V binary does not contain shared OpImageSampleExplicitLod "
|
|
"emission");
|
|
Check(SpirvContainsOpcode(result.spirv, 90),
|
|
"SPIR-V binary does not contain shared OpImageSampleDrefExplicitLod "
|
|
"emission");
|
|
Check(SpirvContainsOpcode(result.spirv, 80),
|
|
"SPIR-V binary does not contain coordinate/gradient composite "
|
|
"construction");
|
|
Check(SpirvContainsExtInst(result.spirv, 62),
|
|
"SPIR-V binary does not unpack A16 image address halves");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageSampleA16SamplerCoords() {
|
|
constexpr uint32_t MimgDim3D = 2;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x20, 0x7, false, MimgDim3D),
|
|
EncodeMimg1(8, 0, 0, 4, true), // image_sample, A16 bit, 3D xyz coords
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData(Prospero::ImageType::kColor3D);
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_dim=3d sample_flags=a16 addr_components=3") != std::string::npos),
|
|
"3D IMAGE_SAMPLE with MIMG A16 bit did not decode as three A16 sampler "
|
|
"coords");
|
|
Check((result.ir_dump.find("ImageSample v8") != std::string::npos),
|
|
"3D A16 IMAGE_SAMPLE did not lower to sample IR");
|
|
Check((result.ir_dump.find("image_flags=0x80") != std::string::npos),
|
|
"3D A16 IMAGE_SAMPLE did not preserve A16 memory metadata");
|
|
Check((result.ir_dump.find("image_dim=3d") != std::string::npos),
|
|
"3D A16 IMAGE_SAMPLE did not preserve image dimension metadata");
|
|
Check(
|
|
(result.ir_dump.find("image_addr=3") != std::string::npos),
|
|
"3D A16 IMAGE_SAMPLE did not preserve three logical address components");
|
|
Check(SpirvExtInstCount(result.spirv, 62) == 3,
|
|
"sampler A16 xyz coordinates should be converted from three packed f16 "
|
|
"components");
|
|
Check(SpirvContainsOpcode(result.spirv, 194),
|
|
"sampler A16 high-half coordinate extraction is missing");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"sampler A16 low-half coordinate masking is missing");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageSampleOpcodeAliases() {
|
|
const uint32_t shader[] = {
|
|
0xf0800109u,
|
|
0x00c00602u, // observed image_sample_a v6, v2, s0, s24
|
|
EncodeMimg0(0xa5, 0xf),
|
|
EncodeMimg1(12, 0, 0, 4), // image_sample_b_a
|
|
EncodeMimg0(0xa8, 0x1),
|
|
EncodeMimg1(16, 0, 0, 4), // image_sample_c_a
|
|
EncodeMimg0(0xad, 0x1),
|
|
EncodeMimg1(20, 0, 0, 4), // image_sample_c_b_a
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_sample_a") != std::string::npos),
|
|
"MIMG opcode 0xa0 should decode as image_sample_a alias");
|
|
Check((result.decoded_dump.find("image_sample_b_a") != std::string::npos),
|
|
"MIMG opcode 0xa5 should decode as image_sample_b_a alias");
|
|
Check((result.decoded_dump.find("image_sample_c_a") != std::string::npos),
|
|
"MIMG opcode 0xa8 should decode as image_sample_c_a alias");
|
|
Check((result.decoded_dump.find("image_sample_c_b_a") != std::string::npos),
|
|
"MIMG opcode 0xad should decode as image_sample_c_b_a alias");
|
|
Check((result.decoded_dump.find("sample_flags=adjust") != std::string::npos),
|
|
"opcode 0xa0 alias should expose SampleAdjust with normal 32-bit "
|
|
"coordinates");
|
|
Check((result.decoded_dump.find("sample_flags=bias|compare|adjust") != std::string::npos),
|
|
"compare+bias opcode alias did not expose expected sample flags");
|
|
Check((result.decoded_dump.find("a16") == std::string::npos),
|
|
"A16 must come from MIMG bit 62, not from the opcode alias");
|
|
Check(!SpirvContainsExtInst(result.spirv, 62),
|
|
"opcode aliases without bit 62 must not unpack sampled f16 address "
|
|
"halves");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageSampleA16ExceptionComponents() {
|
|
auto compile = [](uint32_t opcode, uint32_t dmask, uint32_t vdata) {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(opcode, dmask),
|
|
EncodeMimg1(vdata, 0, 0, 4, true),
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
return result;
|
|
};
|
|
|
|
{
|
|
const auto result = compile(0x28, 0x1, 12); // image_sample_c with A16 bit
|
|
Check((result.decoded_dump.find("sample_flags=compare|a16 addr_components=3") != std::string::npos),
|
|
"A16 IMAGE_SAMPLE_C did not preserve compare+A16 metadata");
|
|
Check((result.ir_dump.find("image_flags=0x88") != std::string::npos),
|
|
"A16 IMAGE_SAMPLE_C did not preserve compare+A16 IR flags");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 90) == 1,
|
|
"A16 IMAGE_SAMPLE_C should emit one dref sample");
|
|
Check(SpirvExtInstCount(result.spirv, 62) == 2,
|
|
"PCF reference must remain 32-bit while only xy sampler coords use "
|
|
"A16");
|
|
}
|
|
|
|
{
|
|
const auto result = compile(0x30, 0xf, 16); // image_sample_o with A16 bit
|
|
Check((result.decoded_dump.find("sample_flags=offset|a16 addr_components=3") != std::string::npos),
|
|
"A16 IMAGE_SAMPLE_O did not preserve offset+A16 metadata");
|
|
Check((result.ir_dump.find("image_flags=0x90") != std::string::npos),
|
|
"A16 IMAGE_SAMPLE_O did not preserve offset+A16 IR flags");
|
|
Check(SpirvContainsOpcode(result.spirv, 202),
|
|
"texel offset should still be decoded from its packed 6-bit fields");
|
|
Check(
|
|
SpirvExtInstCount(result.spirv, 62) == 2,
|
|
"texel offset must remain 32-bit while only xy sampler coords use A16");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageLoadA16UintCoords() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x00, 0x3),
|
|
EncodeMimg1(20, 0, 0, 4, true), // image_load, A16 bit
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_load") != std::string::npos),
|
|
"A16 IMAGE_LOAD did not decode");
|
|
Check((result.ir_dump.find("ImageLoad v20") != std::string::npos),
|
|
"A16 IMAGE_LOAD did not lower to image-load IR");
|
|
Check((result.ir_dump.find("image_flags=0x80") != std::string::npos),
|
|
"A16 IMAGE_LOAD did not preserve A16 memory metadata");
|
|
Check((result.ir_dump.find("image_addr=2") != std::string::npos),
|
|
"A16 IMAGE_LOAD did not preserve logical address component count");
|
|
Check(!SpirvContainsExtInst(result.spirv, 62),
|
|
"image ops without sampler use u16 A16 addresses and must not unpack "
|
|
"f16");
|
|
Check(SpirvContainsOpcode(result.spirv, 194),
|
|
"u16 A16 high-half coordinate extraction is missing");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"u16 A16 low-half coordinate masking is missing");
|
|
Check(SpirvContainsOpcode(result.spirv, 95),
|
|
"A16 IMAGE_LOAD did not emit an image fetch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerPixelImageSampleLodSelection() {
|
|
constexpr uint32_t OpImageSampleImplicitLod = 87;
|
|
constexpr uint32_t OpImageSampleExplicitLod = 88;
|
|
constexpr uint32_t OpImageSampleDrefImplicitLod = 89;
|
|
constexpr uint32_t OpImageSampleDrefExplicitLod = 90;
|
|
|
|
auto compile = [](uint32_t opcode, uint32_t dmask) {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(opcode, dmask),
|
|
EncodeMimg1(0, 0, 0, 4),
|
|
EncodeExp0(0x00, 0xf, true, false, true),
|
|
EncodeExp1(0, 1, 2, 3),
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto ps_info = RegressionPixelInputInfo();
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &ps_info;
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
return result;
|
|
};
|
|
|
|
{
|
|
const auto result = compile(0x20, 0xf); // image_sample
|
|
const auto metrics = MeasureSpirv(result.spirv);
|
|
Check(metrics.type_images == 1u && metrics.type_samplers == 1u &&
|
|
metrics.type_sampled_images == 1u &&
|
|
metrics.sampled_1d_capabilities == 0u &&
|
|
metrics.image_1d_capabilities == 0u &&
|
|
metrics.image_query_capabilities == 0u,
|
|
"plain 2D sample emitted unrelated image declarations");
|
|
Check((result.decoded_dump.find("image_sample") != std::string::npos),
|
|
"plain pixel IMAGE_SAMPLE did not decode");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleImplicitLod) ==
|
|
1,
|
|
"plain pixel IMAGE_SAMPLE must use OpImageSampleImplicitLod");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleExplicitLod) ==
|
|
0,
|
|
"plain pixel IMAGE_SAMPLE unexpectedly used explicit lod");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 245) == 0,
|
|
"plain pixel IMAGE_SAMPLE retained an EXEC-generated OpPhi");
|
|
}
|
|
{
|
|
const auto result = compile(0x25, 0xf); // image_sample_b
|
|
Check((result.decoded_dump.find("sample_flags=bias") != std::string::npos),
|
|
"pixel IMAGE_SAMPLE_B did not preserve bias metadata");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleImplicitLod) ==
|
|
1,
|
|
"pixel IMAGE_SAMPLE_B must use implicit lod with a bias operand");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleExplicitLod) ==
|
|
0,
|
|
"pixel IMAGE_SAMPLE_B unexpectedly used explicit lod");
|
|
}
|
|
{
|
|
const auto result = compile(0x27, 0xf); // image_sample_lz
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleExplicitLod) ==
|
|
1,
|
|
"pixel IMAGE_SAMPLE_LZ must use explicit lod");
|
|
}
|
|
{
|
|
const auto result = compile(0x28, 0x1); // image_sample_c
|
|
Check(SpirvInstructionOpcodeCount(result.spirv,
|
|
OpImageSampleDrefImplicitLod) == 1,
|
|
"pixel IMAGE_SAMPLE_C must use OpImageSampleDrefImplicitLod");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv,
|
|
OpImageSampleDrefExplicitLod) == 0,
|
|
"pixel IMAGE_SAMPLE_C unexpectedly used explicit dref lod");
|
|
}
|
|
{
|
|
const auto result = compile(0x2f, 0x1); // image_sample_c_lz
|
|
Check(SpirvInstructionOpcodeCount(result.spirv,
|
|
OpImageSampleDrefExplicitLod) == 1,
|
|
"pixel IMAGE_SAMPLE_C_LZ must use explicit dref lod");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageViewDimensions() {
|
|
constexpr uint32_t MimgDim1D = 0;
|
|
constexpr uint32_t MimgDim3D = 2;
|
|
constexpr uint32_t MimgDim1DArray = 4;
|
|
constexpr uint32_t MimgDim2DArray = 5;
|
|
constexpr uint32_t SpirvDim1D = 0;
|
|
constexpr uint32_t SpirvDim2D = 1;
|
|
constexpr uint32_t SpirvDim3D = 2;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x20, 0xf, false, MimgDim2DArray),
|
|
EncodeMimg1(0, 0, 1, 0), // image_sample 2D array
|
|
EncodeMimg0(0x24, 0xf, false, MimgDim2DArray),
|
|
EncodeMimg1(4, 1, 1, 4), // image_sample_l 2D array
|
|
EncodeMimg0(0x20, 0xf, false, MimgDim3D),
|
|
EncodeMimg1(8, 2, 1, 8), // image_sample 3D
|
|
EncodeMimg0(0x01, 0xf, false, MimgDim2DArray),
|
|
EncodeMimg1(12, 3, 0, 12), // image_load_mip 2D array
|
|
EncodeMimg0(0x20, 0xf, false, MimgDim1D),
|
|
EncodeMimg1(16, 4, 1, 16), // image_sample 1D
|
|
EncodeMimg0(0x20, 0xf, false, MimgDim1DArray),
|
|
EncodeMimg1(20, 5, 1, 20), // image_sample 1D array
|
|
EncodeMimg0(0x22, 0xf, false, MimgDim1D),
|
|
EncodeMimg1(24, 6, 1, 24), // image_sample_d 1D
|
|
EncodeMimg0(0x01, 0xf, false, MimgDim1D),
|
|
EncodeMimg1(28, 7, 0, 28), // image_load_mip 1D
|
|
EncodeMimg0(0x0e, 0xf, false, MimgDim1D),
|
|
EncodeMimg1(32, 8, 0, 32), // image_get_resinfo 1D
|
|
EncodeMimg0(0x00, 0xf, false, MimgDim1D),
|
|
EncodeMimg1(36, 9, 0, 36), // integer image_load 1D
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto ps_info = RegressionPixelInputInfo();
|
|
auto user_data = ImageTestUserData();
|
|
SetImageTestType(&user_data, 0, Prospero::ImageType::kColor2DArray);
|
|
SetImageTestType(&user_data, 1, Prospero::ImageType::kColor2DArray);
|
|
SetImageTestType(&user_data, 2, Prospero::ImageType::kColor3D);
|
|
SetImageTestType(&user_data, 3, Prospero::ImageType::kColor2DArray);
|
|
SetImageTestType(&user_data, 4, Prospero::ImageType::kColor1D);
|
|
SetImageTestType(&user_data, 5, Prospero::ImageType::kColor1DArray);
|
|
SetImageTestType(&user_data, 6, Prospero::ImageType::kColor1D);
|
|
SetImageTestType(&user_data, 7, Prospero::ImageType::kColor1D);
|
|
SetImageTestType(&user_data, 8, Prospero::ImageType::kColor1D);
|
|
SetImageTestType(&user_data, 9, Prospero::ImageType::kColor1D);
|
|
SetImageTestFormat(&user_data, 9, Prospero::BufferFormat::k32UInt);
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &ps_info;
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_dim=2d_array") != std::string::npos),
|
|
"MIMG DIM did not decode 2D-array image view");
|
|
Check((result.decoded_dump.find("image_dim=3d") != std::string::npos),
|
|
"MIMG DIM did not decode 3D image view");
|
|
Check((result.decoded_dump.find("image_dim=1d sample_flags=none addr_components=1") != std::string::npos),
|
|
"1D sample did not preserve its scalar coordinate");
|
|
Check((result.decoded_dump.find("image_dim=1d_array sample_flags=none addr_components=2") != std::string::npos),
|
|
"1D-array sample did not preserve coordinate plus layer");
|
|
Check((result.decoded_dump.find("image_dim=1d sample_flags=derivative addr_components=3") != std::string::npos),
|
|
"1D derivative sample did not preserve scalar gradients");
|
|
Check((result.decoded_dump.find("image_sample_l") != std::string::npos),
|
|
"2D-array LOD sample did not decode");
|
|
Check(
|
|
(result.decoded_dump.find("image_dim=2d_array sample_flags=lod addr_components=4") != std::string::npos),
|
|
"2D-array LOD sample did not include slice plus LOD address components");
|
|
Check((result.ir_dump.find("image_dim=2d_array") != std::string::npos),
|
|
"2D-array image view did not survive into IR metadata");
|
|
Check((result.ir_dump.find("image_dim=3d") != std::string::npos),
|
|
"3D image view did not survive into IR metadata");
|
|
Check((result.ir_dump.find("image_dim=1d") != std::string::npos),
|
|
"1D image view did not survive into IR metadata");
|
|
Check((result.ir_dump.find("image_dim=1d_array") != std::string::npos),
|
|
"1D-array image view did not survive into IR metadata");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 0, 1),
|
|
"SPIR-V binary does not contain sampled 1D image type");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 1, 1),
|
|
"SPIR-V binary does not contain sampled 1D-array image type");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim2D, 1, 1),
|
|
"SPIR-V binary does not contain sampled 2D-array image type");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim3D, 0, 1),
|
|
"SPIR-V binary does not contain sampled 3D image type");
|
|
Check(SpirvContainsCapability(result.spirv, 43),
|
|
"SPIR-V binary does not request Sampled1D");
|
|
Check(SpirvContainsCapability(result.spirv, 44),
|
|
"SPIR-V binary does not request Image1D");
|
|
Check(SpirvContainsOpcode(result.spirv, 95),
|
|
"SPIR-V binary does not contain array image fetch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check((source.find(" Grad ") != std::string::npos),
|
|
"1D derivative sample did not emit scalar SPIR-V gradients");
|
|
Check((source.find("OpImageQuerySizeLod") != std::string::npos),
|
|
"1D resource query did not emit a scalar size query");
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_8 "),
|
|
"integer 1D load did not access the uint 1D descriptor binding");
|
|
}
|
|
|
|
void TestNewShaderRecompilerStorageImage1DDescriptorVariants() {
|
|
constexpr uint32_t MimgDim1D = 0;
|
|
constexpr uint32_t MimgDim1DArray = 4;
|
|
constexpr uint32_t SpirvDim1D = 0;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x08, 0xf, false, MimgDim1D),
|
|
EncodeMimg1(20, 4, 0, 4), // image_store 1D
|
|
EncodeMimg0(0x08, 0xf, false, MimgDim1DArray),
|
|
EncodeMimg1(24, 5, 0, 8), // image_store 1D array
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
SetImageTestType(&user_data, 4, Prospero::ImageType::kColor1D);
|
|
SetImageTestType(&user_data, 5, Prospero::ImageType::kColor1DArray);
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("image_dim=1d") != std::string::npos),
|
|
"1D storage dimension did not survive into IR metadata");
|
|
Check((result.ir_dump.find("image_dim=1d_array") != std::string::npos),
|
|
"1D-array storage dimension did not survive into IR metadata");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 0, 2),
|
|
"SPIR-V binary does not contain storage 1D image type");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 1, 2),
|
|
"SPIR-V binary does not contain storage 1D-array image type");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_22 "),
|
|
"1D store did not access the 1D storage descriptor binding");
|
|
Check(
|
|
SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_23 "),
|
|
"1D-array store did not access the 1D-array storage descriptor binding");
|
|
}
|
|
|
|
void TestNewShaderRecompilerNullImageUsesCanonical2DView() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x20, 0xf, false, 0),
|
|
EncodeMimg1(0, 0, 1, 0), // 1D instruction with a null image descriptor
|
|
0xbf810000u,
|
|
};
|
|
std::array<uint32_t, 64> user_data{};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_3 "),
|
|
"null image did not use the canonical 2D sampled descriptor binding");
|
|
Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_1 "),
|
|
"null image retained the decoded 1D descriptor binding");
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageGatherVariants() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x47, 0x1),
|
|
EncodeMimg1(60, 0, 1, 4), // image_gather4_lz
|
|
EncodeMimg0(0x48, 0x2),
|
|
EncodeMimg1(64, 0, 1, 8), // image_gather4_c
|
|
EncodeMimg0(0x4f, 0x1),
|
|
EncodeMimg1(68, 0, 1, 12), // image_gather4_c_lz
|
|
EncodeMimg0(0x57, 0x4),
|
|
EncodeMimg1(72, 0, 1, 16), // image_gather4_lz_o
|
|
EncodeMimg0(0x58, 0x1),
|
|
EncodeMimg1(76, 0, 1, 20), // image_gather4_c_o
|
|
EncodeMimg0(0x5f, 0x1),
|
|
EncodeMimg1(80, 0, 1, 24), // image_gather4_c_lz_o
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_gather4_lz") != std::string::npos),
|
|
"new decoder did not decode IMAGE_GATHER4_LZ");
|
|
Check((result.decoded_dump.find("image_gather4_lz_o") != std::string::npos),
|
|
"new decoder did not decode IMAGE_GATHER4_LZ_O");
|
|
Check((result.decoded_dump.find("image_gather4_c") != std::string::npos),
|
|
"new decoder did not decode IMAGE_GATHER4_C");
|
|
Check((result.decoded_dump.find("image_gather4_c_lz") != std::string::npos),
|
|
"new decoder did not decode IMAGE_GATHER4_C_LZ");
|
|
Check((result.decoded_dump.find("image_gather4_c_o") != std::string::npos),
|
|
"new decoder did not decode IMAGE_GATHER4_C_O");
|
|
Check((result.decoded_dump.find("image_gather4_c_lz_o") != std::string::npos),
|
|
"new decoder did not decode IMAGE_GATHER4_C_LZ_O");
|
|
Check((result.decoded_dump.find("dmask=0x4") != std::string::npos),
|
|
"IMAGE_GATHER4_LZ_O did not preserve gather component dmask");
|
|
Check((result.decoded_dump.find("sample_flags=offset|level_zero addr_components=3") != std::string::npos),
|
|
"IMAGE_GATHER4_LZ_O did not expose shared offset sample metadata");
|
|
Check((result.decoded_dump.find("sample_flags=compare addr_components=3") != std::string::npos),
|
|
"IMAGE_GATHER4_C did not expose compare sample metadata");
|
|
Check(
|
|
(result.decoded_dump.find("sample_flags=compare|level_zero addr_components=3") != std::string::npos),
|
|
"IMAGE_GATHER4_C_LZ did not expose compare+level-zero sample metadata");
|
|
Check((result.decoded_dump.find("sample_flags=compare|offset addr_components=4") != std::string::npos),
|
|
"IMAGE_GATHER4_C_O did not expose compare+offset sample metadata");
|
|
Check((result.decoded_dump.find("sample_flags=compare|offset|level_zero addr_components=4") != std::string::npos),
|
|
"IMAGE_GATHER4_C_LZ_O did not expose compare+offset+level-zero sample "
|
|
"metadata");
|
|
Check((result.ir_dump.find("ImageGather4 v60") != std::string::npos),
|
|
"IMAGE_GATHER4_LZ did not lower to shared IR ImageGather4");
|
|
Check((result.ir_dump.find("ImageGather4 v64") != std::string::npos),
|
|
"IMAGE_GATHER4_C did not lower to shared IR ImageGather4");
|
|
Check((result.ir_dump.find("ImageGather4 v68") != std::string::npos),
|
|
"IMAGE_GATHER4_C_LZ did not lower to shared IR ImageGather4");
|
|
Check((result.ir_dump.find("ImageGather4 v72") != std::string::npos),
|
|
"IMAGE_GATHER4_LZ_O did not lower to shared IR ImageGather4");
|
|
Check((result.ir_dump.find("ImageGather4 v76") != std::string::npos),
|
|
"IMAGE_GATHER4_C_O did not lower to shared IR ImageGather4");
|
|
Check((result.ir_dump.find("ImageGather4 v80") != std::string::npos),
|
|
"IMAGE_GATHER4_C_LZ_O did not lower to shared IR ImageGather4");
|
|
Check((result.ir_dump.find("data_dwords=4") != std::string::npos),
|
|
"IMAGE_GATHER4 did not preserve four-component result metadata");
|
|
Check((result.ir_dump.find("image_flags=0x30") != std::string::npos),
|
|
"IMAGE_GATHER4_LZ_O flags did not survive into IR memory metadata");
|
|
Check((result.ir_dump.find("image_flags=0x8") != std::string::npos),
|
|
"IMAGE_GATHER4_C flags did not survive into IR memory metadata");
|
|
Check((result.ir_dump.find("image_flags=0x28") != std::string::npos),
|
|
"IMAGE_GATHER4_C_LZ flags did not survive into IR memory metadata");
|
|
Check((result.ir_dump.find("image_flags=0x18") != std::string::npos),
|
|
"IMAGE_GATHER4_C_O flags did not survive into IR memory metadata");
|
|
Check((result.ir_dump.find("image_flags=0x38") != std::string::npos),
|
|
"IMAGE_GATHER4_C_LZ_O flags did not survive into IR memory metadata");
|
|
Check(SpirvContainsCapability(result.spirv, 25),
|
|
"SPIR-V binary does not request ImageGatherExtended");
|
|
Check(SpirvContainsOpcode(result.spirv, 96),
|
|
"SPIR-V binary does not contain OpImageGather");
|
|
Check(SpirvContainsOpcode(result.spirv, 97),
|
|
"SPIR-V binary does not contain OpImageDrefGather");
|
|
Check(SpirvContainsOpcode(result.spirv, 202),
|
|
"SPIR-V binary does not contain packed gather offset extraction");
|
|
Check(SpirvContainsOpcode(result.spirv, 81),
|
|
"SPIR-V binary does not contain gather result extraction");
|
|
Check(SpirvContainsOpcode(result.spirv, 124),
|
|
"SPIR-V binary does not contain gather result bitcast");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageLoadVariants() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x00, 0x3),
|
|
EncodeMimg1(8, 0, 0, 1), // image_load dmask xy
|
|
EncodeMimg0(0x01, 0xf),
|
|
EncodeMimg1(12, 1, 0, 4), // image_load_mip dmask xyzw
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_load") != std::string::npos),
|
|
"new decoder did not decode MIMG image load");
|
|
Check((result.decoded_dump.find("image_load_mip") != std::string::npos),
|
|
"new decoder did not decode MIMG image load mip");
|
|
Check((result.decoded_dump.find("dmask=0x3") != std::string::npos),
|
|
"image_load decode did not preserve partial dmask");
|
|
Check((result.decoded_dump.find("dmask=0xf") != std::string::npos),
|
|
"image_load_mip decode did not preserve full dmask");
|
|
Check((result.ir_dump.find("ImageLoad v8") != std::string::npos),
|
|
"image_load did not lower through shared image-load IR");
|
|
Check((result.ir_dump.find("ImageLoad v12") != std::string::npos),
|
|
"image_load_mip did not lower through shared image-load IR");
|
|
Check((result.ir_dump.find("data_dwords=2") != std::string::npos),
|
|
"image_load dmask xy did not preserve two-component result metadata");
|
|
Check((result.ir_dump.find("data_dwords=4") != std::string::npos),
|
|
"image_load_mip dmask xyzw did not preserve four-component result "
|
|
"metadata");
|
|
Check((result.ir_dump.find("image_addr=3 image_mip=1") != std::string::npos),
|
|
"image_load_mip did not preserve mip address component metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 95),
|
|
"SPIR-V binary does not contain OpImageFetch");
|
|
Check(
|
|
SpirvContainsOpcode(result.spirv, 81),
|
|
"SPIR-V binary does not contain OpCompositeExtract for image-load dmask");
|
|
Check(SpirvContainsOpcode(result.spirv, 124),
|
|
"SPIR-V binary does not contain OpBitcast for image-load result bits");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageLoad2DMsaa() {
|
|
const uint32_t shader[] = {
|
|
0xf0000130u, // image_load v3, v[5:7], s[0:7] dmask:x dim:2d_msaa
|
|
0x00000305u,
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData(Prospero::ImageType::kColor2DMsaa);
|
|
user_data[3] |= 2u << 16u;
|
|
user_data[5] |= 2u << 4u;
|
|
user_data[6] |= 1u << 10u;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_dim=2d_msaa") != std::string::npos) &&
|
|
(result.ir_dump.find("image_dim=2d_msaa") != std::string::npos) &&
|
|
(result.ir_dump.find("image_addr=3 image_mip=0") != std::string::npos),
|
|
"RDNA2 2D-MSAA load did not preserve x, y, and fragment ID");
|
|
Check(result.program.info.images.size() == 1 &&
|
|
result.program.info.images[0].dimension ==
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa,
|
|
"2D-MSAA descriptor specialization lost the multisample dimension");
|
|
const auto binding_kind = ShaderRecompiler::IR::DescriptorBindingForImage(
|
|
result.program.info.images[0]);
|
|
Check(binding_kind.has_value() &&
|
|
ShaderRecompiler::IR::FindBinding(result.program.bindings,
|
|
*binding_kind) != nullptr,
|
|
"2D-MSAA image did not receive a multisampled descriptor binding");
|
|
Check(SpirvContainsTypeImage(result.spirv, 1, 0, 1, 1),
|
|
"SPIR-V binary does not contain a multisampled 2D image type");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_5 "),
|
|
"2D-MSAA load did not access its multisampled descriptor");
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpImageFetch", " Sample "),
|
|
"2D-MSAA load did not emit the fragment ID as a SPIR-V Sample operand");
|
|
Check(!SpirvSourceHasInstructionUsing(source, "OpImageFetch", " Lod "),
|
|
"2D-MSAA load incorrectly emitted its fragment ID as a mip level");
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageStoreTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x08, 0xf),
|
|
EncodeMimg1(20, 0, 0, 4), // image_store
|
|
EncodeMimg0(0x09, 0x3),
|
|
EncodeMimg1(24, 1, 0, 8), // image_store_mip
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_store") != std::string::npos),
|
|
"new decoder did not decode MIMG image store");
|
|
Check((result.decoded_dump.find("image_store_mip") != std::string::npos),
|
|
"new decoder did not decode MIMG image store mip");
|
|
Check((result.decoded_dump.find("dmask=0xf") != std::string::npos),
|
|
"image store decode did not preserve full dmask");
|
|
Check((result.decoded_dump.find("dmask=0x3") != std::string::npos),
|
|
"image store mip decode did not preserve partial dmask");
|
|
Check((result.ir_dump.find("ImageStore null, v20, v4") != std::string::npos),
|
|
"image_store did not lower to shared image-store IR");
|
|
Check((result.ir_dump.find("ImageStore null, v24, v8") != std::string::npos),
|
|
"image_store_mip did not lower to shared image-store IR");
|
|
Check((result.ir_dump.find("storage_image") != std::string::npos),
|
|
"image store IR did not use storage-image resource metadata");
|
|
Check((result.ir_dump.find("data_dwords=4") != std::string::npos),
|
|
"full-dmask image store did not preserve data component count");
|
|
Check((result.ir_dump.find("image_addr=3 image_mip=1") != std::string::npos),
|
|
"mip image store did not preserve mip address component count");
|
|
Check(SpirvContainsOpcode(result.spirv, 99),
|
|
"SPIR-V binary does not contain OpImageWrite");
|
|
Check(SpirvContainsOpcode(result.spirv, 80),
|
|
"SPIR-V binary does not contain image texel/coordinate composites");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerStorageImage3DDescriptorVariant() {
|
|
constexpr uint32_t ImageTypeColor3D = 10;
|
|
constexpr uint32_t ImageFormatRgba16f = 71;
|
|
constexpr uint32_t SpirvDim3D = 2;
|
|
|
|
constexpr uint32_t MimgDim3D = 2;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x08, 0xf, false, MimgDim3D),
|
|
EncodeMimg1(20, 5, 0, 4), // image_store 3D
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderComputeInputInfo input_info = RegressionComputeInputInfo();
|
|
std::array<uint32_t, 64> user_data{};
|
|
user_data[20] = 0x1000u;
|
|
user_data[21] = ImageFormatRgba16f << 20u;
|
|
user_data[22] = 255u | (255u << 14u);
|
|
user_data[23] = ImageTypeColor3D << 28u;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.input_info.compute = &input_info;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_dim=3d") != std::string::npos),
|
|
"MIMG store did not decode the RDNA2 3D instruction dimension");
|
|
const auto has_3d_store = std::ranges::any_of(
|
|
result.program.blocks, [&](const auto *block) {
|
|
return std::ranges::any_of(*block, [&](const auto &inst) {
|
|
if (inst.GetOpcode() !=
|
|
ShaderRecompiler::IR::ValueOpcode::ImageWrite) {
|
|
return false;
|
|
}
|
|
const auto index =
|
|
inst.template Flags<ShaderRecompiler::IR::MemoryFlags>().index;
|
|
return index < result.program.memory_info.size() &&
|
|
result.program.memory_info[index].image_dimension ==
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
|
|
result.program.memory_info[index].image_address_components ==
|
|
3u;
|
|
});
|
|
});
|
|
Check(has_3d_store,
|
|
"3D storage image store did not preserve three address components");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim3D, 0, 2),
|
|
"SPIR-V binary does not contain storage 3D image type");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_26 "),
|
|
"storage image store did not access the 3D storage descriptor binding");
|
|
}
|
|
|
|
void TestNewShaderRecompilerStorageImage2DDescriptorOverridesMimg3D() {
|
|
constexpr uint32_t ImageTypeColor2D = 9;
|
|
constexpr uint32_t ImageFormatRgba16f = 71;
|
|
constexpr uint32_t MimgDim3D = 2;
|
|
constexpr uint32_t SpirvDim2D = 1;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x08, 0xf, false, MimgDim3D),
|
|
EncodeMimg1(20, 5, 0, 4),
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderComputeInputInfo input_info = RegressionComputeInputInfo();
|
|
std::array<uint32_t, 64> user_data{};
|
|
user_data[20] = 0x1000u;
|
|
user_data[21] = ImageFormatRgba16f << 20u;
|
|
user_data[22] = 255u | (255u << 14u);
|
|
user_data[23] = ImageTypeColor2D << 28u;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.input_info.compute = &input_info;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_dim=3d") != std::string::npos),
|
|
"test MIMG store should decode as a 3D instruction");
|
|
Check(SpirvContainsTypeImage(result.spirv, SpirvDim2D, 0, 2),
|
|
"SPIR-V binary does not contain storage 2D image type");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_24 "),
|
|
"2D descriptor storage image store did not access the base storage "
|
|
"binding");
|
|
Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_25 "),
|
|
"2D descriptor storage image store unexpectedly used the array storage "
|
|
"binding");
|
|
Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_26 "),
|
|
"2D descriptor storage image store unexpectedly used the 3D storage "
|
|
"binding");
|
|
}
|
|
|
|
void TestNewShaderRecompilerImageAtomicTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMimg0(0x11, 0x1, true),
|
|
EncodeMimg1(52, 0, 0, 1), // image_atomic_add
|
|
EncodeMimg0(0x15, 0x1, true),
|
|
EncodeMimg1(53, 0, 0, 1), // image_atomic_umin
|
|
EncodeMimg0(0x17, 0x1, true),
|
|
EncodeMimg1(57, 0, 0, 1), // image_atomic_umax
|
|
EncodeMimg0(0x18, 0x1, true),
|
|
EncodeMimg1(54, 0, 0, 1), // image_atomic_and
|
|
EncodeMimg0(0x19, 0x1, true),
|
|
EncodeMimg1(55, 0, 0, 1), // image_atomic_or
|
|
EncodeMimg0(0x1a, 0x1, true),
|
|
EncodeMimg1(56, 0, 0, 1), // image_atomic_xor
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("image_atomic_add") != std::string::npos),
|
|
"new decoder did not decode MIMG image atomic add");
|
|
Check((result.decoded_dump.find("image_atomic_umin") != std::string::npos),
|
|
"new decoder did not decode MIMG image atomic umin");
|
|
Check((result.decoded_dump.find("image_atomic_umax") != std::string::npos),
|
|
"new decoder did not decode MIMG image atomic umax");
|
|
Check((result.decoded_dump.find("image_atomic_and") != std::string::npos),
|
|
"new decoder did not decode MIMG image atomic and");
|
|
Check((result.decoded_dump.find("image_atomic_or") != std::string::npos),
|
|
"new decoder did not decode MIMG image atomic or");
|
|
Check((result.decoded_dump.find("image_atomic_xor") != std::string::npos),
|
|
"new decoder did not decode MIMG image atomic xor");
|
|
Check((result.decoded_dump.find("dmask=0x1") != std::string::npos),
|
|
"image atomic decode did not preserve dmask metadata");
|
|
Check((result.ir_dump.find("AtomicAddU32 v52, v52, v1") != std::string::npos),
|
|
"image_atomic_add did not lower through shared atomic IR");
|
|
Check((result.ir_dump.find("AtomicUMinU32 v53, v53, v1") != std::string::npos),
|
|
"image_atomic_umin did not lower through shared atomic IR");
|
|
Check((result.ir_dump.find("AtomicUMaxU32 v57, v57, v1") != std::string::npos),
|
|
"image_atomic_umax did not lower through shared atomic IR");
|
|
Check((result.ir_dump.find("AtomicAndU32 v54, v54, v1") != std::string::npos),
|
|
"image_atomic_and did not lower through shared atomic IR");
|
|
Check((result.ir_dump.find("AtomicOrU32 v55, v55, v1") != std::string::npos),
|
|
"image_atomic_or did not lower through shared atomic IR");
|
|
Check((result.ir_dump.find("AtomicXorU32 v56, v56, v1") != std::string::npos),
|
|
"image_atomic_xor did not lower through shared atomic IR");
|
|
Check((result.ir_dump.find("storage_image_uint") != std::string::npos),
|
|
"image atomic IR did not use uint storage-image metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 60),
|
|
"SPIR-V binary does not contain OpImageTexelPointer");
|
|
Check(SpirvContainsOpcode(result.spirv, 234),
|
|
"SPIR-V binary does not contain OpAtomicIAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 237),
|
|
"SPIR-V binary does not contain OpAtomicUMin");
|
|
Check(SpirvContainsOpcode(result.spirv, 239),
|
|
"SPIR-V binary does not contain OpAtomicUMax");
|
|
Check(SpirvContainsOpcode(result.spirv, 240),
|
|
"SPIR-V binary does not contain OpAtomicAnd");
|
|
Check(SpirvContainsOpcode(result.spirv, 241),
|
|
"SPIR-V binary does not contain OpAtomicOr");
|
|
Check(SpirvContainsOpcode(result.spirv, 242),
|
|
"SPIR-V binary does not contain OpAtomicXor");
|
|
Check(SpirvContainsOpcode(result.spirv, 225),
|
|
"image atomic SPIR-V binary does not contain OpMemoryBarrier");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerVintrpTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeVintrp(0, 10, 1, 2, 4), // v_interp_p1_f32 v10, v4, attr1.z
|
|
EncodeVintrp(1, 11, 1, 2, 4), // v_interp_p2_f32 v11, v4, attr1.z
|
|
EncodeVintrp(2, 12, 0, 3, 2), // v_interp_mov_f32 v12, p0, attr0.w
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderPixelInputInfo ps_info{};
|
|
ps_info.input_num = 2;
|
|
SetIdentityInterpolatorSettings(&ps_info);
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &ps_info;
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("v_interp_p1_f32") != std::string::npos),
|
|
"new decoder did not decode VINTRP P1");
|
|
Check((result.decoded_dump.find("v_interp_p2_f32") != std::string::npos),
|
|
"new decoder did not decode VINTRP P2");
|
|
Check((result.decoded_dump.find("v_interp_mov_f32") != std::string::npos),
|
|
"new decoder did not decode VINTRP MOV");
|
|
Check((result.ir_dump.find("ControlNop") != std::string::npos),
|
|
"VINTRP P1 did not lower to an explicit no-op marker");
|
|
Check((result.ir_dump.find("LoadInputF32 v11") != std::string::npos),
|
|
"VINTRP P2 did not lower to input-load IR");
|
|
Check((result.ir_dump.find("input_attr=1 input_chan=2") != std::string::npos),
|
|
"VINTRP P2 did not preserve attr/channel metadata");
|
|
Check((result.ir_dump.find("LoadInputF32 v12") != std::string::npos),
|
|
"VINTRP MOV did not lower to input-load IR");
|
|
Check((result.ir_dump.find("input_attr=0 input_chan=3") != std::string::npos),
|
|
"VINTRP MOV did not preserve attr/channel metadata");
|
|
Check(ProgramInputCount(result.program,
|
|
ShaderRecompiler::IR::StageInputKind::Parameter) == 2,
|
|
"VINTRP pixel shader did not reflect parameter inputs");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 81),
|
|
"SPIR-V binary does not contain input component extraction");
|
|
Check(SpirvContainsOpcode(result.spirv, 124),
|
|
"SPIR-V binary does not contain input bitcast");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t remapped_shader[] = {
|
|
EncodeVintrp(1, 11, 2, 0, 4), // v_interp_p2_f32 v11, v4, attr2.x
|
|
0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo remapped_ps_info{};
|
|
remapped_ps_info.input_num = 3;
|
|
SetIdentityInterpolatorSettings(&remapped_ps_info);
|
|
remapped_ps_info.interpolator_settings[2] = 3;
|
|
|
|
options.input_info.pixel = &remapped_ps_info;
|
|
|
|
auto remapped_result = RecompileForTest(remapped_shader, options);
|
|
Check(
|
|
(remapped_result.ir_dump.find("input_attr=2 input_chan=0") != std::string::npos),
|
|
"remapped VINTRP did not preserve raw pixel attribute metadata");
|
|
Check(
|
|
SpirvHasDecorationValue(remapped_result.spirv, 30u, 3u),
|
|
"remapped VINTRP did not use SPI_PS_INPUT_CNTL input offset as Location");
|
|
Check(!SpirvHasDecorationValue(remapped_result.spirv, 30u, 2u),
|
|
"remapped VINTRP still emitted the raw attribute as Location");
|
|
CheckSpirvBinaryValidates(remapped_result.spirv);
|
|
|
|
const uint32_t duplicate_location_shader[] = {
|
|
EncodeVintrp(0, 6, 1, 0, 4), // v_interp_p1_f32 v6, v4, attr1.x
|
|
EncodeVintrp(0, 10, 0, 0, 4), // v_interp_p1_f32 v10, v4, attr0.x
|
|
EncodeVintrp(1, 6, 1, 0, 4), // v_interp_p2_f32 v6, v4, attr1.x
|
|
EncodeVintrp(1, 10, 0, 0, 4), // v_interp_p2_f32 v10, v4, attr0.x
|
|
0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo duplicate_ps_info{};
|
|
duplicate_ps_info.input_num = 2;
|
|
|
|
options.input_info.pixel = &duplicate_ps_info;
|
|
|
|
auto duplicate_result =
|
|
RecompileForTest(duplicate_location_shader, options);
|
|
Check(ProgramInputCount(duplicate_result.program,
|
|
ShaderRecompiler::IR::StageInputKind::Parameter) == 2,
|
|
"duplicate-location VINTRP shader did not reflect both raw parameter "
|
|
"attrs");
|
|
Check(SpirvDecorationValueCount(duplicate_result.spirv, 30u, 0u) == 1,
|
|
"duplicate-location VINTRP emitted more than one Location 0 input");
|
|
Check(SpirvDecorationValueCount(duplicate_result.spirv, 30u, 1u) == 1,
|
|
"duplicate-location VINTRP did not fall back attr1 to Location 1");
|
|
CheckSpirvBinaryValidates(duplicate_result.spirv);
|
|
|
|
const uint32_t flat_shader[] = {
|
|
EncodeVintrp(2, 12, 0, 0, 2), // v_interp_mov_f32 v12, p0, attr0.x
|
|
0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo flat_ps_info{};
|
|
flat_ps_info.input_num = 1;
|
|
SetIdentityInterpolatorSettings(&flat_ps_info);
|
|
flat_ps_info.interpolator_settings[0] = 0x00000400u;
|
|
|
|
options.input_info.pixel = &flat_ps_info;
|
|
|
|
auto flat_result = RecompileForTest(flat_shader, options);
|
|
Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u),
|
|
"flat VINTRP input did not emit a Flat decoration");
|
|
Check(!SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 13u),
|
|
"flat VINTRP input should not also emit NoPerspective");
|
|
Check(SpirvHasDecorationValue(flat_result.spirv, 30u, 0u),
|
|
"flat VINTRP input did not preserve Location 0");
|
|
CheckSpirvBinaryValidates(flat_result.spirv);
|
|
|
|
ShaderPixelInputInfo no_perspective_ps_info{};
|
|
no_perspective_ps_info.input_num = 1;
|
|
no_perspective_ps_info.ps_no_perspective = true;
|
|
SetIdentityInterpolatorSettings(&no_perspective_ps_info);
|
|
|
|
options.input_info.pixel = &no_perspective_ps_info;
|
|
|
|
auto no_perspective_result =
|
|
RecompileForTest(flat_shader, options);
|
|
Check(SpirvHasDecorationValueWithDecoration(no_perspective_result.spirv, 30u,
|
|
0u, 13u),
|
|
"no-perspective VINTRP input did not emit a NoPerspective decoration");
|
|
Check(!SpirvHasDecorationValueWithDecoration(no_perspective_result.spirv, 30u,
|
|
0u, 14u),
|
|
"non-flat no-perspective VINTRP input should not emit Flat");
|
|
CheckSpirvBinaryValidates(no_perspective_result.spirv);
|
|
}
|
|
|
|
void TestCustomVintrpMovTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeVintrp(2, 12, 0, 3, 2), EncodeVintrp(2, 13, 0, 3, 0),
|
|
EncodeVintrp(2, 14, 0, 3, 1), EncodeVop2(0x03, 15, 12 + 256, 0),
|
|
EncodeVop2(0x03, 16, 13 + 256, 1), EncodeExp0(0x00, 0xf),
|
|
EncodeExp1(15, 16, 14, 12), 0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo custom_ps_info{};
|
|
custom_ps_info.input_num = 1;
|
|
custom_ps_info.ps_system_input_base = 2;
|
|
custom_ps_info.custom_interpolation_mask = 1;
|
|
custom_ps_info.ps_perspective_center_vgpr = 0;
|
|
SetIdentityInterpolatorSettings(&custom_ps_info);
|
|
custom_ps_info.interpolator_settings[0] = 0x00000420u;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &custom_ps_info;
|
|
options.dump_ir = true;
|
|
|
|
auto custom_result = RecompileForTest(shader, options);
|
|
Check((custom_result.ir_dump.find("GetInterpolationParameter 0x00000000, 0x00000003, 0x00000000") != std::string::npos),
|
|
"custom VINTRP did not preserve P10 mode");
|
|
Check((custom_result.ir_dump.find("GetInterpolationParameter 0x00000000, 0x00000003, 0x00000001") != std::string::npos),
|
|
"custom VINTRP did not preserve P20 mode");
|
|
Check((custom_result.ir_dump.find("GetInterpolationParameter 0x00000000, 0x00000003, 0x00000002") != std::string::npos),
|
|
"custom VINTRP did not preserve P0 mode");
|
|
Check(SpirvContainsCapability(custom_result.spirv, 5284u),
|
|
"custom VINTRP did not enable FragmentBarycentricKHR");
|
|
Check(SpirvHasDecorationValueWithDecoration(custom_result.spirv, 30u, 0u,
|
|
5285u),
|
|
"custom VINTRP input did not emit PerVertexKHR");
|
|
Check(
|
|
!SpirvHasDecorationValueWithDecoration(custom_result.spirv, 30u, 0u, 14u),
|
|
"custom VINTRP input was misclassified as flat");
|
|
Check(SpirvHasDecorationValue(custom_result.spirv, 11u, 5286u),
|
|
"perspective-center VGPRs did not use BaryCoordKHR");
|
|
const auto source = DisassembleSpirvBinary(custom_result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"in_param_0 %uint_0 %uint_3"),
|
|
"custom VINTRP P0 did not select vertex 0");
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"in_param_0 %uint_1 %uint_3"),
|
|
"custom VINTRP P10 did not select vertex 1");
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"in_param_0 %uint_2 %uint_3"),
|
|
"custom VINTRP P20 did not select vertex 2");
|
|
Check((source.find("OpFSub") == std::string::npos),
|
|
"custom VINTRP incorrectly applied hardware delta subtraction");
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"gl_BaryCoordKHR %uint_1") &&
|
|
SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"gl_BaryCoordKHR %uint_2") &&
|
|
!SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"gl_BaryCoordKHR %uint_0"),
|
|
"guest perspective-center I/J did not map to BaryCoordKHR Y/Z");
|
|
CheckSpirvBinaryValidates(custom_result.spirv);
|
|
|
|
custom_ps_info.custom_interpolation_mask = 0;
|
|
custom_ps_info.interpolator_settings[0] = 0;
|
|
auto standard_result = RecompileForTest(shader, options);
|
|
Check(SpirvInstructionOpcodeCount(standard_result.spirv, 131u) == 2u,
|
|
"standard VINTRP P10/P20 did not subtract P0 exactly once each");
|
|
CheckSpirvBinaryValidates(standard_result.spirv);
|
|
|
|
const uint32_t flat_shader[] = {
|
|
EncodeVintrp(2, 12, 0, 3, 2),
|
|
EncodeExp0(0x00, 0x1),
|
|
EncodeExp1(12, 0, 0, 0),
|
|
0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo flat_ps_info{};
|
|
flat_ps_info.input_num = 1;
|
|
flat_ps_info.interpolator_settings[0] = 0x00000400u;
|
|
options.input_info.pixel = &flat_ps_info;
|
|
auto flat_result = RecompileForTest(flat_shader, options);
|
|
Check(!SpirvContainsCapability(flat_result.spirv, 5284u),
|
|
"flat P0 unexpectedly required fragment barycentric support");
|
|
Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u),
|
|
"flat P0 did not retain ordinary flat interpolation");
|
|
CheckSpirvBinaryValidates(flat_result.spirv);
|
|
|
|
const uint32_t mixed_shader[] = {
|
|
EncodeVintrp(0, 12, 0, 3, 0),
|
|
EncodeVintrp(1, 12, 0, 3, 0),
|
|
EncodeVintrp(2, 13, 0, 3, 2),
|
|
EncodeVop2(0x03, 14, 12 + 256, 13),
|
|
EncodeExp0(0x00, 0x1),
|
|
EncodeExp1(14, 0, 0, 0),
|
|
0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo mixed_ps_info{};
|
|
mixed_ps_info.input_num = 1;
|
|
options.input_info.pixel = &mixed_ps_info;
|
|
auto mixed_result = RecompileForTest(mixed_shader, options);
|
|
Check(SpirvHasDecorationValue(mixed_result.spirv, 11u, 5286u) &&
|
|
SpirvInstructionOpcodeCount(mixed_result.spirv, 133u) == 3u,
|
|
"mixed ordinary/P0 input was not interpolated from per-vertex values");
|
|
CheckSpirvBinaryValidates(mixed_result.spirv);
|
|
|
|
mixed_ps_info.ps_no_perspective = true;
|
|
auto mixed_linear_result = RecompileForTest(mixed_shader, options);
|
|
Check(SpirvHasDecorationValue(mixed_linear_result.spirv, 11u, 5287u),
|
|
"mixed linear input did not use BaryCoordNoPerspKHR");
|
|
CheckSpirvBinaryValidates(mixed_linear_result.spirv);
|
|
}
|
|
|
|
void TestPerspectiveCentroidInputs() {
|
|
constexpr std::array cases{std::array{4u, UINT32_MAX, 0u},
|
|
std::array{5u, UINT32_MAX, 2u},
|
|
std::array{6u, 0u, 2u}, std::array{7u, 2u, 4u}};
|
|
for (const auto &[inputs, center, centroid] : cases) {
|
|
const uint32_t other = center == UINT32_MAX ? centroid : center;
|
|
const std::vector<uint32_t> shader = {
|
|
EncodeExp0(0x00, 0xf), EncodeExp1(centroid, centroid + 1, other, other + 1),
|
|
EncodeSopp(0x01)};
|
|
HW::PixelShaderInfo regs{};
|
|
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader.data());
|
|
ShaderMappedData mapped{};
|
|
mapped.code_size_bytes = shader.size() * sizeof(uint32_t);
|
|
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
|
HW::ShaderRegisters sh{};
|
|
sh.ps_input_ena = sh.ps_input_addr = inputs;
|
|
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
|
|
ShaderPixelInputInfo pixel{};
|
|
(void)PrepareProgram(regs, sh, mappings, pixel);
|
|
Check(pixel.ps_perspective_centroid_vgpr == centroid &&
|
|
pixel.ps_perspective_center_vgpr == center &&
|
|
pixel.ps_system_input_base == centroid + 2,
|
|
"perspective-centroid pair did not follow enabled sample/center inputs");
|
|
const auto key = MakeStageStaticKey(pixel);
|
|
pixel.ps_perspective_centroid_vgpr = UINT32_MAX;
|
|
Check(key != MakeStageStaticKey(pixel), "centroid input is missing from shader key");
|
|
pixel.ps_perspective_centroid_vgpr = centroid;
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &pixel;
|
|
const auto result = RecompileForTest(shader, options);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(SpirvContainsCapability(result.spirv, 52u) &&
|
|
SpirvHasDecorationValue(result.spirv, 11u, 5286u) &&
|
|
(source.find("InterpolateAtCentroid %gl_BaryCoordKHR") != std::string::npos) &&
|
|
SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 1") &&
|
|
SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 2"),
|
|
"centroid I/J did not evaluate BaryCoordKHR Y/Z at the centroid");
|
|
Check(!SpirvHasDecorationValueWithDecoration(result.spirv, 11u, 5286u, 16u),
|
|
"centroid input changed the shared center builtin's interpolation location");
|
|
if (center != UINT32_MAX) {
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpLoad", "%float "),
|
|
"center pair lost its ordinary barycentric loads when centroid was enabled");
|
|
}
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestPsInputCountRegisterDecode() {
|
|
HW::Context context;
|
|
// NUM_INTERP is 3 while bit 14 is an independent control flag that must be
|
|
// preserved.
|
|
context.SetPsInControl(0x00004003u);
|
|
const auto ps_in_control = context.GetShaderRegisters().ps_in_control;
|
|
Check(ps_in_control == 0x00004003u,
|
|
"SPI_PS_IN_CONTROL flags were not preserved");
|
|
Check((ps_in_control & 0x3fu) == 3,
|
|
"SPI_PS_IN_CONTROL NUM_INTERP decoding failed");
|
|
}
|
|
|
|
void TestPixelAncillaryLayerInput() {
|
|
using namespace ShaderRecompiler::IR;
|
|
std::vector<uint32_t> shader = {
|
|
EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11),
|
|
EncodeVop3Word0(0x149, 7), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 3),
|
|
EncodeExp0(0x00, 0x3), EncodeExp1(6, 7, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
HW::PixelShaderInfo regs{};
|
|
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader.data());
|
|
ShaderMappedData mapped{};
|
|
mapped.code_size_bytes = shader.size() * sizeof(uint32_t);
|
|
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
|
HW::ShaderRegisters sh{};
|
|
sh.ps_input_ena = sh.ps_input_addr = 0x3320; // Linear I/J, X/Y, front-face, ancillary.
|
|
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
|
|
ShaderPixelInputInfo pixel{};
|
|
(void)PrepareProgram(regs, sh, mappings, pixel);
|
|
Check(pixel.ps_system_input_base == 2 && pixel.ps_front_face && pixel.ps_ancillary,
|
|
"pixel ancillary register flags were not retained");
|
|
const auto ancillary_key = MakeStageStaticKey(pixel);
|
|
pixel.ps_ancillary = false;
|
|
Check(ancillary_key != MakeStageStaticKey(pixel),
|
|
"pixel ancillary input is missing from the shader cache key");
|
|
pixel.ps_ancillary = true;
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &pixel;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(ProgramHasInput(result.program, StageInputKind::Layer),
|
|
"ancillary after X/Y and front-face did not provide the layer input");
|
|
Check(!ProgramHasInput(result.program, StageInputKind::PackedAncillary),
|
|
"symbolic ancillary input survived its layer extraction");
|
|
Check(SpirvHasDecorationValueWithDecoration(result.spirv, 11u, 9u, 14u) &&
|
|
result.spirv[1] == 0x00010500u &&
|
|
SpirvContainsCapability(result.spirv, 69u) &&
|
|
!SpirvContainsCapability(result.spirv, 5254u),
|
|
"fragment layer input lacks its builtin, flat decoration or capability");
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpLoad", "%int %gl_Layer") &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 202u) == 1u &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 203u) == 1u,
|
|
"layer extraction lost its scalar integer load or signedness");
|
|
Check(!SpirvHasDecorationValue(result.spirv, 11u, 18u),
|
|
"layer extraction unexpectedly enabled SampleId");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
for (const uint32_t destination : {126u, 106u, 0u}) {
|
|
std::vector<uint32_t> wqm = {
|
|
EncodeSop1(0x04, 8, 126), // Save the incoming EXEC predicate.
|
|
EncodeSop1(0x0a, destination, 126),
|
|
EncodeSop1(0x04, 126, destination),
|
|
EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11),
|
|
EncodeVop1(0x01, 5, 6 + 256), // Overwrite the packed ancillary register.
|
|
EncodeSop1(0x04, 126, 8),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(5, 0, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
auto wqm_result = RecompileForTest(wqm, options);
|
|
Check(ProgramHasInput(wqm_result.program, StageInputKind::Layer) &&
|
|
!ProgramHasInput(wqm_result.program, StageInputKind::PackedAncillary),
|
|
"WQM retained an unreachable old ancillary register value");
|
|
CheckSpirvBinaryValidates(wqm_result.spirv);
|
|
}
|
|
|
|
const uint32_t loop[] = {
|
|
EncodeSop1(0x04, 8, 126), // Save the original EXEC mask.
|
|
EncodeSop1(0x0a, 126, 8), // Widen for helper lanes.
|
|
EncodeSop1(0x24, 12, 0), // Narrow EXEC while saving the widened mask.
|
|
EncodeSop1(0x04, 10, 126), // Save the final export mask.
|
|
EncodeSopp(0x08, 6), // Skip the body when EXEC is zero.
|
|
EncodeVop3Word0(0x148, 5), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11),
|
|
EncodeSMovB32(2, 128),
|
|
EncodeSop2(0x00, 2, 2, 129),
|
|
EncodeSopc(0x0a, 2, 131),
|
|
EncodeSopp(0x05, 0xfffdu), // Repeat the scalar loop three times.
|
|
EncodeSop1(0x04, 126, 12), // Pack under the widened mask.
|
|
EncodeVop2(0x2f, 6, 5 + 256, 5),
|
|
EncodeSop1(0x04, 126, 10), // Export only lanes that entered the body.
|
|
EncodeExp0(0x00, 0x1, true, false, true), EncodeExp1(6, 0, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
auto loop_result = RecompileForTest(loop, options);
|
|
Check(ProgramHasInput(loop_result.program, StageInputKind::Layer) &&
|
|
!ProgramHasInput(loop_result.program, StageInputKind::PackedAncillary),
|
|
"loop and skipped body retained the symbolic ancillary input after field lowering");
|
|
Check(SpirvContainsOpcode(loop_result.spirv, 246u),
|
|
"ancillary regression did not retain its scalar loop");
|
|
CheckSpirvBinaryValidates(loop_result.spirv);
|
|
|
|
const uint32_t unused[] = {EncodeSopp(0x01)};
|
|
auto unused_result = RecompileForTest(unused, options);
|
|
Check(!ProgramHasInput(unused_result.program, StageInputKind::Layer) &&
|
|
unused_result.spirv[1] == 0x00010300u &&
|
|
!SpirvContainsCapability(unused_result.spirv, 69u),
|
|
"unused ancillary input changed the module requirements");
|
|
|
|
shader[1] = EncodeVop3Word1(5 + 256, 128 + 8, 128 + 4);
|
|
auto sample_result = RecompileForTest(shader, options);
|
|
Check(ProgramHasInput(sample_result.program, StageInputKind::SampleId) &&
|
|
ProgramHasInput(sample_result.program, StageInputKind::Layer) &&
|
|
!ProgramHasInput(sample_result.program, StageInputKind::PackedAncillary),
|
|
"sample index extraction discarded another live ancillary field");
|
|
Check(SpirvHasDecorationValueWithDecoration(sample_result.spirv, 11u, 18u, 14u) &&
|
|
SpirvContainsCapability(sample_result.spirv, 35u),
|
|
"sample index lacks its flat builtin or sample-rate capability");
|
|
const auto sample_source = DisassembleSpirvBinary(sample_result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(sample_source, "OpLoad", "%int %gl_SampleID"),
|
|
"sample index was not loaded from its scalar integer input");
|
|
CheckSpirvBinaryValidates(sample_result.spirv);
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
shader[1] = EncodeVop3Word1(5 + 256, 128 + 12, 128 + 4);
|
|
ExpectFatal([&] { (void)RecompileForTest(shader, options); },
|
|
"unsupported live ancillary field was silently replaced");
|
|
shader[1] = EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11);
|
|
shader[4] = EncodeExp0(0x00, 0x7);
|
|
shader[5] = EncodeExp1(6, 7, 5, 0);
|
|
ExpectFatal([&] { (void)RecompileForTest(shader, options); },
|
|
"direct raw ancillary export was silently replaced after field lowering");
|
|
#endif
|
|
}
|
|
|
|
void TestGraphicsCreateInterpolantMapping() {
|
|
ShaderRegister regs[32] = {};
|
|
|
|
Check(Gen5::AgcCreateInterpolantMapping(regs, nullptr, nullptr) == 0,
|
|
"null pixel shader interpolant mapping failed");
|
|
for (uint32_t i = 0; i < 32u; i++) {
|
|
Check(regs[i].offset == Pm4::SPI_PS_INPUT_CNTL_0 + i,
|
|
"identity interpolant register offset was unexpected");
|
|
Check(regs[i].value == i,
|
|
"identity interpolant register value was unexpected");
|
|
}
|
|
|
|
ShaderRegister native_regs[32] = {};
|
|
Check(Gen5::AgcCreateInterpolantMapping2(native_regs, nullptr, nullptr) == 0,
|
|
"native interpolant mapping identity path failed");
|
|
for (uint32_t i = 0; i < 32u; i++) {
|
|
Check(native_regs[i].offset == Pm4::SPI_PS_INPUT_CNTL_0 + i,
|
|
"native interpolant register offset was unexpected");
|
|
Check(native_regs[i].value == i,
|
|
"native interpolant register value was unexpected");
|
|
}
|
|
|
|
ShaderSemantic gs_semantics[3] = {};
|
|
gs_semantics[0].semantic = 7;
|
|
gs_semantics[0].hardware_mapping = 5;
|
|
gs_semantics[1].semantic = 9;
|
|
gs_semantics[1].hardware_mapping = 12;
|
|
gs_semantics[2].semantic = 10;
|
|
gs_semantics[2].hardware_mapping = 4;
|
|
gs_semantics[2].is_f16 = 1;
|
|
|
|
Shader gs{};
|
|
gs.output_semantics = gs_semantics;
|
|
gs.num_output_semantics = static_cast<uint16_t>(std::size(gs_semantics));
|
|
|
|
ShaderSemantic ps_semantics[6] = {};
|
|
ps_semantics[0].semantic = 7;
|
|
ps_semantics[1].semantic = 8;
|
|
ps_semantics[1].default_value = 2;
|
|
ps_semantics[2].semantic = 9;
|
|
ps_semantics[2].is_flat_shaded = 1;
|
|
ps_semantics[2].is_custom = 1;
|
|
ps_semantics[2].default_value = 1;
|
|
ps_semantics[3].semantic = 10;
|
|
ps_semantics[3].is_f16 = 1;
|
|
ps_semantics[3].default_value = 3;
|
|
ps_semantics[3].default_value_hi = 2;
|
|
ps_semantics[4].semantic = 10;
|
|
ps_semantics[4].is_f16 = 2;
|
|
ps_semantics[4].default_value_hi = 3;
|
|
ps_semantics[5].semantic = 11;
|
|
ps_semantics[5].is_f16 = 1;
|
|
ps_semantics[5].default_value = 2;
|
|
ps_semantics[5].default_value_hi = 1;
|
|
|
|
Shader ps{};
|
|
ps.input_semantics = ps_semantics;
|
|
ps.num_input_semantics = static_cast<uint32_t>(std::size(ps_semantics));
|
|
|
|
Check(Gen5::AgcCreateInterpolantMapping(regs, &gs, &ps) == 0,
|
|
"shader interpolant mapping failed");
|
|
Check(regs[0].value == 0x00000005u,
|
|
"matched interpolant mapping did not use GS hardware mapping");
|
|
Check(regs[1].value == 0x00000220u,
|
|
"missing interpolant mapping did not use PS default value");
|
|
Check(regs[2].value == 0x0000052cu,
|
|
"flat/custom interpolant mapping bits were unexpected");
|
|
Check(regs[3].value == 0x01580304u,
|
|
"f16 interpolant mapping bits were unexpected");
|
|
Check(regs[6].offset == Pm4::SPI_PS_INPUT_CNTL_0 + 6u && regs[6].value == 6u,
|
|
"interpolant identity tail was not filled");
|
|
|
|
Check(Gen5::AgcCreateInterpolantMapping2(native_regs, &gs, &ps) == 0,
|
|
"native shader interpolant mapping failed");
|
|
Check(native_regs[0].value == 0x00000005u &&
|
|
native_regs[1].value == 0x00000220u &&
|
|
native_regs[2].value == 0x0000052cu &&
|
|
native_regs[3].value == 0x01580304u,
|
|
"native shader interpolant values were unexpected");
|
|
Check(native_regs[4].offset == Pm4::SPI_PS_INPUT_CNTL_0 + 4u &&
|
|
native_regs[4].value == 0x02680324u &&
|
|
native_regs[5].offset == Pm4::SPI_PS_INPUT_CNTL_0 + 5u &&
|
|
native_regs[5].value == 0x01380220u,
|
|
"native mode-specific interpolant values were unexpected");
|
|
Check(native_regs[6].offset == Pm4::SPI_PS_INPUT_CNTL_0 + 6u &&
|
|
native_regs[6].value == 6u,
|
|
"native interpolant identity tail was not filled");
|
|
}
|
|
|
|
void TestNewShaderRecompilerNativeWideScalarMemoryIr() {
|
|
const uint32_t shader[] = {
|
|
EncodeSmem0(0x00, 0, 4), 125u << 25u,
|
|
EncodeSmem0(0x01, 2, 4), 125u << 25u,
|
|
EncodeSmem0(0x02, 4, 4), 125u << 25u,
|
|
EncodeSmem0(0x03, 8, 4), 125u << 25u,
|
|
EncodeSmem0(0x04, 92, 4), 125u << 25u,
|
|
EncodeSmem0(0x08, 32, 4), 125u << 25u,
|
|
EncodeSmem0(0x09, 106, 4), 125u << 25u,
|
|
EncodeSmem0(0x0a, 36, 4), 125u << 25u,
|
|
EncodeSmem0(0x0b, 40, 4), 125u << 25u,
|
|
EncodeSmem0(0x0c, 48, 4), 125u << 25u,
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
ShaderRecompiler::IR::Program ir;
|
|
ShaderComputeInputInfo compute{};
|
|
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
|
|
translate_options.stage = ShaderType::Compute;
|
|
translate_options.wave_size = 64u;
|
|
translate_options.input_info.compute = &compute;
|
|
ShaderRecompiler::Decoder::DecodeProgram(shader, decoded);
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
|
|
translate_options);
|
|
std::vector<uint32_t> address_widths;
|
|
std::vector<uint32_t> buffer_widths;
|
|
for (const auto &memory : ir.memory_info) {
|
|
if (memory.component_index != 0u) {
|
|
continue;
|
|
}
|
|
if (memory.kind == ShaderRecompiler::IR::ResourceKind::ScalarAddress) {
|
|
address_widths.push_back(memory.component_count);
|
|
} else if (memory.kind == ShaderRecompiler::IR::ResourceKind::ScalarBuffer) {
|
|
buffer_widths.push_back(memory.component_count);
|
|
}
|
|
}
|
|
const std::vector<uint32_t> expected{1u, 2u, 4u, 8u, 16u};
|
|
Check(address_widths == expected && buffer_widths == expected,
|
|
"SMEM x1/x2/x4/x8/x16 lost their typed component spans");
|
|
|
|
std::array<bool, 2> buffer_x2_targets_vcc{};
|
|
for (const auto *block : ir.blocks) {
|
|
for (const auto &inst : *block) {
|
|
uint32_t component = UINT32_MAX;
|
|
if (inst.GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::SetVccLo) {
|
|
component = 0u;
|
|
} else if (inst.GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::SetVccHi) {
|
|
component = 1u;
|
|
}
|
|
const auto *load = component < buffer_x2_targets_vcc.size()
|
|
? inst.Arg(0).Resolve().TryInstruction()
|
|
: nullptr;
|
|
if (load == nullptr ||
|
|
load->GetOpcode() !=
|
|
ShaderRecompiler::IR::ValueOpcode::ReadConstBuffer) {
|
|
continue;
|
|
}
|
|
const auto flags =
|
|
load->Flags<ShaderRecompiler::IR::MemoryFlags>();
|
|
if (flags.index >= ir.memory_info.size()) {
|
|
continue;
|
|
}
|
|
const auto &memory = ir.memory_info[flags.index];
|
|
buffer_x2_targets_vcc[component] =
|
|
memory.kind ==
|
|
ShaderRecompiler::IR::ResourceKind::ScalarBuffer &&
|
|
memory.component_count == 2u &&
|
|
memory.component_index == component;
|
|
}
|
|
}
|
|
Check(buffer_x2_targets_vcc[0] && buffer_x2_targets_vcc[1],
|
|
"s_buffer_load_dwordx2 did not preserve its VCC destination span");
|
|
|
|
}
|
|
|
|
void TestNewShaderRecompilerNativeWideBufferIr() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(83, 255), 3u << 28u, // Raw bounds for the s[80:83] fixture.
|
|
EncodeMubuf0(0x0d, 0),
|
|
EncodeMubuf1(0, 20, 1), // buffer_load_dwordx2 v[0:1]
|
|
EncodeMubuf0(0x1d, 16),
|
|
EncodeMubuf1(0, 20, 1), // buffer_store_dwordx2 v[0:1]
|
|
EncodeMubuf0(0x0f, 32),
|
|
EncodeMubuf1(4, 20, 1), // buffer_load_dwordx3 v[4:6]
|
|
EncodeMubuf0(0x1f, 48),
|
|
EncodeMubuf1(4, 20, 1), // buffer_store_dwordx3 v[4:6]
|
|
EncodeMubuf0(0x0e, 64),
|
|
EncodeMubuf1(8, 20, 1), // buffer_load_dwordx4 v[8:11]
|
|
EncodeMubuf0(0x1e, 80),
|
|
EncodeMubuf1(8, 20, 1), // buffer_store_dwordx4 v[8:11]
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
std::array<uint32_t, 7> counts{};
|
|
for (const auto *block : result.program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
switch (inst.GetOpcode()) {
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32:
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32:
|
|
counts[0]++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32x2:
|
|
counts[1]++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32x2:
|
|
counts[2]++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32x3:
|
|
counts[3]++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32x3:
|
|
counts[4]++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32x4:
|
|
counts[5]++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32x4:
|
|
counts[6]++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Check(counts == std::array<uint32_t, 7>{0u, 1u, 1u, 1u, 1u, 1u, 1u},
|
|
"native-wide buffer translation retained scalar siblings or lost a width");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerBufferSignedLoadTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMubuf0(0x09, 3), EncodeMubuf1(46, 0, 1), // buffer_load_sbyte
|
|
EncodeMubuf0(0x0b, 6), EncodeMubuf1(47, 0, 1), // buffer_load_sshort
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("buffer_load_sbyte") != std::string::npos),
|
|
"new decoder did not decode buffer signed byte load");
|
|
Check((result.decoded_dump.find("buffer_load_sshort") != std::string::npos),
|
|
"new decoder did not decode buffer signed short load");
|
|
Check((result.decoded_dump.find("bits=8") != std::string::npos),
|
|
"signed byte buffer load did not preserve bit width metadata");
|
|
Check((result.decoded_dump.find("bits=16") != std::string::npos),
|
|
"signed short buffer load did not preserve bit width metadata");
|
|
Check((result.decoded_dump.find("signed=1") != std::string::npos),
|
|
"signed buffer loads did not preserve signed metadata");
|
|
Check((result.ir_dump.find("BufferLoadSbyte v46") != std::string::npos),
|
|
"buffer_load_sbyte did not lower to signed byte IR load");
|
|
Check((result.ir_dump.find("BufferLoadSshort v47") != std::string::npos),
|
|
"buffer_load_sshort did not lower to signed short IR load");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad");
|
|
Check(SpirvContainsOpcode(result.spirv, 195),
|
|
"SPIR-V binary does not contain OpShiftRightArithmetic for sign "
|
|
"extension");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical for sign extension");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerBufferSubDwordStoreTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMubuf0(0x18, 2), EncodeMubuf1(48, 0, 1), // buffer_store_byte
|
|
EncodeMubuf0(0x1a, 4), EncodeMubuf1(49, 0, 1), // buffer_store_short
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("buffer_store_byte") != std::string::npos),
|
|
"new decoder did not decode buffer byte store");
|
|
Check((result.decoded_dump.find("buffer_store_short") != std::string::npos),
|
|
"new decoder did not decode buffer short store");
|
|
Check((result.decoded_dump.find("bits=8") != std::string::npos),
|
|
"buffer byte store did not preserve bit width metadata");
|
|
Check((result.decoded_dump.find("bits=16") != std::string::npos),
|
|
"buffer short store did not preserve bit width metadata");
|
|
Check((result.ir_dump.find("BufferStoreByte null, v48") != std::string::npos),
|
|
"buffer_store_byte did not lower to byte store IR");
|
|
Check((result.ir_dump.find("BufferStoreShort null, v49") != std::string::npos),
|
|
"buffer_store_short did not lower to short store IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad for sub-dword store RMW");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore for sub-dword store RMW");
|
|
Check(SpirvContainsOpcode(result.spirv, 197),
|
|
"SPIR-V binary does not contain OpBitwiseOr");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
Check(SpirvContainsOpcode(result.spirv, 200),
|
|
"SPIR-V binary does not contain OpNot");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerMubufFormatTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMubuf0(0x00, 4),
|
|
EncodeMubuf1(84, 0, 1), // buffer_load_format_x
|
|
EncodeMubuf0(0x01, 8),
|
|
EncodeMubuf1(88, 0, 1), // buffer_load_format_xy
|
|
EncodeMubuf0(0x02, 12),
|
|
EncodeMubuf1(92, 0, 1), // buffer_load_format_xyz
|
|
EncodeMubuf0(0x03, 16),
|
|
EncodeMubuf1(96, 0, 1), // buffer_load_format_xyzw
|
|
EncodeMubuf0(0x04, 20),
|
|
EncodeMubuf1(100, 0, 1), // buffer_store_format_x
|
|
EncodeMubuf0(0x05, 24),
|
|
EncodeMubuf1(104, 0, 1), // buffer_store_format_xy
|
|
EncodeMubuf0(0x06, 28),
|
|
EncodeMubuf1(108, 0, 1), // buffer_store_format_xyz
|
|
EncodeMubuf0(0x07, 32),
|
|
EncodeMubuf1(112, 0, 1), // buffer_store_format_xyzw
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("BUFFER_LOAD_FORMAT_X") != std::string::npos),
|
|
"new decoder did not decode MUBUF format-x load");
|
|
Check((result.decoded_dump.find("BUFFER_LOAD_FORMAT_XYZW") != std::string::npos),
|
|
"new decoder did not decode MUBUF format-xyzw load");
|
|
Check((result.decoded_dump.find("BUFFER_STORE_FORMAT_X") != std::string::npos),
|
|
"new decoder did not decode MUBUF format-x store");
|
|
Check((result.decoded_dump.find("BUFFER_STORE_FORMAT_XYZW") != std::string::npos),
|
|
"new decoder did not decode MUBUF format-xyzw store");
|
|
Check((result.decoded_dump.find("typed=0 formatted=1") != std::string::npos),
|
|
"MUBUF format decode did not preserve formatted non-typed metadata");
|
|
Check((result.ir_dump.find("BufferLoadDword v99") == std::string::npos),
|
|
"MUBUF formatted load retained a scalar tail sibling");
|
|
Check(CountSourceOccurrences(result.ir_dump, "StoreBufferU32 ") == 1u &&
|
|
CountSourceOccurrences(result.ir_dump, "StoreBufferU32x2 ") == 1u &&
|
|
CountSourceOccurrences(result.ir_dump, "StoreBufferU32x3 ") == 1u &&
|
|
CountSourceOccurrences(result.ir_dump, "StoreBufferU32x4 ") == 1u,
|
|
"MUBUF formatted stores were not preserved as native-width operations");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
|
|
const uint32_t shader[] = {
|
|
EncodeMubuf0(0x04),
|
|
EncodeMubuf1(0, 0, 1), // buffer_store_format_x
|
|
0xbf810000u,
|
|
};
|
|
|
|
std::array<uint32_t, 64> user_data{};
|
|
user_data[1] = 4u << 16u;
|
|
user_data[2] = 5u;
|
|
user_data[3] = static_cast<uint32_t>(Prospero::BufferFormat::k32UInt) << 12u;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(result.resources.buffers.size() == 1 &&
|
|
result.resources.buffers[0].dwords[2] == 5u,
|
|
"formatted store test did not preserve descriptor NumRecords");
|
|
Check((result.decoded_dump.find("BUFFER_STORE_FORMAT_X") != std::string::npos),
|
|
"formatted store regression did not decode buffer_store_format_x");
|
|
Check((result.decoded_dump.find("typed=0 formatted=1") != std::string::npos),
|
|
"formatted store regression did not preserve formatted metadata");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check((source.find("OpArrayLength") != std::string::npos),
|
|
"formatted store SPIR-V lacks runtime storage-buffer bounds check");
|
|
Check(
|
|
!SpirvSourceHasInstructionUsing(source, "OpULessThan", "%uint_5"),
|
|
"formatted store SPIR-V baked descriptor NumRecords into a store guard");
|
|
|
|
user_data[1] = 8u << 16u;
|
|
user_data[3] = static_cast<uint32_t>(Prospero::BufferFormat::k16_16_16_16Float) << 12u;
|
|
options.user_data = user_data;
|
|
result = RecompileForTest(shader, options);
|
|
Check((DisassembleSpirvBinary(result.spirv).find("PackHalf2x16") !=
|
|
std::string::npos),
|
|
"formatted half-float store did not convert F32 to F16");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerTypedBufferTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMtbuf0(0x00, 14, 7, 4),
|
|
EncodeMtbuf1(0x00, 60, 0, 1), // tbuffer_load_format_x
|
|
EncodeMtbuf0(0x01, 13, 7, 8),
|
|
EncodeMtbuf1(0x01, 61, 0, 1), // tbuffer_load_format_xy
|
|
EncodeMtbuf0(0x02, 13, 4, 12),
|
|
EncodeMtbuf1(0x02, 64, 0, 1), // tbuffer_load_format_xyz
|
|
EncodeMtbuf0(0x03, 14, 7, 16, true, true),
|
|
EncodeMtbuf1(0x03, 68, 0, 1), // tbuffer_load_format_xyzw
|
|
EncodeMtbuf0(0x04, 14, 7, 20),
|
|
EncodeMtbuf1(0x04, 72, 0, 1), // tbuffer_store_format_x
|
|
EncodeMtbuf0(0x05, 13, 7, 24),
|
|
EncodeMtbuf1(0x05, 76, 0, 1), // tbuffer_store_format_xy
|
|
EncodeMtbuf0(0x07, 14, 7, 28),
|
|
EncodeMtbuf1(0x07, 80, 0, 1), // tbuffer_store_format_xyzw
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("TBUFFER_LOAD_FORMAT_X") != std::string::npos),
|
|
"new decoder did not decode typed buffer format-x load");
|
|
Check((result.decoded_dump.find("TBUFFER_LOAD_FORMAT_XY") != std::string::npos),
|
|
"new decoder did not decode typed buffer format-xy load");
|
|
Check((result.decoded_dump.find("TBUFFER_LOAD_FORMAT_XYZ") != std::string::npos),
|
|
"new decoder did not decode typed buffer format-xyz load");
|
|
Check((result.decoded_dump.find("TBUFFER_LOAD_FORMAT_XYZW") != std::string::npos),
|
|
"new decoder did not decode typed buffer format-xyzw load");
|
|
Check((result.decoded_dump.find("TBUFFER_STORE_FORMAT_X") != std::string::npos),
|
|
"new decoder did not decode typed buffer format-x store");
|
|
Check((result.decoded_dump.find("TBUFFER_STORE_FORMAT_XY") != std::string::npos),
|
|
"new decoder did not decode typed buffer format-xy store");
|
|
Check((result.decoded_dump.find("TBUFFER_STORE_FORMAT_XYZW") != std::string::npos),
|
|
"new decoder did not decode typed buffer format-xyzw store");
|
|
Check((result.decoded_dump.find("dfmt=14 nfmt=7") != std::string::npos),
|
|
"MTBUF decode did not expose dfmt/nfmt metadata");
|
|
Check((result.decoded_dump.find("typed=1") != std::string::npos),
|
|
"MTBUF decode did not preserve typed metadata");
|
|
Check((result.decoded_dump.find("offen=1") != std::string::npos),
|
|
"MTBUF decode did not preserve offen metadata");
|
|
Check((result.ir_dump.find("BufferLoadDword v71") == std::string::npos),
|
|
"typed buffer load retained a scalar tail sibling");
|
|
Check(CountSourceOccurrences(result.ir_dump, "StoreBufferU32 ") == 1u &&
|
|
CountSourceOccurrences(result.ir_dump, "StoreBufferU32x2 ") == 1u &&
|
|
CountSourceOccurrences(result.ir_dump, "StoreBufferU32x4 ") == 1u,
|
|
"typed buffer stores were not preserved as native-width operations");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerFlatOldBackedTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeFlat0(0x08, 0, 4),
|
|
EncodeFlat1(9, 0x7d, 0, 1), // flat_load_ubyte
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("flat_load_ubyte") != std::string::npos),
|
|
"new decoder did not decode old-backed FLAT ubyte load");
|
|
Check((result.ir_dump.find("FlatLoadUbyte v9") != std::string::npos),
|
|
"old-backed FLAT ubyte load did not lower to IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad");
|
|
Check(SpirvContainsOpcode(result.spirv, 194),
|
|
"SPIR-V binary does not contain OpShiftRightLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerUnbasedFlatUsesBda() {
|
|
const uint32_t shader[] = {
|
|
EncodeFlat0(0x0c, 0, 0),
|
|
EncodeFlat1(0, 0x7d, 0, 1),
|
|
EncodeExp0(0x00, 0x1),
|
|
EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(result.program.info.uses_dma &&
|
|
ShaderRecompiler::IR::FindBinding(
|
|
result.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) !=
|
|
nullptr &&
|
|
ShaderRecompiler::IR::FindBinding(
|
|
result.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer) !=
|
|
nullptr,
|
|
"unbased FLAT did not compile through BDA");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerFlatSignedLoadTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeFlat0(0x09, 0, 4),
|
|
EncodeFlat1(10, 0x7d, 0, 1), // flat_load_sbyte
|
|
EncodeFlat0(0x0b, 1, 8),
|
|
EncodeFlat1(11, 0x7d, 0, 1), // scratch_load_sshort
|
|
EncodeFlat0(0x09, 2, 12),
|
|
EncodeFlat1(12, 0x7d, 0, 1), // global_load_sbyte
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("flat_load_sbyte") != std::string::npos),
|
|
"new decoder did not decode flat signed byte load");
|
|
Check((result.decoded_dump.find("flat_load_sshort") != std::string::npos),
|
|
"new decoder did not decode flat signed short load");
|
|
Check((result.decoded_dump.find("bits=8") != std::string::npos),
|
|
"signed byte flat load did not preserve bit width metadata");
|
|
Check((result.decoded_dump.find("bits=16") != std::string::npos),
|
|
"signed short flat load did not preserve bit width metadata");
|
|
Check((result.decoded_dump.find("signed=1") != std::string::npos),
|
|
"signed flat loads did not preserve signed metadata");
|
|
Check((result.ir_dump.find("FlatLoadSbyte v10") != std::string::npos),
|
|
"flat_load_sbyte did not lower to signed byte IR load");
|
|
Check((result.ir_dump.find("FlatLoadSshort v11") != std::string::npos),
|
|
"flat_load_sshort did not lower to signed short IR load");
|
|
Check((result.ir_dump.find("scratch") != std::string::npos),
|
|
"signed scratch load did not preserve scratch metadata");
|
|
Check((result.ir_dump.find("global") != std::string::npos),
|
|
"signed global load did not preserve global metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad");
|
|
Check(SpirvContainsOpcode(result.spirv, 195),
|
|
"SPIR-V binary does not contain OpShiftRightArithmetic for sign "
|
|
"extension");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical for sign extension");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerFlatStoreTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeFlat0(0x18, 0, 2),
|
|
EncodeFlat1(0, 0x7d, 72, 1), // flat_store_byte
|
|
EncodeFlat0(0x1a, 1, 6),
|
|
EncodeFlat1(0, 0x7d, 73, 1), // scratch_store_short
|
|
EncodeFlat0(0x1c, 0, 4),
|
|
EncodeFlat1(0, 0x7d, 60, 1), // flat_store_dword
|
|
EncodeFlat0(0x1d, 1, 8),
|
|
EncodeFlat1(0, 0x7d, 61, 1), // scratch_store_dwordx2
|
|
EncodeFlat0(0x1f, 2, 12),
|
|
EncodeFlat1(0, 0x7d, 64, 1), // global_store_dwordx3
|
|
EncodeFlat0(0x1e, 2, 16),
|
|
EncodeFlat1(0, 0x7d, 68, 1), // global_store_dwordx4
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("flat_store_byte") != std::string::npos),
|
|
"new decoder did not decode flat byte store");
|
|
Check((result.decoded_dump.find("flat_store_short") != std::string::npos),
|
|
"new decoder did not decode flat short store");
|
|
Check((result.decoded_dump.find("flat_store_dword") != std::string::npos),
|
|
"new decoder did not decode flat dword store");
|
|
Check((result.decoded_dump.find("flat_store_dwordx2") != std::string::npos),
|
|
"new decoder did not decode flat dwordx2 store");
|
|
Check((result.decoded_dump.find("flat_store_dwordx3") != std::string::npos),
|
|
"new decoder did not decode flat dwordx3 store");
|
|
Check((result.decoded_dump.find("flat_store_dwordx4") != std::string::npos),
|
|
"new decoder did not decode flat dwordx4 store");
|
|
Check((result.decoded_dump.find("segment=1") != std::string::npos),
|
|
"flat store decode did not preserve scratch segment metadata");
|
|
Check((result.decoded_dump.find("segment=2") != std::string::npos),
|
|
"flat store decode did not preserve global segment metadata");
|
|
Check((result.decoded_dump.find("dwords=1 bits=8") != std::string::npos),
|
|
"flat byte store decode did not expose byte-width metadata");
|
|
Check((result.decoded_dump.find("dwords=1 bits=16") != std::string::npos),
|
|
"flat short store decode did not expose short-width metadata");
|
|
Check((result.decoded_dump.find("dwords=4 bits=32") != std::string::npos),
|
|
"flat store decode did not expose wide width metadata");
|
|
Check((result.ir_dump.find("FlatStoreByte null, v72") != std::string::npos),
|
|
"flat_store_byte did not lower through shared flat store IR");
|
|
Check((result.ir_dump.find("FlatStoreShort null, v73") != std::string::npos),
|
|
"flat_store_short did not lower through shared flat store IR");
|
|
Check((result.ir_dump.find("FlatStoreDword null, v60") != std::string::npos),
|
|
"flat_store_dword did not lower through shared flat store IR");
|
|
Check((result.ir_dump.find("FlatStoreDword null, v62") != std::string::npos),
|
|
"scratch_store_dwordx2 did not expand to the last dword store");
|
|
Check((result.ir_dump.find("FlatStoreDword null, v66") != std::string::npos),
|
|
"global_store_dwordx3 did not expand to the last dword store");
|
|
Check((result.ir_dump.find("FlatStoreDword null, v71") != std::string::npos),
|
|
"global_store_dwordx4 did not expand to the last dword store");
|
|
Check((result.ir_dump.find("flat") != std::string::npos),
|
|
"flat store IR did not preserve flat resource metadata");
|
|
Check((result.ir_dump.find("scratch") != std::string::npos),
|
|
"flat store IR did not preserve scratch resource metadata");
|
|
Check((result.ir_dump.find("global") != std::string::npos),
|
|
"flat store IR did not preserve global resource metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad for sub-dword flat store merge");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 197),
|
|
"SPIR-V binary does not contain OpBitwiseOr for sub-dword flat store "
|
|
"merge");
|
|
Check(SpirvContainsOpcode(result.spirv, 200),
|
|
"SPIR-V binary does not contain OpNot for sub-dword flat store mask");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerAtomicTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeMubuf0(0x30, 4, true, true),
|
|
EncodeMubuf1(0, 0, 1), // buffer_atomic_swap
|
|
EncodeMubuf0(0x32, 8, true, true),
|
|
EncodeMubuf1(1, 0, 1), // buffer_atomic_add
|
|
EncodeMubuf0(0x33, 12, true, true),
|
|
EncodeMubuf1(7, 0, 1), // buffer_atomic_sub
|
|
EncodeMubuf0(0x35, 16, true, true),
|
|
EncodeMubuf1(8, 0, 1), // buffer_atomic_smin
|
|
EncodeMubuf0(0x36, 20, true, true),
|
|
EncodeMubuf1(2, 0, 1), // buffer_atomic_umin
|
|
EncodeMubuf0(0x37, 24, true, true),
|
|
EncodeMubuf1(9, 0, 1), // buffer_atomic_smax
|
|
EncodeMubuf0(0x38, 28, true, true),
|
|
EncodeMubuf1(3, 0, 1), // buffer_atomic_umax
|
|
EncodeMubuf0(0x39, 32, true, true),
|
|
EncodeMubuf1(4, 0, 1), // buffer_atomic_and
|
|
EncodeMubuf0(0x3a, 36, true, true),
|
|
EncodeMubuf1(5, 0, 1), // buffer_atomic_or
|
|
EncodeMubuf0(0x3b, 40, true, true),
|
|
EncodeMubuf1(6, 0, 1), // buffer_atomic_xor
|
|
0xe0fc0000u,
|
|
0x80010000u, // exact buffer_atomic_fmin v0, s[4:7], 0 (GLC=0)
|
|
0xe100000cu,
|
|
0x80010300u, // exact failing buffer_atomic_fmax v3, s[4:7], 12 (GLC=0)
|
|
EncodeDs0(0x00),
|
|
EncodeDs1(0, 2, 1), // ds_add_u32
|
|
EncodeDs0(0x01),
|
|
EncodeDs1(0, 10, 1), // ds_sub_u32
|
|
EncodeDs0(0x05),
|
|
EncodeDs1(0, 11, 1), // ds_min_i32
|
|
EncodeDs0(0x06),
|
|
EncodeDs1(0, 12, 1), // ds_max_i32
|
|
EncodeDs0(0x09),
|
|
EncodeDs1(0, 13, 1), // ds_and_b32
|
|
EncodeDs0(0x0b),
|
|
EncodeDs1(0, 14, 1), // ds_xor_b32
|
|
EncodeDs0(0x20),
|
|
EncodeDs1(15, 2, 1), // ds_add_rtn_u32
|
|
EncodeDs0(0x21),
|
|
EncodeDs1(16, 10, 1), // ds_sub_rtn_u32
|
|
EncodeDs0(0x25),
|
|
EncodeDs1(17, 11, 1), // ds_min_rtn_i32
|
|
EncodeDs0(0x26),
|
|
EncodeDs1(18, 12, 1), // ds_max_rtn_i32
|
|
EncodeDs0(0x27),
|
|
EncodeDs1(19, 2, 1), // ds_min_rtn_u32
|
|
EncodeDs0(0x28),
|
|
EncodeDs1(20, 2, 1), // ds_max_rtn_u32
|
|
EncodeDs0(0x29),
|
|
EncodeDs1(21, 13, 1), // ds_and_rtn_b32
|
|
EncodeDs0(0x2a),
|
|
EncodeDs1(22, 2, 1), // ds_or_rtn_b32
|
|
EncodeDs0(0x2b),
|
|
EncodeDs1(23, 14, 1), // ds_xor_rtn_b32
|
|
EncodeDs0(0x2d),
|
|
EncodeDs1(24, 15, 1), // ds_wrxchg_rtn_b32
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("buffer_atomic_swap") != std::string::npos),
|
|
"new decoder did not decode buffer atomic swap");
|
|
Check((result.decoded_dump.find("buffer_atomic_add") != std::string::npos),
|
|
"new decoder did not decode buffer atomic add");
|
|
Check((result.decoded_dump.find("buffer_atomic_sub") != std::string::npos),
|
|
"new decoder did not decode buffer atomic sub");
|
|
Check((result.decoded_dump.find("buffer_atomic_smin") != std::string::npos),
|
|
"new decoder did not decode buffer atomic signed min");
|
|
Check((result.decoded_dump.find("buffer_atomic_smax") != std::string::npos),
|
|
"new decoder did not decode buffer atomic signed max");
|
|
Check((result.decoded_dump.find("buffer_atomic_xor") != std::string::npos),
|
|
"new decoder did not decode buffer atomic xor");
|
|
Check((result.decoded_dump.find("buffer_atomic_fmin") != std::string::npos),
|
|
"new decoder did not decode buffer atomic float min");
|
|
Check((result.decoded_dump.find("buffer_atomic_fmax") != std::string::npos),
|
|
"new decoder did not decode buffer atomic float max");
|
|
Check((result.decoded_dump.find("ds_add_u32") != std::string::npos),
|
|
"new decoder did not decode DS atomic add");
|
|
Check((result.decoded_dump.find("ds_sub_u32") != std::string::npos),
|
|
"new decoder did not decode DS atomic sub");
|
|
Check((result.decoded_dump.find("ds_min_i32") != std::string::npos),
|
|
"new decoder did not decode DS signed min");
|
|
Check((result.decoded_dump.find("ds_xor_rtn_b32") != std::string::npos),
|
|
"new decoder did not decode DS xor-return");
|
|
Check((result.decoded_dump.find("ds_wrxchg_rtn_b32") != std::string::npos),
|
|
"new decoder did not decode DS write-exchange-return");
|
|
Check((result.decoded_dump.find("ds_add_rtn_u32") != std::string::npos),
|
|
"new decoder did not decode DS atomic add-return");
|
|
Check((result.ir_dump.find("AtomicSwapU32 v0") != std::string::npos),
|
|
"buffer atomic swap did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicAddU32 v1") != std::string::npos),
|
|
"buffer atomic add did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSubU32 v7") != std::string::npos),
|
|
"buffer atomic sub did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSMinI32 v8") != std::string::npos),
|
|
"buffer atomic signed min did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicUMinU32 v2") != std::string::npos),
|
|
"buffer atomic umin did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSMaxI32 v9") != std::string::npos),
|
|
"buffer atomic signed max did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicUMaxU32 v3") != std::string::npos),
|
|
"buffer atomic umax did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicAndU32 v4") != std::string::npos),
|
|
"buffer atomic and did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicOrU32 v5") != std::string::npos),
|
|
"buffer atomic or did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicXorU32 v6") != std::string::npos),
|
|
"buffer atomic xor did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicFMinF32 null, v0") != std::string::npos),
|
|
"buffer atomic float min did not lower to IR without a GLC return");
|
|
Check((result.ir_dump.find("AtomicFMaxF32 null, v3") != std::string::npos),
|
|
"buffer atomic float max did not lower to IR without a GLC return");
|
|
Check((result.ir_dump.find("AtomicAddU32 null, v2") != std::string::npos),
|
|
"DS no-return atomic add did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSubU32 null, v10") != std::string::npos),
|
|
"DS no-return atomic sub did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSMinI32 null, v11") != std::string::npos),
|
|
"DS no-return signed min did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSMaxI32 null, v12") != std::string::npos),
|
|
"DS no-return signed max did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicAndU32 null, v13") != std::string::npos),
|
|
"DS no-return and did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicXorU32 null, v14") != std::string::npos),
|
|
"DS no-return xor did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicAddU32 v15") != std::string::npos),
|
|
"DS add-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSubU32 v16") != std::string::npos),
|
|
"DS sub-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSMinI32 v17") != std::string::npos),
|
|
"DS signed min-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSMaxI32 v18") != std::string::npos),
|
|
"DS signed max-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicUMinU32 v19") != std::string::npos),
|
|
"DS unsigned min-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicUMaxU32 v20") != std::string::npos),
|
|
"DS unsigned max-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicAndU32 v21") != std::string::npos),
|
|
"DS and-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicOrU32 v22") != std::string::npos),
|
|
"DS or-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicXorU32 v23") != std::string::npos),
|
|
"DS xor-return did not lower to IR");
|
|
Check((result.ir_dump.find("AtomicSwapU32 v24") != std::string::npos),
|
|
"DS write-exchange-return did not lower to shared atomic swap IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 229),
|
|
"SPIR-V binary does not contain OpAtomicExchange");
|
|
Check(SpirvContainsOpcode(result.spirv, 234),
|
|
"SPIR-V binary does not contain OpAtomicIAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 235),
|
|
"SPIR-V binary does not contain OpAtomicISub");
|
|
Check(SpirvContainsOpcode(result.spirv, 236),
|
|
"SPIR-V binary does not contain OpAtomicSMin");
|
|
Check(SpirvContainsOpcode(result.spirv, 237),
|
|
"SPIR-V binary does not contain OpAtomicUMin");
|
|
Check(SpirvContainsOpcode(result.spirv, 238),
|
|
"SPIR-V binary does not contain OpAtomicSMax");
|
|
Check(SpirvContainsOpcode(result.spirv, 239),
|
|
"SPIR-V binary does not contain OpAtomicUMax");
|
|
Check(SpirvContainsOpcode(result.spirv, 240),
|
|
"SPIR-V binary does not contain OpAtomicAnd");
|
|
Check(SpirvContainsOpcode(result.spirv, 241),
|
|
"SPIR-V binary does not contain OpAtomicOr");
|
|
Check(SpirvContainsOpcode(result.spirv, 242),
|
|
"SPIR-V binary does not contain OpAtomicXor");
|
|
Check(SpirvContainsOpcode(result.spirv, 230),
|
|
"SPIR-V binary does not contain OpAtomicCompareExchange for float min");
|
|
Check(SpirvContainsOpcode(result.spirv, 225),
|
|
"buffer atomic SPIR-V binary does not contain OpMemoryBarrier");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerDsReadWrite2Translation() {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(0x0e, (3u << 8u) | 1u),
|
|
EncodeDs1Ex(0, 61, 60, 1),
|
|
EncodeDs0(0x37, (4u << 8u) | 2u),
|
|
EncodeDs1Ex(70, 0, 0, 1),
|
|
EncodeDs0(0x38, (5u << 8u) | 1u),
|
|
EncodeDs1Ex(72, 0, 0, 1),
|
|
EncodeDs0(0x77, (6u << 8u) | 2u),
|
|
EncodeDs1Ex(80, 0, 0, 1),
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
ShaderRecompiler::IR::Program typed;
|
|
ShaderComputeInputInfo compute{};
|
|
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
|
|
translate_options.stage = ShaderType::Compute;
|
|
translate_options.wave_size = 64u;
|
|
translate_options.input_info.compute = &compute;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
typed = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
|
|
translate_options);
|
|
uint32_t scalar_loads = 0;
|
|
uint32_t vector_loads = 0;
|
|
uint32_t scalar_stores = 0;
|
|
for (const auto *block : typed.blocks) {
|
|
for (const auto &inst : *block) {
|
|
scalar_loads +=
|
|
inst.GetOpcode() == ShaderRecompiler::IR::ValueOpcode::LoadSharedU32;
|
|
vector_loads += inst.GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::LoadSharedU32x2;
|
|
scalar_stores +=
|
|
inst.GetOpcode() == ShaderRecompiler::IR::ValueOpcode::WriteSharedU32;
|
|
}
|
|
}
|
|
Check(scalar_loads == 4u && vector_loads == 2u && scalar_stores == 2u,
|
|
"DS read/write2 did not use the native scalar/x2 transaction shape");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("DS_WRITE2_B32") != std::string::npos),
|
|
"new decoder did not decode old-backed DS write2");
|
|
Check((result.decoded_dump.find("DS_READ2_B32") != std::string::npos),
|
|
"new decoder did not decode old-backed DS read2");
|
|
Check((result.decoded_dump.find("DS_READ2ST64_B32") != std::string::npos),
|
|
"new decoder did not preserve the DS read2st64 opcode identity");
|
|
Check((result.decoded_dump.find("DS_READ2_B64") != std::string::npos),
|
|
"new decoder did not decode old-backed DS read2 b64");
|
|
Check((result.decoded_dump.find("offset=4") != std::string::npos),
|
|
"DS write2 decode did not scale offset0 to bytes");
|
|
Check((result.decoded_dump.find("offset2=12") != std::string::npos),
|
|
"DS write2 decode did not scale offset1 to bytes");
|
|
Check((result.decoded_dump.find("offset=8") != std::string::npos),
|
|
"DS read2 decode did not scale offset0 to bytes");
|
|
Check((result.decoded_dump.find("offset2=16") != std::string::npos),
|
|
"DS read2 decode did not scale offset1 to bytes");
|
|
Check((result.decoded_dump.find("offset=256") != std::string::npos),
|
|
"DS read2 st64 decode did not scale offset0 to bytes");
|
|
Check((result.decoded_dump.find("offset2=1280") != std::string::npos),
|
|
"DS read2 st64 decode did not scale offset1 to bytes");
|
|
Check((result.decoded_dump.find("offset=16") != std::string::npos),
|
|
"DS read2 b64 decode did not scale offset0 to bytes");
|
|
Check((result.decoded_dump.find("offset2=48") != std::string::npos),
|
|
"DS read2 b64 decode did not scale offset1 to bytes");
|
|
Check((result.decoded_dump.find("dwords=2 bits=32") != std::string::npos),
|
|
"DS read/write2 decode did not preserve two-dword metadata");
|
|
Check((result.decoded_dump.find("dwords=4 bits=32") != std::string::npos),
|
|
"DS read2 b64 decode did not preserve four-dword metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerDsSubDwordTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(0x1e, 1), EncodeDs1(0, 40, 1), // ds_write_b8 v40, v1
|
|
EncodeDs0(0x1f, 2), EncodeDs1(0, 41, 1), // ds_write_b16 v41, v1
|
|
EncodeDs0(0x39, 3), EncodeDs1(42, 0, 1), // ds_read_i8 v42, v1
|
|
EncodeDs0(0x3a, 4), EncodeDs1(43, 0, 1), // ds_read_u8 v43, v1
|
|
EncodeDs0(0x3b, 6), EncodeDs1(44, 0, 1), // ds_read_i16 v44, v1
|
|
EncodeDs0(0x3c, 8), EncodeDs1(45, 0, 1), // ds_read_u16 v45, v1
|
|
0xda980000u, 0x07000007u, // ds_read_u16_d16 v7, v7
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("ds_write_b8") != std::string::npos),
|
|
"new decoder did not decode DS byte write");
|
|
Check((result.decoded_dump.find("ds_write_b16") != std::string::npos),
|
|
"new decoder did not decode DS short write");
|
|
Check((result.decoded_dump.find("ds_read_i8") != std::string::npos),
|
|
"new decoder did not decode DS signed byte read");
|
|
Check((result.decoded_dump.find("ds_read_u8") != std::string::npos),
|
|
"new decoder did not decode DS unsigned byte read");
|
|
Check((result.decoded_dump.find("ds_read_i16") != std::string::npos),
|
|
"new decoder did not decode DS signed short read");
|
|
Check((result.decoded_dump.find("ds_read_u16") != std::string::npos),
|
|
"new decoder did not decode DS unsigned short read");
|
|
Check((result.decoded_dump.find("DS_READ_U16_D16 v7.sdwa(sel=4") != std::string::npos),
|
|
"new decoder did not decode captured DS masked short read");
|
|
Check((result.decoded_dump.find("dwords=1 bits=8") != std::string::npos),
|
|
"DS byte decode did not preserve byte-width metadata");
|
|
Check((result.decoded_dump.find("dwords=1 bits=16") != std::string::npos),
|
|
"DS short decode did not preserve short-width metadata");
|
|
Check((result.ir_dump.find("DsWriteByte null, v40") != std::string::npos),
|
|
"DS byte write did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsWriteShort null, v41") != std::string::npos),
|
|
"DS short write did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsReadSbyte v42") != std::string::npos),
|
|
"DS signed byte read did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsReadUbyte v43") != std::string::npos),
|
|
"DS unsigned byte read did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsReadSshort v44") != std::string::npos),
|
|
"DS signed short read did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsReadUshort v45") != std::string::npos),
|
|
"DS unsigned short read did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsReadUshort v7.sdwa(sel=4") != std::string::npos),
|
|
"DS masked short read did not preserve its partial destination");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
Check(SpirvContainsOpcode(result.spirv, 194),
|
|
"SPIR-V binary does not contain OpShiftRightLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 195),
|
|
"SPIR-V binary does not contain OpShiftRightArithmetic");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
Check(SpirvContainsOpcode(result.spirv, 200),
|
|
"SPIR-V binary does not contain OpNot");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerDsWideAndAtomicTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(0x4d, 4), EncodeDs1(0, 10, 1), // ds_write_b64 v[10:11], v1
|
|
EncodeDs0(0xde, 8), EncodeDs1(0, 12, 1), // ds_write_b96 v[12:14], v1
|
|
EncodeDs0(0xdf, 12), EncodeDs1(0, 16, 1), // ds_write_b128 v[16:19], v1
|
|
EncodeDs0(0x76, 16), EncodeDs1(20, 0, 1), // ds_read_b64 v[20:21], v1
|
|
EncodeDs0(0xfe, 20), EncodeDs1(24, 0, 1), // ds_read_b96 v[24:26], v1
|
|
EncodeDs0(0xff, 24), EncodeDs1(28, 0, 1), // ds_read_b128 v[28:31], v1
|
|
EncodeDs0(0x07, 28), EncodeDs1(0, 32, 1), // ds_min_u32 v32, v1
|
|
EncodeDs0(0x08, 32), EncodeDs1(0, 33, 1), // ds_max_u32 v33, v1
|
|
EncodeDs0(0x0a, 36), EncodeDs1(0, 34, 1), // ds_or_b32 v34, v1
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
ShaderRecompiler::IR::Program typed;
|
|
ShaderComputeInputInfo compute{};
|
|
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
|
|
translate_options.stage = ShaderType::Compute;
|
|
translate_options.wave_size = 64u;
|
|
translate_options.input_info.compute = &compute;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
typed = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
|
|
translate_options);
|
|
std::array<uint32_t, 3> load_widths{};
|
|
std::array<uint32_t, 3> store_widths{};
|
|
for (const auto *block : typed.blocks) {
|
|
for (const auto &inst : *block) {
|
|
using ShaderRecompiler::IR::ValueOpcode;
|
|
if (inst.GetOpcode() == ValueOpcode::LoadSharedU32x2)
|
|
load_widths[0]++;
|
|
if (inst.GetOpcode() == ValueOpcode::LoadSharedU32x3)
|
|
load_widths[1]++;
|
|
if (inst.GetOpcode() == ValueOpcode::LoadSharedU32x4)
|
|
load_widths[2]++;
|
|
if (inst.GetOpcode() == ValueOpcode::WriteSharedU32x2)
|
|
store_widths[0]++;
|
|
if (inst.GetOpcode() == ValueOpcode::WriteSharedU32x3)
|
|
store_widths[1]++;
|
|
if (inst.GetOpcode() == ValueOpcode::WriteSharedU32x4)
|
|
store_widths[2]++;
|
|
}
|
|
}
|
|
Check(load_widths == std::array<uint32_t, 3>{1u, 1u, 1u} &&
|
|
store_widths == std::array<uint32_t, 3>{1u, 1u, 1u},
|
|
"wide DS transfers retained scalar sibling operations");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("DS_WRITE_B64") != std::string::npos),
|
|
"new decoder did not decode DS b64 write");
|
|
Check((result.decoded_dump.find("DS_WRITE_B96") != std::string::npos),
|
|
"new decoder did not decode DS b96 write");
|
|
Check((result.decoded_dump.find("DS_WRITE_B128") != std::string::npos),
|
|
"new decoder did not decode DS b128 write");
|
|
Check((result.decoded_dump.find("DS_READ_B64") != std::string::npos),
|
|
"new decoder did not decode DS b64 read");
|
|
Check((result.decoded_dump.find("DS_READ_B96") != std::string::npos),
|
|
"new decoder did not decode DS b96 read");
|
|
Check((result.decoded_dump.find("DS_READ_B128") != std::string::npos),
|
|
"new decoder did not decode DS b128 read");
|
|
Check((result.decoded_dump.find("DS_MIN_U32") != std::string::npos),
|
|
"new decoder did not decode DS min atomic");
|
|
Check((result.decoded_dump.find("DS_MAX_U32") != std::string::npos),
|
|
"new decoder did not decode DS max atomic");
|
|
Check((result.decoded_dump.find("DS_OR_B32") != std::string::npos),
|
|
"new decoder did not decode DS or atomic");
|
|
Check((result.decoded_dump.find("dwords=4 bits=32") != std::string::npos),
|
|
"new decoder did not expose DS wide width metadata");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 237),
|
|
"SPIR-V binary does not contain OpAtomicUMin");
|
|
Check(SpirvContainsOpcode(result.spirv, 239),
|
|
"SPIR-V binary does not contain OpAtomicUMax");
|
|
Check(SpirvContainsOpcode(result.spirv, 241),
|
|
"SPIR-V binary does not contain OpAtomicOr");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerDsSwizzleTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(0x35, 0x001f),
|
|
EncodeDs1(8, 0, 5), // ds_swizzle_b32 v8, v5
|
|
EncodeDs0(0x35, 0x801b),
|
|
EncodeDs1(9, 0, 6), // ds_swizzle_b32 v9, v6
|
|
0xd8d4c480u,
|
|
0x45000045u, // ds_swizzle_b32 rotate mode from boot shader
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("ds_swizzle_b32") != std::string::npos),
|
|
"new decoder did not decode DS swizzle");
|
|
Check((result.decoded_dump.find("offset=31") != std::string::npos),
|
|
"new decoder did not expose DS swizzle control");
|
|
Check((result.decoded_dump.find("offset=32795") != std::string::npos),
|
|
"new decoder did not expose DS swizzle quad control");
|
|
Check((result.decoded_dump.find("offset=50304") != std::string::npos),
|
|
"new decoder did not expose DS swizzle rotate control");
|
|
Check((result.ir_dump.find("DsSwizzleB32 v8, v5, 0x0000001f") != std::string::npos),
|
|
"DS swizzle did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsSwizzleB32 v9, v6, 0x0000801b") != std::string::npos),
|
|
"DS quad swizzle did not lower to explicit IR");
|
|
Check(
|
|
(result.ir_dump.find("DsSwizzleB32 v69, v69, 0x0000c480") != std::string::npos),
|
|
"DS rotate swizzle did not lower to explicit IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 345),
|
|
"SPIR-V binary does not contain OpGroupNonUniformShuffle");
|
|
Check(SpirvContainsOpcode(result.spirv, 128),
|
|
"SPIR-V binary does not contain OpIAdd for DS rotate swizzle");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 198),
|
|
"SPIR-V binary does not contain OpBitwiseOr");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerDsAddtidTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(124, 132), // m0 = 4
|
|
EncodeDs0(0xb0, 8),
|
|
EncodeDs1(0, 7, 0), // ds_write_addtid_b32 v7
|
|
EncodeDs0(0xb1, 12),
|
|
EncodeDs1(8, 0, 0), // ds_read_addtid_b32 v8
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderComputeInputInfo input_info = RegressionComputeInputInfo();
|
|
input_info.thread_ids_num = 1;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.input_info.compute = &input_info;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("ds_write_addtid_b32") != std::string::npos),
|
|
"new decoder did not decode DS write addtid");
|
|
Check((result.decoded_dump.find("ds_read_addtid_b32") != std::string::npos),
|
|
"new decoder did not decode DS read addtid");
|
|
Check((result.ir_dump.find("DsWriteAddtidB32 null, v7, m0") != std::string::npos),
|
|
"DS write addtid did not lower to explicit IR");
|
|
Check((result.ir_dump.find("DsReadAddtidB32 v8, m0") != std::string::npos),
|
|
"DS read addtid did not lower to explicit IR");
|
|
Check(ProgramHasInput(
|
|
result.program,
|
|
ShaderRecompiler::IR::StageInputKind::LocalInvocationIndex),
|
|
"DS addtid did not request LocalInvocationIndex input");
|
|
Check(SpirvContainsOpcode(result.spirv, 61),
|
|
"SPIR-V binary does not contain OpLoad");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"SPIR-V binary does not contain OpStore");
|
|
Check(SpirvContainsOpcode(result.spirv, 65),
|
|
"SPIR-V binary does not contain OpAccessChain");
|
|
Check(SpirvContainsOpcode(result.spirv, 128),
|
|
"SPIR-V binary does not contain OpIAdd");
|
|
Check(SpirvContainsOpcode(result.spirv, 196),
|
|
"SPIR-V binary does not contain OpShiftLeftLogical");
|
|
Check(SpirvContainsOpcode(result.spirv, 199),
|
|
"SPIR-V binary does not contain OpBitwiseAnd");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestDsFloatMinMaxDecoder() {
|
|
using namespace ShaderRecompiler::Decoder;
|
|
for (bool max_value : {false, true}) {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(max_value ? 0x13 : 0x12, 4), EncodeDs1Ex(0, 250, 7, 1)};
|
|
Instruction decoded;
|
|
DecodeInstruction(shader, 0u, decoded);
|
|
Check(decoded.opcode == (max_value ? Opcode::DS_MAX_F32 : Opcode::DS_MIN_F32) &&
|
|
decoded.src_count == 2u && decoded.src0.reg == 1u &&
|
|
decoded.src1.reg == 7u && decoded.offset == 4u,
|
|
"DS float min/max must read only address and DATA0");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgStraightLine() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 129),
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("CFG:") != std::string::npos),
|
|
"CFG dump was not emitted");
|
|
Check((result.ir_dump.find("block_0") != std::string::npos),
|
|
"straight-line CFG block missing");
|
|
Check((result.ir_dump.find("successors=[") != std::string::npos),
|
|
"CFG successors were not dumped");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgIfElse() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
|
EncodeSopp(0x05, 2), // s_cbranch_scc1 else
|
|
EncodeSMovB32(1, 129), // then
|
|
EncodeSopp(0x02, 1), // s_branch merge
|
|
EncodeSMovB32(1, 130), // else
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("condition=scc1") != std::string::npos),
|
|
"if/else branch condition missing");
|
|
Check(SpirvContainsOpcode(result.spirv, 247),
|
|
"if/else SPIR-V lacks OpSelectionMerge");
|
|
Check(SpirvContainsOpcode(result.spirv, 250),
|
|
"if/else SPIR-V lacks OpBranchConditional");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgConsecutiveNativePhis() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 1), // s_cmp_eq_u32 s0, s1
|
|
EncodeSopp(0x05, 3), // s_cbranch_scc1 else
|
|
EncodeSMovB32(2, 129), // then: s2 = 1
|
|
EncodeSMovB32(3, 130), // s3 = 2
|
|
EncodeSopp(0x02, 2), // merge
|
|
EncodeSMovB32(2, 131), // else: s2 = 3
|
|
EncodeSMovB32(3, 132), // s3 = 4
|
|
EncodeVop1(0x01, 0, 2), // v_mov_b32 v0, s2
|
|
EncodeVop1(0x01, 1, 3), // v_mov_b32 v1, s3
|
|
EncodeExp0(0x0c, 0x3), EncodeExp1(0, 0, 1, 0), // POS0.xy
|
|
EncodeSopp(0x01),
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(CountSourceOccurrences(result.ir_dump, " = Phi") == 2u &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 245u) == 2u,
|
|
"consecutive typed Phis were not emitted as two native OpPhi "
|
|
"instructions");
|
|
CheckSpirvPhiParents(result.spirv);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerStructuredU64Phi() {
|
|
using namespace ShaderRecompiler;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 1), EncodeSopp(0x05, 2), EncodeSMovB32(2, 129),
|
|
EncodeSopp(0x02, 1), EncodeSMovB32(2, 130), EncodeVop1(0x01, 0, 2),
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback,
|
|
"U64 Phi fixture did not select structured mode");
|
|
|
|
auto program = std::move(result.program);
|
|
IR::Block *join = nullptr;
|
|
for (auto *block : program.blocks) {
|
|
if (block->ImmPredecessors().size() == 2u) {
|
|
join = block;
|
|
break;
|
|
}
|
|
}
|
|
Check(join != nullptr, "U64 Phi fixture has no two-parent join block");
|
|
auto &phi = *join->PrependNewInst(join->begin(), IR::ValueOpcode::Phi);
|
|
phi.SetFlags(IR::Type::U64);
|
|
phi.AddPhiOperand(join->ImmPredecessors()[0],
|
|
IR::Value(uint64_t{0x1111111122222222ull}));
|
|
phi.AddPhiOperand(join->ImmPredecessors()[1],
|
|
IR::Value(uint64_t{0x3333333344444444ull}));
|
|
IR::IREmitter use(join);
|
|
use.Emit(IR::ValueOpcode::CompositeExtractU64,
|
|
{IR::Value(&phi), IR::Value(1u)});
|
|
|
|
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
|
|
CheckSpirvBinaryValidates(spirv);
|
|
const auto before = MeasureSpirv(result.spirv);
|
|
const auto after = MeasureSpirv(spirv);
|
|
Check(after.phis == before.phis + 1u &&
|
|
after.function_variables == before.function_variables,
|
|
"structured U64 Phi was not emitted as a native vector OpPhi");
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgTerminalExitMergePS() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x04, 2), // s_cbranch_scc0 end
|
|
EncodeSMovB32(0, 129), // fallthrough work
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"terminal PS branch should stay on structured path");
|
|
Check((result.ir_dump.find("conditional block 0 has no structured merge") == std::string::npos),
|
|
"known terminal PS branch shape still reports missing merge");
|
|
Check(SpirvContainsOpcode(result.spirv, 247),
|
|
"terminal PS branch SPIR-V lacks OpSelectionMerge");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgPostEndTargetMergePS() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x04, 2), // s_cbranch_scc0 label_000c
|
|
EncodeSMovB32(0, 129), // fallthrough work
|
|
0xbf810000u,
|
|
EncodeSMovB32(1, 129), // branch target after first s_endpgm
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("0x0000000c: S_MOV_B32") != std::string::npos),
|
|
"post-end branch target was not decoded");
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"post-end terminal branch should stay on structured path");
|
|
Check((result.ir_dump.find("pc=0x0000000c") != std::string::npos),
|
|
"post-end branch target did not reach IR blocks");
|
|
Check((result.ir_dump.find("conditional block 0 has no structured merge") == std::string::npos),
|
|
"known post-end PS branch shape still reports missing merge");
|
|
Check(SpirvContainsOpcode(result.spirv, 247),
|
|
"post-end terminal branch SPIR-V lacks OpSelectionMerge");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopBreakContinue() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 128), // s0 = 0
|
|
EncodeSopc(0x0a, 0, 129), // s_cmp_lt_u32 s0, 1
|
|
EncodeSopp(0x04, 2), // break when scc == 0
|
|
EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
|
|
EncodeSopp(0x02, 0xfffcu), // continue/backedge
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("backedge") != std::string::npos),
|
|
"loop backedge was not detected");
|
|
Check((result.ir_dump.find("loop_header=1") != std::string::npos),
|
|
"loop header was not marked");
|
|
Check((result.ir_dump.find("continue=") != std::string::npos),
|
|
"loop continue block was not identified");
|
|
Check(SpirvContainsOpcode(result.spirv, 246),
|
|
"loop SPIR-V lacks OpLoopMerge");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopHeaderDynamicScalarBufferLoadStructured() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 128), // preheader: s0 = 0
|
|
EncodeSmem0(0x08, 8, 4),
|
|
0u, // loop: s_buffer_load_dword s8, s[8:11], s0
|
|
EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
|
|
EncodeSopc(0x0a, 0, 130), // s_cmp_lt_u32 s0, 2
|
|
EncodeSopp(0x05, 0xfffbu), // s_cbranch_scc1 loop
|
|
EncodeVop1(0x01, 0, 8),
|
|
EncodeMubuf0(0x1c, 0, false),
|
|
EncodeMubuf1(0, 12, 0),
|
|
0xbf810000u,
|
|
};
|
|
|
|
std::array<uint32_t, 64> user_data{};
|
|
for (const uint32_t base : {8u, 48u}) {
|
|
user_data[base] = base == 8u ? 0x1000u : 0x2000u;
|
|
user_data[base + 2] = 64u;
|
|
user_data[base + 3] = 0x30005204u;
|
|
}
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.user_data = user_data;
|
|
const auto result = RecompileForTest(shader, options);
|
|
Check(result.program.info.uses_dma && !result.program.dispatcher_fallback &&
|
|
SpirvContainsOpcode(result.spirv, 246),
|
|
"self-modifying scalar descriptor lost its structured GPU loop");
|
|
Check(result.program.info.buffers.size() == 1u && result.program.info.buffers[0].written &&
|
|
result.resources.flattened_srt.empty(),
|
|
"self-modifying scalar descriptor was flattened or bound on the host");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopHeaderBufferLoadStructured() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 128), // preheader: s0 = 0
|
|
EncodeMubuf0(0x0c),
|
|
EncodeMubuf1(0, 0, 1), // loop: buffer_load_dword v0
|
|
EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
|
|
EncodeSopc(0x0a, 0, 130), // s_cmp_lt_u32 s0, 2
|
|
EncodeSopp(0x05, 0xfffbu), // s_cbranch_scc1 loop
|
|
EncodeMubuf0(0x1c, 0, false),
|
|
EncodeMubuf1(0, 12, 0),
|
|
0xbf810000u,
|
|
};
|
|
|
|
const auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
|
Check(result.program.info.uses_dma && !result.program.dispatcher_fallback &&
|
|
SpirvContainsOpcode(result.spirv, 246),
|
|
"loop-carried vector descriptor lost its structured GPU loop");
|
|
Check(result.program.info.buffers.size() == 1u && result.program.info.buffers[0].written &&
|
|
result.resources.flattened_srt.empty() && result.resources.specialization_reads.empty(),
|
|
"loop-carried vector descriptor was evaluated on the host");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeStructured() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(124, 129), // m0 = one counter
|
|
EncodeDs0(0x3e), // loop: ds_append
|
|
EncodeDs1(0, 0, 0),
|
|
EncodeDs0(0x3d), // ds_consume
|
|
EncodeDs1(1, 0, 0),
|
|
EncodeSop2(0x00, 2, 2, 129), // s_add_u32 s2, s2, 1
|
|
EncodeSopc(0x0a, 2, 130), // s_cmp_lt_u32 s2, 2
|
|
EncodeSopp(0x05, 0xfff9u), // s_cbranch_scc1 loop
|
|
0xbf810000u,
|
|
};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_block_count = graph.blocks.size();
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(graph.natural_loops.size() == 1u, "DS loop was not preserved");
|
|
Check(graph.blocks.size() == original_block_count + 1u,
|
|
"DS loop structurization did not add exactly one empty header");
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"DS loop canonicalization duplicated semantic instructions");
|
|
const auto *loop_header = graph.FindBlock(graph.natural_loops.front().header);
|
|
Check(loop_header != nullptr &&
|
|
loop_header->inst_begin == loop_header->inst_end,
|
|
"DS loop header still contains semantic instructions");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"DS append/consume loop did not stay structured");
|
|
Check(!result.program.dispatcher_fallback,
|
|
"DS append/consume loop unexpectedly selected dispatcher fallback");
|
|
Check(SpirvContainsOpcode(result.spirv, 246),
|
|
"DS structured SPIR-V lacks OpLoopMerge");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopHeaderDsReadStructured() {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(0x36), // loop: ds_read_b32 v0, v1
|
|
EncodeDs1(0, 0, 1),
|
|
EncodeSop2(0x00, 2, 2, 129), // s_add_u32 s2, s2, 1
|
|
EncodeSopc(0x0a, 2, 130), // s_cmp_lt_u32 s2, 2
|
|
EncodeSopp(0x05, 0xfffbu), // s_cbranch_scc1 loop
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 4, 1), // keep the guarded LDS result live
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
!result.program.dispatcher_fallback,
|
|
"DS read loop header did not stay structured");
|
|
Check(!SpirvContainsOpcode(result.spirv, 251),
|
|
"DS read structured SPIR-V unexpectedly contains OpSwitch");
|
|
const auto metrics = MeasureSpirv(result.spirv);
|
|
Check((result.ir_dump.find("Phi") != std::string::npos) && metrics.phis != 0u &&
|
|
metrics.selection_merges != 0u,
|
|
"guarded LDS loop did not exercise deferred Phi exit-label patching");
|
|
CheckSpirvPhiParents(result.spirv);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopHeaderDsRead2B64Structured() {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(0x77, (48u << 8u) | 32u), // loop: ds_read2_b64 v[0:3], v1
|
|
EncodeDs1(0, 0, 1),
|
|
EncodeSop2(0x00, 2, 2, 129), // s_add_u32 s2, s2, 1
|
|
EncodeSopc(0x0a, 2, 130), // s_cmp_lt_u32 s2, 2
|
|
EncodeSopp(0x05, 0xfffbu), // s_cbranch_scc1 loop
|
|
EncodeMubuf0(0x1e),
|
|
EncodeMubuf1(0, 4, 1), // keep all four guarded LDS results live
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
!result.program.dispatcher_fallback,
|
|
"DS read2 b64 loop header did not stay structured");
|
|
Check((result.decoded_dump.find("DS_READ2_B64") != std::string::npos),
|
|
"DS read2 b64 loop regression did not decode the captured opcode");
|
|
Check(!SpirvContainsOpcode(result.spirv, 251),
|
|
"DS read2 b64 structured SPIR-V unexpectedly contains OpSwitch");
|
|
CheckSpirvPhiParents(result.spirv);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgSharedOuterAndLoopMerge() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
|
EncodeSopp(0x05, 5), // outer early exit -> end
|
|
EncodeSMovB32(2, 128), // s2 = 0
|
|
EncodeSopc(0x0a, 2, 129), // loop: s_cmp_lt_u32 s2, 1
|
|
EncodeSopp(0x04, 2), // loop exit -> same real end block
|
|
EncodeSop2(0x00, 2, 2, 129), // s_add_u32 s2, s2, 1
|
|
EncodeSopp(0x02, 0xfffcu), // continue/backedge
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"shared outer/loop merge should stay on structured path");
|
|
Check(
|
|
(result.ir_dump.find("duplicate structured merge block") == std::string::npos),
|
|
"shared outer/loop merge was not split before structurization");
|
|
Check(SpirvContainsOpcode(result.spirv, 246),
|
|
"shared outer/loop merge SPIR-V lacks OpLoopMerge");
|
|
Check(SpirvContainsOpcode(result.spirv, 247),
|
|
"shared outer/loop merge SPIR-V lacks OpSelectionMerge");
|
|
Check(!SpirvContainsOpcode(result.spirv, 251),
|
|
"shared outer/loop merge unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopExitSharedWithSelection() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x0a, 0, 129), // loop condition
|
|
EncodeSopp(0x04, 10), // loop exit -> end
|
|
EncodeSopc(0x06, 1, 1), // selection within the loop
|
|
EncodeSopp(0x05, 3), // choose either arm
|
|
EncodeSopc(0x06, 2, 2), // first arm
|
|
EncodeSopp(0x04, 6), // first arm -> shared end
|
|
EncodeSopp(0x02, 3), // first arm -> repeat
|
|
EncodeSopc(0x06, 3, 3), // second arm
|
|
EncodeSopp(0x04, 3), // second arm -> shared end
|
|
EncodeSopp(0x02, 0), // second arm -> repeat
|
|
EncodeSop2(0x00, 0, 0, 129), // repeat work
|
|
EncodeSopp(0x02, 0xfff4u), // backedge
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto block_count = graph.blocks.size();
|
|
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(block_count == 8u && graph.natural_loops.size() == 1u,
|
|
"shared loop-exit fixture has the wrong native CFG");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
|
"shared loop-exit routing changed semantic instruction coverage");
|
|
|
|
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
|
Check(!result.program.dispatcher_fallback && !SpirvContainsOpcode(result.spirv, 251),
|
|
"shared loop exit did not retain structured control flow");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x0a, 0, 129), // loop: s_cmp_lt_u32 s0, 1
|
|
EncodeSopp(0x04, 4), // loop exit -> end
|
|
EncodeSopc(0x06, 1, 1), // s_cmp_eq_u32 s1, s1
|
|
EncodeSopp(0x04, 2), // early break -> same loop end
|
|
EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
|
|
EncodeSopp(0x02, 0xfffau), // backedge -> loop header
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"loop early-break CFG did not stay on structured path");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
|
"loop early-break SPIR-V lacks OpLoopMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
|
"loop early-break SPIR-V unexpectedly used OpSelectionMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"loop early-break CFG unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgNestedLoopNonlocalExitStructured() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x0a, 0, 129), // outer loop: s_cmp_lt_u32 s0, 1
|
|
EncodeSopp(0x04, 9), // outer exit -> end
|
|
EncodeSopc(0x0a, 1, 129), // inner loop: s_cmp_lt_u32 s1, 1
|
|
EncodeSopp(0x04, 5), // inner exit -> outer continue
|
|
EncodeSopc(0x06, 2, 2), // s_cmp_eq_u32 s2, s2
|
|
EncodeSopp(0x05, 5), // nonlocal exit -> outer end
|
|
EncodeSMovB32(3, 129), // inner work
|
|
EncodeSop2(0x00, 1, 1, 129), // s_add_u32 s1, s1, 1
|
|
EncodeSopp(0x02, 0xfff9u), // inner backedge
|
|
EncodeSop2(0x00, 0, 0, 129), // outer continue: s_add_u32 s0, s0, 1
|
|
EncodeSopp(0x02, 0xfff5u), // outer backedge
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"nested-loop nonlocal exit did not retain structured control flow");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"nested-loop nonlocal exit unexpectedly selected dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x0a, 0, 129), // outer loop: s_cmp_lt_u32 s0, 1
|
|
EncodeSopp(0x04, 6), // outer exit -> end
|
|
EncodeSopc(0x0a, 1, 129), // inner loop: s_cmp_lt_u32 s1, 1
|
|
EncodeSopp(0x04, 2), // inner exit -> outer continue
|
|
EncodeSMovB32(2, 129), // inner work
|
|
EncodeSopp(0x02, 0xfffcu), // inner backedge
|
|
EncodeSop2(0x00, 0, 0, 129), // outer continue: s_add_u32 s0, s0, 1
|
|
EncodeSopp(0x02, 0xfff8u), // outer backedge
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"nested local loop exit did not stay on structured path");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) >= 2,
|
|
"nested local loop exit SPIR-V lacks both OpLoopMerge instructions");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
|
"nested local loop exit SPIR-V unexpectedly used OpSelectionMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"nested local loop exit unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x0a, 0, 129), // outer loop: s_cmp_lt_u32 s0, 1
|
|
EncodeSopp(0x04, 11), // outer exit -> end
|
|
EncodeSopc(0x06, 1, 1), // inner loop first exit condition
|
|
EncodeSopp(0x05, 3), // first inner exit -> tail A
|
|
EncodeSopc(0x06, 2, 2), // inner loop second exit condition
|
|
EncodeSopp(0x05, 3), // second inner exit -> tail B
|
|
EncodeSopp(0x02, 0xfffbu), // inner backedge
|
|
EncodeSMovB32(3, 129), // tail A
|
|
EncodeSopp(0x02, 2), // tail A -> outer continue
|
|
EncodeSMovB32(4, 129), // tail B
|
|
EncodeSopp(0x02, 0), // tail B -> outer continue
|
|
EncodeSop2(0x00, 0, 0, 129), // outer continue: s_add_u32 s0, s0, 1
|
|
EncodeSopp(0x02, 0xfff3u), // outer backedge
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program program;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, program);
|
|
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(program);
|
|
const auto original_block_count = graph.blocks.size();
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(graph.blocks.size() > original_block_count,
|
|
"nested loop exit tails did not create a private inner merge");
|
|
|
|
std::vector<const ShaderRecompiler::CFG::BasicBlock *> headers;
|
|
for (const auto &block : graph.blocks)
|
|
if (block.terminator.loop_header) headers.push_back(&block);
|
|
Check(headers.size() == 2u, "nested loop exit-tail fixture lost a loop header");
|
|
if (!graph.Dominates(headers[0]->id, headers[1]->id)) std::swap(headers[0], headers[1]);
|
|
const auto *outer_header = headers[0];
|
|
const auto *inner_header = headers[1];
|
|
Check(inner_header->terminator.merge_block != outer_header->terminator.continue_block &&
|
|
graph.Dominates(outer_header->id, inner_header->terminator.merge_block),
|
|
"inner loop merge escaped the enclosing loop body");
|
|
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
|
Check(!result.program.dispatcher_fallback,
|
|
"nested loop exit tails selected the dispatcher");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgMixedContinueNonmergeExitStructured() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 7, 7), // entry branch bypasses loop -> exit X
|
|
EncodeSopp(0x05, 5), // entry -> X
|
|
EncodeSopc(0x0a, 0, 129), // loop: s_cmp_lt_u32 s0, 1
|
|
EncodeSopp(0x04, 5), // loop exit -> Y
|
|
EncodeSopc(0x06, 1, 1), // inner condition
|
|
EncodeSopp(0x05, 1), // nonmerge exit -> X, else continue
|
|
EncodeSopp(0x02, 0xfffbu), // loop backedge
|
|
EncodeSMovB32(2, 129), // X
|
|
EncodeSopp(0x02, 2), // X -> end
|
|
EncodeSMovB32(3, 129), // Y
|
|
EncodeSopp(0x02, 0), // Y -> end
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"mixed continue/nonmerge exit did not retain structured control flow");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"mixed continue/nonmerge exit unexpectedly selected dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgConditionalLatchNoSelection() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x02, 0), // loop header -> conditional block
|
|
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
|
EncodeSopp(0x05, 1), // loop exit -> end
|
|
EncodeSopp(0x02, 0xfffcu), // separate latch -> loop header
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"conditional latch did not stay on structured path");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
|
"conditional latch SPIR-V lacks OpLoopMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
|
"conditional latch SPIR-V unexpectedly used OpSelectionMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"conditional latch unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgDirectConditionalLatchNoSelection() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x02, 0), // loop header -> conditional latch
|
|
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
|
EncodeSopp(0x05, 0xfffdu), // direct latch backedge -> loop header
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"direct conditional latch did not stay on structured path");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
|
"direct conditional latch SPIR-V lacks OpLoopMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
|
"direct conditional latch SPIR-V unexpectedly used OpSelectionMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"direct conditional latch unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopEarlyContinuesStructured() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 128), // s0 = 0
|
|
EncodeSopc(0x0a, 0, 130), // loop: s_cmp_lt_u32 s0, 2
|
|
EncodeSopp(0x04, 9), // loop exit -> end
|
|
EncodeSopc(0x06, 1, 1), // s_cmp_eq_u32 s1, s1
|
|
EncodeSopp(0x05, 5), // first early continue -> continue block
|
|
EncodeSopc(0x06, 2, 2), // s_cmp_eq_u32 s2, s2
|
|
EncodeSopp(0x05, 3), // second early continue -> continue block
|
|
EncodeSopc(0x06, 3, 3), // s_cmp_eq_u32 s3, s3
|
|
EncodeSopp(0x05, 1), // third early continue -> continue block
|
|
EncodeSMovB32(4, 129), // fallthrough work
|
|
EncodeSop2(0x00, 0, 0, 129), // continue: s_add_u32 s0, s0, 1
|
|
EncodeSopp(0x02, 0xfff5u), // backedge -> loop header
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"loop early continues should stay on structured path");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
|
"loop early continues SPIR-V lacks OpLoopMerge");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 250) <= 4u,
|
|
"loop early continues introduced duplicate conditional branches");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"loop early continues unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopGatewaySelection() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x0a, 0, 130), // loop: s_cmp_lt_u32 s0, 2
|
|
EncodeSopp(0x04, 8), // loop exit -> end
|
|
EncodeSopc(0x06, 1, 1), // skip-body condition
|
|
EncodeSopp(0x05, 3), // skip body -> loop-control gateway
|
|
EncodeSopc(0x06, 2, 2), // body early-break condition
|
|
EncodeSopp(0x05, 4), // early break -> end
|
|
EncodeSMovB32(3, 129), // body work
|
|
EncodeSopc(0x0a, 0, 130), // loop-control gateway
|
|
EncodeSopp(0x04, 1), // exit -> end, else latch
|
|
EncodeSopp(0x02, 0xfff6u), // latch -> loop header
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"loop gateway structurization duplicated semantic instructions");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
!result.program.dispatcher_fallback,
|
|
"loop-control gateway selection unexpectedly selected dispatcher");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u,
|
|
"loop-control gateway SPIR-V has the wrong loop-merge count");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 1u,
|
|
"loop-control gateway SPIR-V has the wrong selection-merge count");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"loop-control gateway SPIR-V unexpectedly contains OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgConditionalLoopHeaderSelection() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // loop body selection condition
|
|
EncodeSopp(0x05, 2), // select path B
|
|
EncodeSMovB32(1, 129), // path A
|
|
EncodeSopp(0x02, 1), // path A -> join
|
|
EncodeSMovB32(2, 129), // path B
|
|
EncodeSMovB32(3, 129), // join
|
|
EncodeSopc(0x06, 4, 4), // repeat condition
|
|
EncodeSopp(0x05, 0xfff8u), // repeat -> guest header
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_block_count = graph.blocks.size();
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(graph.blocks.size() > original_block_count,
|
|
"conditional guest loop header did not create a synthetic header");
|
|
|
|
uint32_t loop_headers = 0;
|
|
uint32_t selection_headers = 0;
|
|
for (const auto &block : graph.blocks) {
|
|
if (block.terminator.loop_header) {
|
|
loop_headers++;
|
|
Check(block.inst_begin == block.inst_end &&
|
|
block.terminator.kind ==
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch,
|
|
"canonical loop header is not an empty unconditional block");
|
|
} else if (block.terminator.kind ==
|
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch &&
|
|
block.terminator.merge_block != UINT32_MAX) {
|
|
selection_headers++;
|
|
}
|
|
}
|
|
Check(loop_headers == 1u && selection_headers == 1u,
|
|
"guest conditional was not separated from the loop header");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u,
|
|
"conditional loop-header SPIR-V has the wrong loop-merge count");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 1u,
|
|
"conditional loop-header SPIR-V has the wrong selection-merge count");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"conditional loop-header unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgMultipleLoopLatches() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x0a, 0, 129), // loop condition
|
|
EncodeSopp(0x04, 5), // loop exit -> end
|
|
EncodeSopc(0x06, 1, 1), // early repeat condition
|
|
EncodeSopp(0x05, 0xfffcu), // early repeat -> header
|
|
EncodeSMovB32(2, 129), // body
|
|
EncodeSMovB32(3, 129), // body tail
|
|
EncodeSopp(0x02, 0xfff9u), // ordinary latch -> header
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(graph.back_edges.size() == 2u,
|
|
"multiple-latch fixture lacks two native backedges");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
|
"shared backedge construction duplicated guest instructions");
|
|
Check(graph.back_edges.size() == 1u && graph.natural_loops.size() == 1u,
|
|
"multiple native latches were not coalesced to one SPIR-V backedge");
|
|
const auto &loop = graph.natural_loops.front();
|
|
const auto *header = graph.FindBlock(loop.header);
|
|
Check(header != nullptr, "natural loop header is missing");
|
|
const auto *continue_block = graph.FindBlock(header->terminator.continue_block);
|
|
Check(continue_block != nullptr &&
|
|
continue_block->terminator.true_block == loop.header &&
|
|
graph.Dominates(loop.header, continue_block->id),
|
|
"canonical continue does not return to its natural loop header");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u,
|
|
"multiple-latch SPIR-V has the wrong loop-merge count");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 250) <= 2u,
|
|
"multiple-latch routing duplicated a native conditional branch");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"multiple-latch SPIR-V unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
|
EncodeSopp(0x05, 2), // first early exit -> end
|
|
EncodeSopp(0x05, 1), // second early exit -> same end merge
|
|
EncodeSMovB32(0, 129), // fallthrough work
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"duplicate merge CFG did not stay on structured path");
|
|
Check(
|
|
(result.ir_dump.find("duplicate structured merge block") == std::string::npos),
|
|
"duplicate merge CFG was not split before structurization");
|
|
Check(SpirvContainsOpcode(result.spirv, 247),
|
|
"duplicate merge SPIR-V lacks OpSelectionMerge");
|
|
Check(!SpirvContainsOpcode(result.spirv, 251),
|
|
"duplicate merge CFG unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // preheader condition
|
|
EncodeSopp(0x05, 10), // preheader early exit -> end
|
|
EncodeSopc(0x06, 1, 1), // outer condition
|
|
EncodeSopp(0x05, 8), // outer early exit -> end
|
|
EncodeSopc(0x06, 2, 2), // inner condition
|
|
EncodeSopp(0x05, 2), // inner -> loop, else linear arm
|
|
EncodeSMovB32(3, 129), // linear arm work
|
|
EncodeSopp(0x02, 4), // linear arm -> end
|
|
EncodeSopc(0x06, 4, 4), // loop header condition
|
|
EncodeSopp(0x04, 2), // loop exit -> end
|
|
EncodeSop2(0x00, 5, 5, 129), // loop work
|
|
EncodeSopp(0x02, 0xfffcu), // backedge -> loop header
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_block_count = graph.blocks.size();
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(original_block_count == 7u && graph.natural_loops.size() == 1u,
|
|
"nested early-exit fixture has the wrong native CFG");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage,
|
|
"nested early-exit structurization changed semantic coverage");
|
|
std::unordered_set<uint32_t> merges;
|
|
for (const auto &block : graph.blocks) {
|
|
if (block.terminator.merge_block != UINT32_MAX)
|
|
Check(merges.insert(block.terminator.merge_block).second,
|
|
"nested early-exit constructs share a structured merge");
|
|
}
|
|
Check(graph.natural_loops.size() == 1u,
|
|
"nested early-exit routing changed the natural loop count");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"nested early-exit loop unexpectedly selected dispatcher fallback");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 3u &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 246) == 1u &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"nested early-exit SPIR-V has the wrong structured control flow");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgExecSccSharedArm() {
|
|
const uint32_t shader[] = {
|
|
EncodeSop2(0x0f, 126, 4, 126), // s_and_b64 exec, s4, exec (runtime mask)
|
|
EncodeSopp(0x08, 2), // execz -> shared arm
|
|
EncodeSop2(0x15, 30, 30, 126), // s_andn2_b64 s30, s30, exec
|
|
EncodeSopp(0x04, 2), // scc0 -> other arm, else shared arm
|
|
EncodeSMovB32(0, 129), // shared arm
|
|
0xbf810000u,
|
|
EncodeSMovB32(1, 129), // other arm
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(std::ranges::all_of(original_coverage,
|
|
[](uint32_t uses) { return uses == 1u; }),
|
|
"shared-arm fixture already duplicated a semantic instruction");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"EXEC/SCC shared-arm structurization changed semantic coverage");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"EXEC/SCC shared-arm epilogue did not stay structured");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 2u,
|
|
"EXEC/SCC shared-arm SPIR-V has the wrong selection-merge count");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"EXEC/SCC shared-arm SPIR-V unexpectedly used dispatcher OpSwitch");
|
|
Check((DisassembleSpirvBinary(result.spirv).find("OpGroupNonUniformBallot") != std::string::npos),
|
|
"scalar mask SCC did not reduce the complete wave mask");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestSharedReturnPreservesDescriptorDominance() {
|
|
const uint32_t shader[] = {
|
|
EncodeSmem0(0x02, 32, 14), (125u << 25u) | 0x60u,
|
|
EncodeSopc(0x06, 0, 128),
|
|
EncodeSopp(0x04, 10), // first early return
|
|
EncodeSmem0(0x02, 32, 14), (125u << 25u) | 0x50u,
|
|
EncodeSopc(0x06, 1, 128),
|
|
EncodeSopp(0x04, 6), // second early return after descriptor overwrite
|
|
EncodeSmem0(0x08, 56, 16), 125u << 25u,
|
|
EncodeVop1(0x01, 0, 56),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
EncodeSopp(0x01), // shared return epilogue
|
|
};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
const auto *overwrite = graph.FindBlockByPc(0x10u);
|
|
const auto *body = graph.FindBlockByPc(0x20u);
|
|
Check(overwrite != nullptr && body != nullptr &&
|
|
graph.Dominates(overwrite->id, body->id) &&
|
|
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
|
return block.terminator.kind ==
|
|
ShaderRecompiler::CFG::TerminatorKind::Return;
|
|
}) == 2 &&
|
|
CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"shared return routing lost the live descriptor's dominance");
|
|
|
|
std::array<uint32_t, 28> table{};
|
|
table[0x50u / 4u] = 0x2000u;
|
|
table[0x50u / 4u + 2u] = 4u;
|
|
table[0x60u / 4u] = 0x3000u;
|
|
table[0x60u / 4u + 2u] = 4u;
|
|
std::array<uint32_t, 32> user_data{};
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
user_data[28] = static_cast<uint32_t>(address);
|
|
user_data[29] = static_cast<uint32_t>(address >> 32u);
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.user_data = user_data;
|
|
auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
result.resources.buffers.size() == 1u &&
|
|
result.resources.buffers[0].dwords[0] == 0x2000u,
|
|
"return-only descriptor reached the surviving buffer operation");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNestedSelectionPreservesDescriptorSources() {
|
|
constexpr uint32_t nested_arm = 12, direct_arm = 18, join = 21, end = 23;
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x07, 9, 128),
|
|
EncodeSopp(0x04, end - 1 - 1),
|
|
EncodeSopc(0x07, 8, 130),
|
|
EncodeSopp(0x04, direct_arm - 3 - 1),
|
|
EncodeSmem0(0x02, 16, 10), 125u << 25u,
|
|
EncodeSopc(0x07, 8, 129),
|
|
EncodeSopp(0x04, nested_arm - 7 - 1),
|
|
EncodeSmem0(0x08, 24, 8), 125u << 25u,
|
|
EncodeVop1(0x01, 0, 24),
|
|
EncodeSopp(0x02, join - 11 - 1),
|
|
EncodeSmem0(0x02, 16, 10), (125u << 25u) | 16u,
|
|
EncodeSmem0(0x08, 24, 8), 125u << 25u,
|
|
EncodeVop1(0x01, 0, 24),
|
|
EncodeSopp(0x02, join - 17 - 1),
|
|
EncodeSmem0(0x08, 24, 8), 125u << 25u,
|
|
EncodeVop1(0x01, 0, 24),
|
|
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(0, 3, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(graph.blocks.size() == 8u &&
|
|
std::ranges::all_of(coverage, [](uint32_t uses) { return uses == 1u; }),
|
|
"nested descriptor fixture has the wrong native topology");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
|
"nested descriptor selection changed semantic instruction coverage");
|
|
|
|
const std::array<uint32_t, 8> table{
|
|
0x3000u, 0u, 4u, 3u << 28u, 0x5000u, 0u, 4u, 3u << 28u};
|
|
std::array<uint32_t, 14> user_data{};
|
|
user_data[1] = 1u;
|
|
user_data[4] = 0x4000u;
|
|
user_data[6] = 4u;
|
|
user_data[7] = 3u << 28u;
|
|
user_data[10] = 4u;
|
|
user_data[11] = 3u << 28u;
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
user_data[12] = static_cast<uint32_t>(address);
|
|
user_data[13] = static_cast<uint32_t>(address >> 32u);
|
|
for (uint32_t mode = 0; mode != 3; ++mode) {
|
|
user_data[0] = mode;
|
|
user_data[8] = 0x2000u + mode * 0x10000u;
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.user_data_base = 8;
|
|
options.user_data = user_data;
|
|
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
|
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 4u,
|
|
"nested descriptor selection lost its structured resource bindings");
|
|
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
|
|
const auto pc = result.program.info.buffers[i].first_use_pc;
|
|
Check(pc == 8u * 4u || pc == 14u * 4u || pc == direct_arm * 4u || pc == join * 4u,
|
|
"nested descriptor selection lost a native source location");
|
|
const auto selected_pc = (mode == 0u ? 8u : mode == 1u ? 14u : direct_arm) * 4u;
|
|
const auto *expected = pc == 8u * 4u ? table.data()
|
|
: pc == 14u * 4u ? table.data() + 4
|
|
: pc == direct_arm * 4u ? user_data.data() + 8 : user_data.data() + 4;
|
|
const auto &descriptor = result.resources.buffers[i];
|
|
const bool unused = pc != selected_pc && pc != join * 4u;
|
|
Check(descriptor.dword_count == 4 &&
|
|
(std::equal(expected, expected + 4, descriptor.dwords.begin()) ||
|
|
(unused && std::ranges::all_of(descriptor.dwords, [](auto word) { return word == 0; }))),
|
|
"nested descriptor selection chose another arm's descriptor");
|
|
}
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestSharedExitPreservesNativeDescriptorSources() {
|
|
constexpr uint32_t selected = 8, later = 18, normal = 26, killed = 29;
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x07, 0, 128),
|
|
EncodeSopp(0x04, normal - 1 - 1),
|
|
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 32u,
|
|
EncodeSopc(0x07, 1, 128),
|
|
EncodeSopp(0x04, selected - 5 - 1),
|
|
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 16u,
|
|
EncodeSopc(0x07, 2, 128),
|
|
EncodeSopp(0x04, later - 9 - 1),
|
|
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 32u,
|
|
EncodeSopc(0x07, 3, 128),
|
|
EncodeSopp(0x04, killed - 13 - 1),
|
|
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
|
EncodeVop1(0x01, 0, 16),
|
|
EncodeSopp(0x02, normal - 17 - 1),
|
|
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 48u,
|
|
EncodeSopc(0x07, 4, 128),
|
|
EncodeSopp(0x04, killed - 21 - 1),
|
|
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
|
EncodeVop1(0x01, 0, 16),
|
|
EncodeSopp(0x02, normal - 25 - 1),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(graph.blocks.size() == 10u &&
|
|
std::ranges::all_of(coverage, [](uint32_t uses) { return uses == 1u; }),
|
|
"shared-exit descriptor fixture has the wrong native topology");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
|
"shared-exit descriptor selection changed semantic instruction coverage");
|
|
|
|
const std::array<uint32_t, 16> table{
|
|
0x2000u, 0u, 4u, 3u << 28u, 0x3000u, 0u, 4u, 3u << 28u,
|
|
0x4000u, 0u, 4u, 3u << 28u, 0x5000u, 0u, 4u, 3u << 28u};
|
|
std::array<uint32_t, 14> user_data{};
|
|
std::fill_n(user_data.begin(), 5, 1u);
|
|
user_data[8] = 0x1000u;
|
|
user_data[10] = 4u;
|
|
user_data[11] = 3u << 28u;
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
user_data[12] = static_cast<uint32_t>(address);
|
|
user_data[13] = static_cast<uint32_t>(address >> 32u);
|
|
for (uint32_t arm = 0; arm != 2; ++arm) {
|
|
user_data[2] = arm;
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.user_data = user_data;
|
|
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
|
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 2u,
|
|
"shared-exit descriptor selection lost its structured resource bindings");
|
|
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
|
|
const auto pc = result.program.info.buffers[i].first_use_pc;
|
|
const uint32_t expected = pc == 14u * 4u ? table[8]
|
|
: pc == 22u * 4u ? table[12] : 0u;
|
|
Check(expected != 0u && result.resources.buffers[i].dwords[0] == expected,
|
|
"shared-exit descriptor selection retained an overwritten native source");
|
|
}
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestNativeScalarReadDescriptorPlanning() {
|
|
constexpr uint32_t nested = 10, read = 14, join = 18, end = 20;
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x07, 9, 128),
|
|
EncodeSopp(0x04, end - 1 - 1),
|
|
EncodeSopc(0x07, 8, 130),
|
|
EncodeSopp(0x04, read - 3 - 1),
|
|
EncodeSop1(0x24, 0, 126), EncodeSop1(0x04, 2, 126),
|
|
EncodeSopc(0x07, 8, 129),
|
|
EncodeSopp(0x04, nested - 7 - 1),
|
|
EncodeVop1(0x01, 0, 128),
|
|
EncodeSopp(0x02, join - 9 - 1),
|
|
EncodeSop1(0x04, 0, 126), EncodeSop1(0x04, 2, 126),
|
|
EncodeVop1(0x01, 0, 129),
|
|
EncodeSopp(0x02, join - 13 - 1),
|
|
EncodeSmem0(0x0a, 16, 0), 125u << 25u,
|
|
EncodeMubuf0(0x0c, 0, false), EncodeMubuf1(0, 4, 1),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const std::array<uint32_t, 4> table{0x5000u, 0u, 4u, 3u << 28u};
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
std::array<uint32_t, 10> user_data{};
|
|
user_data[0] = static_cast<uint32_t>(address);
|
|
user_data[1] = static_cast<uint32_t>(address >> 32u);
|
|
user_data[2] = sizeof(table);
|
|
user_data[3] = 3u << 28u;
|
|
user_data[9] = 1u;
|
|
for (const uint32_t mode : {2u, 0u, 1u}) {
|
|
user_data[8] = mode;
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.user_data = user_data;
|
|
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
|
const auto expected = mode == 2u ? table : std::array<uint32_t, 4>{};
|
|
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 1u &&
|
|
std::ranges::equal(result.resources.flattened_srt, expected),
|
|
"scalar descriptor planning retained an unrelated native execution mask");
|
|
Check(std::equal(expected.begin(), expected.end(), result.resources.buffers[0].dwords.begin()),
|
|
"scalar descriptor planning selected the wrong native resource");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestScalarSelectedVccBufferAddress() {
|
|
using namespace ShaderRecompiler::IR;
|
|
// 68ca7b6fd9c44cfa selects an ordinary 64-bit address through VCC before
|
|
// copying its words into a writable buffer descriptor.
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x07, 4, 128), EncodeSop2(0x0b, 106, 0, 2),
|
|
EncodeVop1(0x01, 1, 106), EncodeVop1(0x02, 8, 257),
|
|
EncodeSMovB32(9, 107), EncodeSMovB32(10, 132),
|
|
EncodeSMovB32(11, 255), 3u << 28u,
|
|
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(0, 2, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
std::array<uint32_t, 5> userdata{0x12345670u, 0x12u, 0xb27c0000u, 0x30u, 0u};
|
|
for (const uint32_t wave_size : {32u, 64u}) {
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto input = *options.input_info.compute;
|
|
input.threads_num[0] = input.wave_size = options.wave_size = wave_size;
|
|
options.input_info.compute = &input;
|
|
options.user_data = userdata;
|
|
auto translated = ShaderRecompiler::TranslateProgram(shader, options);
|
|
auto plan = ExtractResourcePlan(translated.program);
|
|
ResourceSnapshot snapshot;
|
|
ResourceSpecialization specialization;
|
|
for (const uint32_t condition : {0u, 1u}) {
|
|
userdata[4] = condition;
|
|
Check(MaterializeResources(plan, {.user_data = userdata}, snapshot, specialization),
|
|
"scalar-selected VCC address did not materialize");
|
|
const auto offset = condition ? 0u : 2u;
|
|
Check(snapshot.buffers.size() == 1 && snapshot.flattened_srt.empty() &&
|
|
snapshot.specialization_reads.empty() &&
|
|
snapshot.buffers[0].dwords[0] == userdata[offset] &&
|
|
snapshot.buffers[0].dwords[1] == userdata[offset + 1],
|
|
"VCC selection replaced raw address words with a lane ballot");
|
|
}
|
|
const auto compiled = ShaderRecompiler::CompileProgram(
|
|
std::move(translated), options, specialization);
|
|
CheckSpirvBinaryValidates(compiled.spirv);
|
|
}
|
|
}
|
|
|
|
void TestNativeGuardedSamplerSource() {
|
|
constexpr uint32_t nested = 12, sample = 18, join = 20, end = 22;
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x07, 21, 128),
|
|
EncodeSopp(0x04, end - 1 - 1),
|
|
EncodeSopc(0x07, 20, 130),
|
|
EncodeSopp(0x04, sample - 3 - 1),
|
|
EncodeSMovB32(16, 133), EncodeSMovB32(17, 134),
|
|
EncodeSMovB32(18, 135), EncodeSMovB32(19, 136),
|
|
EncodeSopc(0x07, 20, 129),
|
|
EncodeSopp(0x04, nested - 9 - 1),
|
|
EncodeVop1(0x01, 0, 128),
|
|
EncodeSopp(0x02, join - 11 - 1),
|
|
EncodeSMovB32(16, 137), EncodeSMovB32(17, 138),
|
|
EncodeSMovB32(18, 139), EncodeSMovB32(19, 140),
|
|
EncodeVop1(0x01, 0, 129),
|
|
EncodeSopp(0x02, join - 17 - 1),
|
|
EncodeMimg0(0x20, 0x1), EncodeMimg1(0, 2, 4, 2),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto user_data = ImageTestUserData();
|
|
const std::array<uint32_t, 4> sampler{0u, 0u, 0x09500000u, 0u};
|
|
std::ranges::copy(sampler, user_data.begin() + 8);
|
|
user_data[13] = 1u;
|
|
for (uint32_t mode = 0; mode != 3; ++mode) {
|
|
user_data[12] = mode;
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.user_data_base = 8;
|
|
options.user_data = user_data;
|
|
const auto result = RecompileForTest(shader, options);
|
|
const auto expected = mode == 2u ? sampler : std::array<uint32_t, 4>{};
|
|
Check(!result.program.dispatcher_fallback && result.resources.samplers.size() == 1u &&
|
|
result.resources.samplers[0].dword_count == expected.size() &&
|
|
std::equal(expected.begin(), expected.end(), result.resources.samplers[0].dwords.begin()),
|
|
"guarded native sample selected an unrelated arm's sampler constants");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestBoundedMaterialBufferStores(bool scalar_key) {
|
|
// Captured batch search and descriptor waterfall, with deformation arithmetic removed.
|
|
const uint32_t shader[] = {
|
|
0xbfa00003u, 0xd7460002u, 0x04010a04u, 0xf4080101u,
|
|
0xfa000080u, 0xbe8e0380u, 0xbe8f037eu, 0xbf8cc07fu,
|
|
0xf4240302u, 0xfa000014u, 0xf4080201u, 0xfa000020u,
|
|
0xbf880019u, 0x7e3602c1u, 0xbf8cc07fu, 0xbf0a0d0eu,
|
|
0xbf800000u, 0x8506807eu, 0xbeea0306u, 0xbefe0306u,
|
|
0xbf860011u, 0x816b0e0cu, 0x810e810eu, 0x936aff6bu,
|
|
0x00000050u, 0x7e36026bu, 0x816aa46au, 0xf4240104u,
|
|
0xd4000000u, 0xbf8cc07fu, 0xbeea0404u, 0x816a0405u,
|
|
0x7d8604f9u, 0x06868405u, 0x7d88046au, 0x876a6a04u,
|
|
0x8a7e6a06u, 0xbf89ffe6u, 0xbefe030fu, 0x7daa36c1u,
|
|
0xbf880013u, 0xbf8cc07fu, 0xe0342030u, 0x8002171bu,
|
|
0xf4080100u, 0xfa000020u, 0x7e020281u, 0x7ed40518u,
|
|
0xbeeb037eu, 0x7da4306au, 0x8f6a846au, 0x7e020280u,
|
|
0xf4280002u, 0xd4000000u, 0xbf8cc07fu, 0xe0701000u,
|
|
0x80003405u, 0xbefe036bu, 0x7daa0280u, 0xbf89fff3u,
|
|
0xbf810000u,
|
|
};
|
|
std::array<uint32_t, 8 * 20> material{};
|
|
std::array<uint32_t, 16 * 4> table{};
|
|
std::array<uint32_t, 8> batch{};
|
|
std::array<uint32_t, 12> table_root{};
|
|
std::array<uint32_t, 36> batch_root{};
|
|
const auto buffer = [](uint32_t* words, const void* data, uint32_t stride,
|
|
uint32_t count, uint32_t mode = 0u) {
|
|
const auto address = reinterpret_cast<uint64_t>(data);
|
|
words[0] = static_cast<uint32_t>(address);
|
|
words[1] = static_cast<uint32_t>(address >> 32u) | (stride << 16u);
|
|
words[2] = count;
|
|
words[3] = 0x5004u | (mode << 28u);
|
|
};
|
|
batch[5] = 1u;
|
|
batch[6] = 2u;
|
|
material[20 + 9] = 32u;
|
|
material[20 + 13] = 3u;
|
|
material[40 + 9] = 32u;
|
|
material[40 + 10] = 32u;
|
|
material[40 + 13] = 5u;
|
|
buffer(table.data() + 12u, reinterpret_cast<void*>(0x100000u), 1u, 128u, 3u);
|
|
buffer(table.data() + 20u, reinterpret_cast<void*>(0x200000u), 1u, 128u, 3u);
|
|
buffer(table_root.data() + 8u, table.data(), 16u, 16u);
|
|
buffer(batch_root.data() + 8u, material.data(), 80u, 8u);
|
|
buffer(batch_root.data() + 32u, batch.data(), 16u, 2u);
|
|
const auto table_address = reinterpret_cast<uint64_t>(table_root.data());
|
|
const auto batch_address = reinterpret_cast<uint64_t>(batch_root.data());
|
|
const std::array<uint32_t, 4> user_data{
|
|
static_cast<uint32_t>(table_address), static_cast<uint32_t>(table_address >> 32u),
|
|
static_cast<uint32_t>(batch_address), static_cast<uint32_t>(batch_address >> 32u)};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.wave_size = 32u;
|
|
options.user_data = user_data;
|
|
auto code = std::to_array(shader);
|
|
if (scalar_key) {
|
|
code[0xa8u / 4u] = EncodeMubuf0(0x0cu, 52u);
|
|
code[0xacu / 4u] = EncodeMubuf1(24u, 2u, 27u);
|
|
}
|
|
auto translated = ShaderRecompiler::TranslateProgram(code, options);
|
|
auto plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
|
ShaderRecompiler::IR::ResourceSnapshot snapshot;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
.user_data = user_data, .read_memory = ReadHostTestMemory,
|
|
.read_specialization_memory = ReadHostTestMemory};
|
|
const auto materialize = [&] {
|
|
return ShaderRecompiler::IR::MaterializeResources(plan, runtime, snapshot, specialization);
|
|
};
|
|
Check(materialize(), "bounded material range failed to materialize");
|
|
const auto& buffers = specialization.buffers;
|
|
const auto root = std::ranges::find_if(buffers, [](const auto& resource) {
|
|
return resource.indirect_root != ShaderRecompiler::IR::BufferResource::NoIndirectBuffer;
|
|
});
|
|
Check(root != buffers.end() && snapshot.flattened_srt[root->indirect_mapping_offset] == 2u &&
|
|
buffers.size() == plan.info.buffers.size() + 1u,
|
|
"bounded material range did not select exactly its two writable buffers");
|
|
const auto root_index = static_cast<size_t>(root - buffers.begin());
|
|
const auto first_specialization = specialization;
|
|
table[12] += 0x1000u;
|
|
Check(materialize() && specialization == first_specialization,
|
|
"buffer address changes created a shader permutation");
|
|
batch[6] = 0u;
|
|
Check(materialize() && specialization.buffers[root_index].indirect_root ==
|
|
ShaderRecompiler::IR::BufferResource::NoIndirectBuffer &&
|
|
std::ranges::all_of(snapshot.buffers[root_index].dwords,
|
|
[](uint32_t word) { return word == 0u; }) &&
|
|
specialization.buffers.size() == plan.info.buffers.size(),
|
|
"empty material batch retained writable candidates");
|
|
batch[6] = 2u;
|
|
batch[5] = 7u;
|
|
Check(!materialize(), "material batch exceeded the descriptor record count");
|
|
batch[5] = 1u;
|
|
batch_root[11] |= 1u << 30u;
|
|
Check(!materialize(), "material selection accepted a non-buffer descriptor type");
|
|
batch_root[11] &= ~(1u << 30u);
|
|
Check(materialize(), "restored material batch failed to materialize");
|
|
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options, specialization, 0u);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
auto unbounded = code;
|
|
unbounded[0x3cu / 4u] = EncodeSopc(0x0bu, 14u, 13u); // s_cmp_le_u32 admits wraparound.
|
|
ExpectFatal([&] { ShaderRecompiler::TranslateProgram(unbounded, options); },
|
|
"material selection accepted an inclusive induction bound");
|
|
#endif
|
|
}
|
|
|
|
void TestNativeDescriptorProvenanceKeepsGpuSelection() {
|
|
const std::array<uint32_t, 13> selected{
|
|
EncodeSmem0(0x02, 8, 6), 125u << 25u,
|
|
EncodeVop1(0x01, 1, 128),
|
|
EncodeVopc(0xc4, 256, 1),
|
|
EncodeSopp(0x06, 2),
|
|
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 16u,
|
|
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
|
EncodeVop1(0x01, 2, 16),
|
|
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(2, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto partial = selected;
|
|
partial[5] = EncodeVop1(0x02, 11, 256);
|
|
partial[6] = EncodeSopp(0x00);
|
|
const std::array<uint32_t, 13> loop{
|
|
EncodeSmem0(0x02, 8, 6), 125u << 25u,
|
|
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
|
EncodeVop1(0x01, 2, 16),
|
|
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(2, 0, 0),
|
|
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 16u,
|
|
EncodeVop1(0x01, 1, 128),
|
|
EncodeVopc(0xc4, 256, 1),
|
|
EncodeSopp(0x07, 0xfff6u),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const std::array<uint32_t, 8> table{0x1000u, 0u, 4u, 3u << 28u,
|
|
0x2000u, 0u, 4u, 3u << 28u};
|
|
std::array<uint32_t, 14> user_data{};
|
|
user_data[0] = 0x3000u;
|
|
user_data[2] = 64u;
|
|
user_data[3] = 0x30005204u;
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
user_data[12] = static_cast<uint32_t>(address);
|
|
user_data[13] = static_cast<uint32_t>(address >> 32u);
|
|
ShaderComputeInputInfo input_info{};
|
|
input_info.thread_ids_num = 1;
|
|
input_info.threads_num[0] = 64;
|
|
input_info.threads_num[1] = input_info.threads_num[2] = 1;
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.input_info.compute = &input_info;
|
|
options.user_data = user_data;
|
|
for (const auto &shader : {selected, partial, loop}) {
|
|
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
|
Check(result.program.info.uses_dma && result.program.info.buffers.size() == 1u &&
|
|
result.program.info.buffers[0].written,
|
|
"GPU-selected scalar descriptor was materialized as a host binding");
|
|
uint32_t indirect_reads = 0;
|
|
for (const auto *block : result.program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() != ShaderRecompiler::IR::ValueOpcode::ReadConstBuffer) continue;
|
|
const auto &memory = result.program.memory_info[
|
|
inst.Flags<ShaderRecompiler::IR::MemoryFlags>().index];
|
|
indirect_reads += memory.kind == ShaderRecompiler::IR::ResourceKind::IndirectBuffer &&
|
|
!memory.planning_only;
|
|
}
|
|
}
|
|
Check(indirect_reads == 1u, "GPU-selected scalar read was flattened or removed");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestCfgSiblingSharedExit() {
|
|
// f7030726b9470dd8: a nested exit on one side and two exits on the
|
|
// other side share an epilogue, separate from the normal return.
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 128), EncodeSopp(0x04, 5), // right or left
|
|
EncodeSopc(0x06, 1, 128), EncodeSopp(0x04, 2), // join or nested exit
|
|
EncodeSopc(0x06, 2, 128), EncodeSopp(0x04, 7), // shared exit
|
|
EncodeSopp(0x02, 4), // left -> main
|
|
EncodeSopc(0x06, 3, 128), EncodeSopp(0x04, 4), // right -> shared exit
|
|
EncodeSopc(0x06, 4, 128), EncodeSopp(0x04, 2), // second shared exit
|
|
EncodeSMovB32(5, 129), EncodeSopp(0x01), // main and normal return
|
|
EncodeSMovB32(5, 130), EncodeSopp(0x01), // shared exit epilogue
|
|
};
|
|
using namespace ShaderRecompiler;
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
const auto original = CFG::BuildGraph(decoded);
|
|
auto structured = CFG::Structurize(original);
|
|
Check(!structured.unsupported, structured.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(structured, decoded.instructions.size()) ==
|
|
CfgInstructionCoverage(original, decoded.instructions.size()),
|
|
"shared sibling exit duplicated or dropped guest instructions");
|
|
const auto trace = [&](const CFG::Graph &graph, uint32_t decisions) {
|
|
std::vector<uint32_t> executed;
|
|
std::vector<bool> variables(original.blocks.size() * 2);
|
|
const auto expression = [&](auto &&self, uint32_t index) -> bool {
|
|
const auto &value = graph.expressions.at(index);
|
|
switch (value.op) {
|
|
case CFG::ConditionExpression::Op::Constant: return value.lhs != 0;
|
|
case CFG::ConditionExpression::Op::Variable: return variables.at(value.lhs);
|
|
case CFG::ConditionExpression::Op::Native: std::abort();
|
|
case CFG::ConditionExpression::Op::Not: return !self(self, value.lhs);
|
|
case CFG::ConditionExpression::Op::Or:
|
|
return self(self, value.lhs) || self(self, value.rhs);
|
|
}
|
|
std::abort();
|
|
};
|
|
auto current = graph.entry_block;
|
|
for (unsigned steps = 0; steps < 100; ++steps) {
|
|
const auto &block = graph.blocks.at(current);
|
|
for (auto inst = block.inst_begin; inst < block.inst_end; ++inst)
|
|
executed.push_back(inst);
|
|
for (const auto &assignment : block.assignments) {
|
|
variables.at(assignment.variable) =
|
|
graph.expressions[assignment.expression].op == CFG::ConditionExpression::Op::Native
|
|
? (decisions & (1u << (assignment.variable - original.blocks.size()))) != 0
|
|
: expression(expression, assignment.expression);
|
|
}
|
|
const auto &term = block.terminator;
|
|
if (term.kind == CFG::TerminatorKind::Return) return executed;
|
|
Check(term.kind == CFG::TerminatorKind::Branch ||
|
|
term.kind == CFG::TerminatorKind::ConditionalBranch,
|
|
"shared exit trace has an unsupported terminator");
|
|
const bool condition = term.expression != UINT32_MAX
|
|
? expression(expression, term.expression)
|
|
: (decisions & (1u << block.id)) != 0;
|
|
current = term.kind == CFG::TerminatorKind::Branch || condition
|
|
? term.true_block : term.false_block;
|
|
}
|
|
Check(false, "shared exit trace did not terminate");
|
|
return executed;
|
|
};
|
|
for (uint32_t decisions = 0; decisions < (1u << original.blocks.size()); ++decisions)
|
|
Check(trace(original, decisions) == trace(structured, decisions),
|
|
"goto elimination changed the executed shared-exit path");
|
|
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
|
Check(!result.program.dispatcher_fallback &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
|
"shared sibling exit selected dispatcher lowering");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgNestedTailEarlyExit() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // preceding outer condition
|
|
EncodeSopp(0x04, 6), // preceding outer -> shared tail or inner
|
|
EncodeSopc(0x06, 1, 1), // preceding inner condition
|
|
EncodeSopp(0x04, 2), // preceding inner -> body or arm
|
|
EncodeSMovB32(2, 129), // preceding arm
|
|
EncodeSopp(0x02, 0), // preceding arm -> body
|
|
EncodeSMovB32(3, 129), // preceding body
|
|
EncodeSopp(0x02, 0), // preceding body -> shared tail
|
|
EncodeSMovB32(4, 129), // preceding shared tail
|
|
EncodeSopc(0x06, 5, 5), // outer condition
|
|
EncodeSopp(0x04, 4), // outer -> right arm or inner condition
|
|
EncodeSopc(0x06, 6, 6), // inner early-exit condition
|
|
EncodeSopp(0x04, 4), // inner -> exit or left arm
|
|
EncodeSMovB32(7, 129), // left arm
|
|
EncodeSopp(0x02, 1), // left arm -> common tail
|
|
EncodeSMovB32(8, 129), // right arm
|
|
EncodeSMovB32(9, 129), // common tail
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"nested-tail routing changed semantic instruction coverage");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"nested-tail early exit selected dispatcher control flow");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // outer forward-skip condition
|
|
EncodeSopp(0x04, 4), // outer -> shared or nested condition
|
|
EncodeSopc(0x06, 1, 1), // nested forward-skip condition
|
|
EncodeSopp(0x04, 2), // nested -> shared or work
|
|
EncodeSMovB32(2, 129), // forward-skip work
|
|
EncodeSopp(0x02, 0), // work -> shared
|
|
EncodeSMovB32(3, 129), // shared work
|
|
EncodeSopc(0x06, 4, 4), // outer terminal condition
|
|
EncodeSopp(0x04, 3), // outer -> shared terminal or inner
|
|
EncodeSopc(0x06, 5, 5), // inner terminal condition
|
|
EncodeSopp(0x04, 1), // inner -> shared terminal or other terminal
|
|
0xbf810000u, // other terminal
|
|
0xbf810000u, // shared terminal
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"shared-exit route ordering changed semantic instruction coverage");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"inner-first shared-exit routing did not stay structured");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgAlternatingSharedReturns() {
|
|
// PS 0x845315ec188c33db: nested conditions alternate between two shared
|
|
// terminal epilogues. Each branch must leave through its own selection merge.
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 128),
|
|
EncodeSopp(0x04, 8), // outer -> second return or middle condition
|
|
EncodeSopc(0x06, 1, 128),
|
|
EncodeSopp(0x04, 2), // middle -> first return or inner condition
|
|
EncodeSopc(0x06, 2, 128),
|
|
EncodeSopp(0x04, 4), // inner -> second return or first return
|
|
EncodeVop1(0x01, 0, 129),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
EncodeVop1(0x01, 0, 130),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(graph.blocks.size() == 5u, "alternating returns fixture has the wrong CFG");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
|
"alternating returns duplicated a terminal epilogue");
|
|
|
|
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Pixel));
|
|
Check(!result.program.dispatcher_fallback,
|
|
"alternating returns did not use structured control flow");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgLoopSharedRegion() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // loop condition
|
|
EncodeSopp(0x04, 14), // loop exit -> end
|
|
EncodeSopc(0x06, 1, 1), // outer condition
|
|
EncodeSopp(0x04, 2), // outer -> shared region or inner
|
|
EncodeSopc(0x06, 2, 2), // inner condition
|
|
EncodeSopp(0x04, 6), // inner -> common tail or shared region
|
|
EncodeSopc(0x06, 3, 3), // shared region condition
|
|
EncodeSopp(0x04, 2), // shared region -> right or left
|
|
EncodeSMovB32(4, 129), // shared left work
|
|
EncodeSopp(0x02, 2), // shared left -> common tail
|
|
EncodeSMovB32(5, 129), // shared right work
|
|
EncodeSopp(0x02, 0), // shared right -> common tail
|
|
EncodeSop2(0x00, 6, 6, 129), // common tail work
|
|
EncodeSopp(0x02, 0), // common tail -> continue
|
|
EncodeSop2(0x00, 7, 7, 129), // continue work
|
|
EncodeSopp(0x02, 0xfff0u), // backedge -> loop header
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(graph.natural_loops.size() == 1u,
|
|
"loop shared-region fixture has the wrong native CFG");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
const auto *loop_header = graph.FindBlockByPc(0x00u);
|
|
Check(graph.natural_loops.size() == 1u && graph.back_edges.size() == 1u &&
|
|
loop_header != nullptr && loop_header->terminator.loop_header &&
|
|
loop_header->terminator.continue_block != UINT32_MAX,
|
|
"loop shared-region routing did not preserve the natural loop");
|
|
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage,
|
|
"loop shared region changed guest instruction coverage");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 246) == 1u &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"loop shared region unexpectedly selected dispatcher fallback");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop() {
|
|
// The first selection needs routing at its externally entered common tail.
|
|
// A later loop has the same lexical shared-arm shape, but its exit branches
|
|
// are already structured and must retain their loop-control role.
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // 0: enclosing selection
|
|
EncodeSopp(0x04, 4), // 0 -> 3 or 1
|
|
EncodeSopc(0x06, 1, 1), // 1: inner selection
|
|
EncodeSopp(0x04, 6), // 1 -> 5 or 2
|
|
EncodeSMovB32(2, 129), // 2: inner body
|
|
EncodeSopp(0x02, 2), // 2 -> 4
|
|
EncodeSMovB32(3, 129), // 3: outer arm
|
|
EncodeSopp(0x02, 0), // 3 -> 4
|
|
EncodeSMovB32(4, 129), // 4: shared tail
|
|
EncodeSopp(0x02, 0), // 4 -> 5
|
|
EncodeSopc(0x06, 5, 5), // 5: loop header
|
|
EncodeSopp(0x04, 4), // 5 -> 8 or 6
|
|
EncodeSopc(0x06, 6, 6), // 6: early break
|
|
EncodeSopp(0x04, 2), // 6 -> 8 or 7
|
|
EncodeSopc(0x06, 7, 7), // 7: loop latch
|
|
EncodeSopp(0x05, 0xfffau), // 7 -> 5 or 8
|
|
EncodeSMovB32(8, 129), // 8: terminal
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(graph.blocks.size() == 9u && graph.natural_loops.size() == 1u,
|
|
"shared-region/early-break fixture has the wrong native CFG");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(graph.natural_loops.size() == 1u && graph.back_edges.size() == 1u,
|
|
"selection routing introduced a cycle around a structured loop exit");
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"shared-region/early-break routing changed semantic coverage");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 246) == 1u &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"shared-region/early-break routing lost structured loop control");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // block 0
|
|
EncodeSopp(0x04, 2), // block 0 -> 2 or 1
|
|
EncodeSopc(0x06, 1, 1), // block 1
|
|
EncodeSopp(0x04, 6), // block 1 -> 5 or 2
|
|
EncodeSopc(0x06, 2, 2), // block 2
|
|
EncodeSopp(0x04, 4), // block 2 -> 5 or 3
|
|
EncodeSopc(0x06, 3, 3), // block 3
|
|
EncodeSopp(0x04, 2), // block 3 -> 5 or 4
|
|
EncodeSMovB32(4, 129), // block 4
|
|
0xbf810000u, // block 4 returns
|
|
EncodeSMovB32(5, 129), // block 5
|
|
0xbf810000u, // block 5 returns
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
Check(
|
|
graph.blocks.size() == 6u &&
|
|
graph.blocks[0].successors == std::vector<uint32_t>({1, 2}) &&
|
|
graph.blocks[0].terminator.true_block == 2u &&
|
|
graph.blocks[0].terminator.false_block == 1u &&
|
|
graph.blocks[1].successors == std::vector<uint32_t>({2, 5}) &&
|
|
graph.blocks[1].terminator.true_block == 5u &&
|
|
graph.blocks[1].terminator.false_block == 2u &&
|
|
graph.blocks[2].successors == std::vector<uint32_t>({3, 5}) &&
|
|
graph.blocks[2].terminator.true_block == 5u &&
|
|
graph.blocks[2].terminator.false_block == 3u &&
|
|
graph.blocks[3].successors == std::vector<uint32_t>({4, 5}) &&
|
|
graph.blocks[3].terminator.true_block == 5u &&
|
|
graph.blocks[3].terminator.false_block == 4u &&
|
|
graph.blocks[4].successors.empty() &&
|
|
graph.blocks[5].successors.empty() &&
|
|
graph.blocks[4].terminator.kind ==
|
|
ShaderRecompiler::CFG::TerminatorKind::Return &&
|
|
graph.blocks[5].terminator.kind ==
|
|
ShaderRecompiler::CFG::TerminatorKind::Return,
|
|
"overlapping early-exit fixture does not match the observed shader CFG");
|
|
const auto original_block_count = graph.blocks.size();
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"early-exit ladder routing changed semantic instruction coverage");
|
|
Check(graph.blocks.size() > original_block_count,
|
|
"early-exit ladder routing did not add forwarding blocks");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"overlapping early-exit ladder did not stay structured");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"overlapping early-exit ladder unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // enclosing condition
|
|
EncodeSopp(0x04, 4), // enclosing -> continuation or inner header
|
|
EncodeSopc(0x06, 1, 1), // inner early-exit condition
|
|
EncodeSopp(0x04, 4), // inner -> shared terminal or body
|
|
EncodeSMovB32(2, 129), // inner body
|
|
EncodeSopp(0x02, 0), // body -> enclosing continuation
|
|
EncodeSMovB32(3, 129), // enclosing continuation
|
|
EncodeSopp(0x02, 0), // continuation -> shared terminal
|
|
EncodeSMovB32(4, 129), // shared terminal epilogue
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"nested early-exit structurization changed semantic coverage");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"nested early exit to a shared terminal did not stay structured");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgEarlyReturnSharedLoopContinuation() {
|
|
// PS d6fb5f22c689ceff: an outer EXEC skip and an inner SCC exit share the
|
|
// continuing arm, which contains another loop and reaches a different return.
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 128),
|
|
EncodeSopp(0x04, 7), // outer -> shared loop or preceding work
|
|
EncodeSMovB32(2, 129),
|
|
EncodeSopc(0x06, 2, 128), // preceding work loop
|
|
EncodeSopp(0x04, 2), // loop -> early-return condition or body
|
|
EncodeSop2(0x01, 2, 2, 129),
|
|
EncodeSopp(0x02, -4),
|
|
EncodeSopc(0x06, 1, 128),
|
|
EncodeSopp(0x04, 5), // inner -> private return or shared loop
|
|
EncodeSopc(0x06, 3, 128), // shared loop header
|
|
EncodeSopp(0x04, 2), // loop -> normal return or body
|
|
EncodeSop2(0x01, 3, 3, 129),
|
|
EncodeSopp(0x02, -4), // body -> loop header
|
|
0xbf810000u, // normal return
|
|
EncodeSMovB32(4, 130), // private return epilogue
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage &&
|
|
graph.natural_loops.size() == 2u,
|
|
"shared-continuation gateway changed semantic blocks or the loops");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"early return with a shared loop continuation selected the dispatcher");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgSharedTerminalEarlyExit() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopc(0x06, 0, 0), // outer early-exit condition
|
|
EncodeSopp(0x04, 8), // outer -> early exit or nested body
|
|
EncodeSopc(0x06, 1, 1), // nested body condition
|
|
EncodeSopp(0x04, 1), // nested body -> right or left
|
|
EncodeSopp(0x02, 4), // left -> body continuation
|
|
EncodeSopc(0x06, 2, 2), // right condition
|
|
EncodeSopp(0x04, 1), // right -> join or work
|
|
EncodeSopp(0x02, 0), // work -> join
|
|
EncodeSopp(0x02, 0), // join -> body continuation
|
|
EncodeSopp(0x02, 1), // body continuation -> shared terminal
|
|
EncodeSopp(0x02, 0), // early exit -> shared terminal
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(
|
|
CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"shared-terminal structurization changed semantic instruction coverage");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos),
|
|
"shared-terminal early exit selected dispatcher control flow");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"shared-terminal early exit unexpectedly used dispatcher OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x02, 1), // entry -> header, skipping unreachable entry
|
|
EncodeSopp(0x02, 2), // unreachable entry -> shared selection arm
|
|
EncodeSopc(0x06, 0, 0), // header condition
|
|
EncodeSopp(0x04, 1), // header -> merge or shared arm
|
|
EncodeSMovB32(1, 129), // shared arm
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
const auto original_coverage =
|
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
|
Check(original_coverage[1] == 0u,
|
|
"CFG retained an unreachable external selection entry");
|
|
graph = ShaderRecompiler::CFG::Structurize(graph);
|
|
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
original_coverage,
|
|
"selection structurization changed reachable semantic code");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(!result.program.dispatcher_fallback &&
|
|
(result.ir_dump.find("mode=structured") != std::string::npos) &&
|
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
|
"unreachable selection entry still forced dispatcher mode");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgFailedStructurizationPreservesGraph() {
|
|
using namespace ShaderRecompiler;
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(0, 129),
|
|
EncodeSopp(0x02, 0xfffeu), // semantic loop header, no exit/merge
|
|
0xbf810000u, // unreachable decoder terminator
|
|
};
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
auto graph = CFG::BuildGraph(decoded);
|
|
const auto original = graph;
|
|
Check(graph.natural_loops.size() == 1u && !graph.irreducible,
|
|
"failed structurization fixture must have one reducible loop");
|
|
const auto failed = CFG::Structurize(graph);
|
|
Check(failed.unsupported &&
|
|
failed.failure_kind == CFG::FailureKind::StructuredControlFlow &&
|
|
!failed.unsupported_reason.empty(),
|
|
"loop without a merge did not retain its structurization failure");
|
|
Check(failed.failure_block == UINT32_MAX,
|
|
"failed structurization exposed a discarded synthetic block ID");
|
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
|
CfgInstructionCoverage(original, decoded.instructions.size()),
|
|
"failed structurization changed instruction coverage");
|
|
Check(CFG::GraphToString(graph) == CFG::GraphToString(original),
|
|
"failed structurization changed CFG topology or analyses");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(result.program.dispatcher_fallback &&
|
|
result.program.cfg_failure_kind == failed.failure_kind &&
|
|
result.program.fallback_reason == failed.unsupported_reason,
|
|
"compiler did not consume CFG failure diagnostics directly");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerCfgIrreducibleDispatcher() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x05, 2), // entry -> B, fallthrough A
|
|
EncodeSopp(0x02, 0), // A -> C
|
|
EncodeSopp(0x05, 0xfffeu), // C -> A, fallthrough B
|
|
EncodeSopp(0x02, 0xfffeu), // B -> C
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("irreducible CFG") != std::string::npos),
|
|
"irreducible CFG reason was not retained");
|
|
Check((result.ir_dump.find("mode=dispatcher") != std::string::npos),
|
|
"irreducible CFG did not select dispatcher fallback");
|
|
Check(SpirvContainsOpcode(result.spirv, 246),
|
|
"dispatcher SPIR-V lacks OpLoopMerge");
|
|
Check(SpirvContainsOpcode(result.spirv, 251),
|
|
"dispatcher SPIR-V lacks OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerDispatcherSpillsU32x3() {
|
|
using namespace ShaderRecompiler;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x05, 2), // entry -> B, fallthrough A
|
|
EncodeSopp(0x02, 0), // A -> C
|
|
EncodeSopp(0x05, 0xfffeu), // C -> A, fallthrough B
|
|
EncodeSopp(0x02, 0xfffeu), // B -> C
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(result.program.dispatcher_fallback &&
|
|
result.program.blocks.size() >= 3u,
|
|
"U32x3 spill fixture did not select dispatcher mode");
|
|
const auto before = MeasureSpirv(result.spirv);
|
|
{
|
|
auto prologue_program = std::move(result.program);
|
|
IR::IREmitter definition(prologue_program.blocks[0]);
|
|
const auto vector =
|
|
definition.Emit(IR::ValueOpcode::CompositeConstructU32x3,
|
|
{IR::Value(1u), IR::Value(2u), IR::Value(3u)});
|
|
IR::IREmitter use(prologue_program.blocks[1]);
|
|
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
|
|
|
|
auto spirv = ShaderRecompiler::Spirv::EmitProgram(prologue_program,
|
|
options.input_info);
|
|
CheckSpirvBinaryValidates(spirv);
|
|
const auto after = MeasureSpirv(spirv);
|
|
Check(after.function_variables == before.function_variables &&
|
|
after.loads == before.loads && after.stores == before.stores,
|
|
"dispatcher spilled a value defined by its dominating prologue");
|
|
}
|
|
|
|
auto spill_result = RecompileForTest(shader, options);
|
|
auto program = std::move(spill_result.program);
|
|
IR::IREmitter definition(program.blocks[1]);
|
|
const auto vector =
|
|
definition.Emit(IR::ValueOpcode::CompositeConstructU32x3,
|
|
{IR::Value(1u), IR::Value(2u), IR::Value(3u)});
|
|
IR::IREmitter use(program.blocks[2]);
|
|
const auto high =
|
|
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
|
|
const auto middle =
|
|
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(1u)});
|
|
// Composite indices stay literal; arithmetic immediates become constant IDs.
|
|
// Resolving either arithmetic input must reuse the same dispatcher load.
|
|
const auto sum = use.Emit(IR::ValueOpcode::IAdd32, {high, IR::Value(7u)});
|
|
use.Emit(IR::ValueOpcode::IAdd32, {sum, middle});
|
|
|
|
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program,
|
|
options.input_info);
|
|
CheckSpirvBinaryValidates(spirv);
|
|
const auto after = MeasureSpirv(spirv);
|
|
Check(after.function_variables == before.function_variables + 1u &&
|
|
after.loads == before.loads + 1u &&
|
|
after.stores == before.stores + 1u,
|
|
"dispatcher did not use one canonical U32x3 spill slot");
|
|
}
|
|
|
|
void TestNewShaderRecompilerPlanningOnlyLoads() {
|
|
using namespace ShaderRecompiler;
|
|
const uint32_t shader[] = {EncodeSopp(0x01)};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto result = RecompileForTest(shader, options);
|
|
auto& program = result.program;
|
|
IR::IREmitter ir(program.blocks.front());
|
|
IR::MemoryInfo memory;
|
|
memory.kind = IR::ResourceKind::ScalarAddress;
|
|
memory.planning_only = true;
|
|
program.memory_info.push_back(memory);
|
|
const auto address = ir.Emit(IR::ValueOpcode::GetAddressResource,
|
|
{IR::Value(0x1000u), IR::Value(0u)});
|
|
const auto load = ir.Emit(IR::ValueOpcode::LoadAddressU32,
|
|
{address, IR::Value(0u), IR::Value(0u), IR::Value(true)});
|
|
load.Instruction()->SetFlags(IR::MemoryFlags{.index = 0});
|
|
memory.kind = IR::ResourceKind::ScalarBuffer;
|
|
program.memory_info.push_back(memory);
|
|
const auto buffer = ir.Emit(IR::ValueOpcode::GetBufferResource,
|
|
{IR::Value(0x2000u), IR::Value(0u),
|
|
IR::Value(16u), IR::Value(0u)});
|
|
const auto read = ir.Emit(IR::ValueOpcode::ReadConstBuffer,
|
|
{buffer, IR::Value(0u)});
|
|
read.Instruction()->SetFlags(IR::MemoryFlags{.index = 1});
|
|
ir.Emit(IR::ValueOpcode::ReferenceU32, {load});
|
|
ir.Emit(IR::ValueOpcode::ReferenceU32, {read});
|
|
const auto spirv = Spirv::EmitProgram(program, options.input_info);
|
|
CheckSpirvBinaryValidates(spirv);
|
|
Check(spirv == result.spirv,
|
|
"planning-only loads or references emitted GPU operands/instructions");
|
|
}
|
|
|
|
void TestNewShaderRecompilerU64PairTranslation() {
|
|
using namespace ShaderRecompiler;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
TestCompileResult result;
|
|
|
|
const uint32_t lshrrev_b64_shader[] = {
|
|
0xd7000001u, 0x0000d50cu, // v_lshrrev_b64 v[1:2], v12, vcc
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Instruction decoded_lshrrev_b64;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(lshrrev_b64_shader, 0u,
|
|
decoded_lshrrev_b64);
|
|
Check(decoded_lshrrev_b64.opcode == Decoder::Opcode::V_LSHRREV_B64 &&
|
|
decoded_lshrrev_b64.dst.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded_lshrrev_b64.dst.reg == 1u &&
|
|
decoded_lshrrev_b64.src_count == 2u &&
|
|
decoded_lshrrev_b64.src0.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded_lshrrev_b64.src0.reg == 12u &&
|
|
decoded_lshrrev_b64.src1.kind == Decoder::OperandKind::VccLo,
|
|
"decoder rejected captured VOP3 V_LSHRREV_B64 fields");
|
|
result = RecompileForTest(lshrrev_b64_shader, options);
|
|
Check((result.decoded_dump.find("V_LSHRREV_B64 v1, v12, vcc_lo") != std::string::npos),
|
|
"captured VOP3 V_LSHRREV_B64 was not present in the decoded dump");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t lshlrev_b64_shader[] = {
|
|
0xd6ff0021u, 0x00010303u, // v_lshlrev_b64 v[33:34], v3, 1
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Instruction decoded_lshlrev_b64;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(lshlrev_b64_shader, 0u,
|
|
decoded_lshlrev_b64);
|
|
Check(decoded_lshlrev_b64.opcode == Decoder::Opcode::V_LSHLREV_B64 &&
|
|
decoded_lshlrev_b64.dst.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded_lshlrev_b64.dst.reg == 33u &&
|
|
decoded_lshlrev_b64.src_count == 2u &&
|
|
decoded_lshlrev_b64.src0.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded_lshlrev_b64.src0.reg == 3u &&
|
|
decoded_lshlrev_b64.src1.kind ==
|
|
Decoder::OperandKind::IntegerInlineConstant &&
|
|
decoded_lshlrev_b64.src1.value == 1u,
|
|
"decoder rejected captured VOP3 V_LSHLREV_B64 fields");
|
|
result = RecompileForTest(lshlrev_b64_shader, options);
|
|
Check((result.decoded_dump.find("V_LSHLREV_B64 v33, v3, 1") != std::string::npos),
|
|
"captured VOP3 V_LSHLREV_B64 was not present in the decoded dump");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t cmpx_i64_shader[] = {
|
|
0xd4b5007eu, 0x00020e80u, // v_cmpx_ne_i64 exec, 0, v[7:8]
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Instruction decoded_cmpx_i64;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(cmpx_i64_shader, 0u,
|
|
decoded_cmpx_i64);
|
|
Check(decoded_cmpx_i64.opcode == Decoder::Opcode::V_CMPX_NE_I64 &&
|
|
decoded_cmpx_i64.dst.kind == Decoder::OperandKind::ExecLo &&
|
|
decoded_cmpx_i64.src0.value == 0u &&
|
|
decoded_cmpx_i64.src1.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded_cmpx_i64.src1.reg == 7u,
|
|
"decoder rejected captured VOP3 V_CMPX_NE_I64 fields");
|
|
result = RecompileForTest(cmpx_i64_shader, options);
|
|
Check((result.decoded_dump.find("V_CMPX_NE_I64 exec_lo, 0, v7") != std::string::npos),
|
|
"captured VOP3 V_CMPX_NE_I64 was not present in the decoded dump");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t cmpx_shader[] = {
|
|
0x7dea0e80u, // v_cmpx_ne_u64 exec, 0, v[7:8]
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Instruction decoded_cmpx;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(cmpx_shader, 0u, decoded_cmpx);
|
|
Check(decoded_cmpx.opcode == Decoder::Opcode::V_CMPX_NE_U64 &&
|
|
decoded_cmpx.dst.kind == Decoder::OperandKind::ExecLo &&
|
|
decoded_cmpx.src0.value == 0u &&
|
|
decoded_cmpx.src1.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded_cmpx.src1.reg == 7u,
|
|
"decoder rejected captured V_CMPX_NE_U64 fields");
|
|
result = RecompileForTest(cmpx_shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t cmpx_v2_shader[] = {
|
|
0x7dea0480u, // v_cmpx_ne_u64 exec, 0, v[2:3]
|
|
EncodeSopp(0x01),
|
|
};
|
|
Decoder::Instruction decoded_cmpx_v2;
|
|
ShaderRecompiler::Decoder::DecodeInstruction(cmpx_v2_shader, 0u,
|
|
decoded_cmpx_v2);
|
|
Check(decoded_cmpx_v2.opcode == Decoder::Opcode::V_CMPX_NE_U64 &&
|
|
decoded_cmpx_v2.dst.kind == Decoder::OperandKind::ExecLo &&
|
|
decoded_cmpx_v2.src0.value == 0u &&
|
|
decoded_cmpx_v2.src1.kind == Decoder::OperandKind::Vgpr &&
|
|
decoded_cmpx_v2.src1.reg == 2u,
|
|
"decoder rejected reported VOPC V_CMPX_NE_U64 fields");
|
|
result = RecompileForTest(cmpx_v2_shader, options);
|
|
Check((result.decoded_dump.find("V_CMPX_NE_U64 exec_lo, 0, v2") != std::string::npos),
|
|
"reported VOPC V_CMPX_NE_U64 was not present in the decoded dump");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t empty_shader[] = {EncodeSopp(0x01)};
|
|
result = RecompileForTest(empty_shader, options);
|
|
|
|
auto program = std::move(result.program);
|
|
IR::IREmitter ir(program.blocks.front());
|
|
const auto lane = ir.Emit(IR::ValueOpcode::LaneId);
|
|
const auto high = ir.Emit(IR::ValueOpcode::IAdd32, {lane, IR::Value(1u)});
|
|
const auto base =
|
|
ir.Emit(IR::ValueOpcode::CompositeConstructU64, {lane, high});
|
|
const auto masked =
|
|
ir.Emit(IR::ValueOpcode::BitwiseAnd64,
|
|
{base, IR::Value(uint64_t{0xff00ff00ff00ff00ull})});
|
|
const auto sum = ir.Emit(IR::ValueOpcode::IAdd64, {masked, base});
|
|
const auto product = ir.Emit(IR::ValueOpcode::IMul64, {sum, base});
|
|
const auto shifted =
|
|
ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {product, lane});
|
|
ir.Emit(IR::ValueOpcode::IEqual64, {shifted, base});
|
|
ir.Emit(IR::ValueOpcode::INotEqual64, {shifted, base});
|
|
ir.Emit(IR::ValueOpcode::ULessThan64, {shifted, base});
|
|
ir.Emit(IR::ValueOpcode::SLessThan64, {shifted, base});
|
|
ir.Emit(IR::ValueOpcode::BitCount64, {shifted});
|
|
for (const uint32_t count : {1u, 31u, 32u, 33u, 63u}) {
|
|
ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {base, IR::Value(count)});
|
|
ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {base, IR::Value(count)});
|
|
ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {base, IR::Value(count)});
|
|
}
|
|
|
|
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
|
|
CheckSpirvBinaryValidates(spirv);
|
|
const auto source = DisassembleSpirvBinary(spirv);
|
|
Check((source.find("OpCapability Int64") == std::string::npos) &&
|
|
(source.find("OpTypeInt 64") == std::string::npos),
|
|
"portable pair-U64 translation introduced native shader Int64");
|
|
Check(SpirvInstructionOpcodeCount(spirv, 149u) == 1u,
|
|
"pair-U64 addition did not use exactly one carry instruction");
|
|
Check(SpirvInstructionOpcodeCount(spirv, 154u) == 1u &&
|
|
SpirvInstructionOpcodeCount(spirv, 155u) == 1u,
|
|
"pair-U64 equality did not reduce its vector comparison with Any/All");
|
|
const auto direct_metrics = MeasureSpirv(spirv);
|
|
Check(
|
|
direct_metrics.words <= 940u && direct_metrics.instructions <= 199u &&
|
|
direct_metrics.type_vectors == 2u && direct_metrics.phis == 0u &&
|
|
direct_metrics.function_variables == 0u,
|
|
"portable pair-U64 translation exceeded its declaration/code-size ratchet");
|
|
|
|
const uint32_t dispatcher_shader[] = {
|
|
EncodeSopp(0x05, 2), EncodeSopp(0x02, 0), EncodeSopp(0x05, 0xfffeu),
|
|
EncodeSopp(0x02, 0xfffeu), EncodeSopp(0x01),
|
|
};
|
|
result = RecompileForTest(dispatcher_shader, options);
|
|
Check(result.program.dispatcher_fallback &&
|
|
result.program.blocks.size() >= 3u,
|
|
"U64 spill fixture did not select dispatcher mode");
|
|
program = std::move(result.program);
|
|
IR::IREmitter definition(program.blocks[1]);
|
|
const auto vector = definition.Emit(IR::ValueOpcode::CompositeConstructU64,
|
|
{IR::Value(1u), IR::Value(2u)});
|
|
IR::IREmitter use(program.blocks[2]);
|
|
use.Emit(IR::ValueOpcode::CompositeExtractU64, {vector, IR::Value(1u)});
|
|
|
|
spirv.clear();
|
|
spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
|
|
CheckSpirvBinaryValidates(spirv);
|
|
const auto before = MeasureSpirv(result.spirv);
|
|
const auto after = MeasureSpirv(spirv);
|
|
Check(after.function_variables == before.function_variables + 1u &&
|
|
after.loads == before.loads + 1u &&
|
|
after.stores == before.stores + 1u,
|
|
"dispatcher did not use one canonical U64 vector spill slot");
|
|
}
|
|
|
|
void TestComputeDispatchWaveSize() {
|
|
Check(Pm4::ComputeWaveSize(0x00000041u) == 64u,
|
|
"dispatch without CS_W32_EN did not select wave64");
|
|
Check(Pm4::ComputeWaveSize(0x00008041u) == 32u,
|
|
"dispatch with CS_W32_EN did not select wave32");
|
|
}
|
|
|
|
void TestNewShaderRecompilerBufferLoadsGuardedByExec() {
|
|
const uint32_t shader[] = {
|
|
EncodeMubuf0(0x0c),
|
|
EncodeMubuf1(0, 0, 1), // buffer_load_dword
|
|
// v0
|
|
EncodeMubuf0(0x1c, 0, false),
|
|
EncodeMubuf1(0, 12, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("BUFFER_LOAD_DWORD") != std::string::npos),
|
|
"buffer load guard regression did not decode MUBUF load");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
const auto exec_branch =
|
|
source.find("OpBranchConditional", 0);
|
|
const auto array_length =
|
|
source.find("OpArrayLength", 0);
|
|
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
|
const auto element_access = source.find("OpAccessChain %_ptr_StorageBuffer_uint", 0);
|
|
Check(exec_branch != std::string::npos,
|
|
"buffer load SPIR-V lacks EXEC guard branch");
|
|
Check(array_length != std::string::npos,
|
|
"buffer load SPIR-V lacks storage buffer array-length bounds check");
|
|
Check(bounds_branch != std::string::npos,
|
|
"buffer load SPIR-V lacks storage buffer bounds branch");
|
|
Check(element_access != std::string::npos,
|
|
"buffer load SPIR-V lacks storage element access");
|
|
Check(exec_branch < array_length,
|
|
"buffer load bounds check was emitted outside EXEC guard");
|
|
Check(bounds_branch < element_access,
|
|
"buffer load storage element pointer was formed before bounds guard");
|
|
}
|
|
|
|
void TestNewShaderRecompilerBufferAtomicsGuardedByBounds() {
|
|
struct Case {
|
|
std::array<uint32_t, 3> shader;
|
|
const char *decoded;
|
|
const char *ir;
|
|
const char *spirv;
|
|
};
|
|
const Case cases[] = {
|
|
{{EncodeMubuf0(0x32, 0, true, true), EncodeMubuf1(0, 0, 1),
|
|
EncodeSopp(0x01)},
|
|
"BUFFER_ATOMIC_ADD", "BufferAtomicIAdd32", "OpAtomicIAdd"},
|
|
{{0xe0c46000u, 0x80080104u, EncodeSopp(0x01)},
|
|
"BUFFER_ATOMIC_CMPSWAP", "BufferAtomicCmpSwap32",
|
|
"OpAtomicCompareExchange"},
|
|
};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
for (const auto &test : cases) {
|
|
const auto result = RecompileForTest(test.shader, options);
|
|
Check((result.decoded_dump.find(test.decoded) != std::string::npos),
|
|
"buffer atomic bounds regression did not decode MUBUF atomic");
|
|
Check((result.ir_dump.find(test.ir) != std::string::npos),
|
|
"buffer atomic did not lower to its native IR opcode");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
const auto array_length =
|
|
source.find("OpArrayLength", 0);
|
|
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
|
const auto atomic = source.find(test.spirv, 0);
|
|
const auto memory_barrier =
|
|
source.find("OpMemoryBarrier", atomic);
|
|
Check(array_length != std::string::npos &&
|
|
bounds_branch != std::string::npos &&
|
|
atomic != std::string::npos &&
|
|
memory_barrier != std::string::npos,
|
|
"buffer atomic SPIR-V lacks its bounds guard, operation, or barrier");
|
|
Check(bounds_branch < atomic && atomic < memory_barrier,
|
|
"buffer atomic bounds guard, operation, and barrier are misordered");
|
|
}
|
|
}
|
|
|
|
void TestCapturedBufferAtomicsX2() {
|
|
using ShaderRecompiler::IR::ValueOpcode;
|
|
struct Case {
|
|
std::array<uint32_t, 2> words;
|
|
uint32_t data_vgpr;
|
|
uint32_t spirv_opcode;
|
|
const char *decoded_name;
|
|
const char *ir_name;
|
|
const char *spirv_name;
|
|
ValueOpcode ir_opcode;
|
|
};
|
|
const Case cases[] = {
|
|
{{0xe1680018u, 0x80000000u}, 0u, 241u, "BUFFER_ATOMIC_OR_X2",
|
|
"BufferAtomicOr64", "OpAtomicOr", ValueOpcode::BufferAtomicOr64},
|
|
{{0xe1402000u, 0x80000913u}, 9u, 229u, "BUFFER_ATOMIC_SWAP_X2",
|
|
"BufferAtomicSwap64", "OpAtomicExchange", ValueOpcode::BufferAtomicSwap64},
|
|
};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
for (const auto &test : cases) {
|
|
const std::array shader = {test.words[0], test.words[1], EncodeSopp(0x01)};
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find(test.decoded_name) != std::string::npos),
|
|
"captured 64-bit MUBUF atomic decoded incorrectly");
|
|
Check((result.ir_dump.find(test.ir_name) != std::string::npos),
|
|
"64-bit MUBUF atomic did not lower to its native IR opcode");
|
|
Check((result.ir_dump.find("SetVectorRegister") == std::string::npos),
|
|
"GLC=0 64-bit MUBUF atomic unexpectedly returned the old value");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check((source.find("OpCapability Int64Atomics") != std::string::npos),
|
|
"64-bit buffer atomic SPIR-V lacks Int64Atomics capability");
|
|
Check((source.find("ArrayStride 8") != std::string::npos),
|
|
"64-bit buffer atomic storage view does not use eight-byte elements");
|
|
Check(CountSourceOccurrences(source, "Aliased") == 2u,
|
|
"both storage-buffer views must declare that they alias");
|
|
const auto array_length =
|
|
source.find("OpArrayLength", 0);
|
|
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
|
const auto atomic =
|
|
source.find(test.spirv_name, 0);
|
|
const auto memory_barrier =
|
|
source.find("OpMemoryBarrier", atomic);
|
|
Check(array_length != std::string::npos &&
|
|
bounds_branch != std::string::npos &&
|
|
atomic != std::string::npos && bounds_branch < atomic,
|
|
"64-bit buffer atomic was not guarded by storage-buffer bounds");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, test.spirv_opcode) == 1u,
|
|
"64-bit buffer atomic did not lower to one native SPIR-V atomic");
|
|
Check(memory_barrier != std::string::npos && atomic < memory_barrier,
|
|
"64-bit buffer atomic lacks a following device-memory barrier");
|
|
|
|
const std::array glc_shader = {
|
|
test.words[0] | (1u << 14u), test.words[1],
|
|
EncodeMubuf0(0x1d, 32, false),
|
|
EncodeMubuf1(test.data_vgpr, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto glc_result = RecompileForTest(glc_shader, options);
|
|
const ShaderRecompiler::IR::Inst *glc_atomic = nullptr;
|
|
for (const auto *block : glc_result.program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() == test.ir_opcode) glc_atomic = &inst;
|
|
}
|
|
}
|
|
Check(glc_atomic != nullptr && glc_atomic->UseCount() == 2u,
|
|
"GLC=1 64-bit MUBUF atomic did not return both dwords");
|
|
CheckSpirvBinaryValidates(glc_result.spirv);
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerBranchConditionForms() {
|
|
struct Case {
|
|
uint32_t opcode;
|
|
const char *condition;
|
|
};
|
|
const Case cases[] = {
|
|
{0x04, "condition=scc0"},
|
|
{0x08, "condition=execz"},
|
|
{0x07, "condition=vccnz"},
|
|
};
|
|
|
|
for (const auto &c : cases) {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(c.opcode, 1),
|
|
EncodeSMovB32(0, 129),
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find(c.condition) != std::string::npos),
|
|
"branch condition was not preserved");
|
|
Check(SpirvContainsOpcode(result.spirv, 250),
|
|
"branch condition SPIR-V lacks OpBranchConditional");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
if (c.opcode == 0x04) {
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check((source.find("OpGroupNonUniformBallot") == std::string::npos),
|
|
"structured SCC branch retained a host-subgroup reduction");
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerSetpcBranch() {
|
|
const uint32_t shader[] = {
|
|
EncodeSop1(0x1f, 4, 0), // s_getpc_b64 s[4:5]
|
|
EncodeSop2(0x00, 4, 4, 140), // s_add_u32 s4, s4, 12
|
|
EncodeSop1(0x20, 0, 4), // s_setpc_b64 s[4:5]
|
|
EncodeSMovB32(0, 129), 0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("s_setpc_b64") != std::string::npos),
|
|
"S_SETPC_B64 was not decoded");
|
|
Check((result.ir_dump.find("successors=[") != std::string::npos),
|
|
"S_SETPC_B64 did not participate in CFG");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestFusedShaderHandoffPreservesRegisters() {
|
|
using namespace ShaderRecompiler;
|
|
uint32_t front[] = {
|
|
EncodeSMovB32(12, 255), 0x1003u, // three vertices and one primitive
|
|
EncodeSop1(0x20, 0, 6), // merged-stage handoff through s[6:7]
|
|
0xffffffffu, // front shader metadata must not be decoded
|
|
};
|
|
const uint32_t back[] = {
|
|
EncodeSMovB32(124, 12), // s_mov_b32 m0, s12
|
|
EncodeSopp(0x10, 9), // s_sendmsg MSG_GS_ALLOC_REQ
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderVertexInputInfo input{};
|
|
input.mesh.threads_num[0] = 192;
|
|
input.mesh.threads_num[1] = input.mesh.threads_num[2] = 1;
|
|
input.mesh.primitives_per_group = 62;
|
|
input.mesh.vertices_per_group = 64;
|
|
CompileOptions options{};
|
|
options.stage = ShaderType::Mesh;
|
|
options.input_info.vertex = &input;
|
|
options.back_code = back;
|
|
for (const auto handoff: {EncodeSop1(0x20, 0, 6), 0xbefd2106u}) {
|
|
front[2] = handoff; // SETPC or captured SWAPPC with NULL destination
|
|
auto translated = TranslateProgram(front, options);
|
|
uint32_t allocations = 0;
|
|
for (const auto* block: translated.program.blocks) {
|
|
for (const auto& inst: *block) {
|
|
if (inst.GetOpcode() != IR::ValueOpcode::MeshAllocate) {
|
|
continue;
|
|
}
|
|
const auto value = inst.Arg(0).Resolve();
|
|
Check(value.IsImmediate() && value.U32() == 0x1003u,
|
|
"fused back shader lost the front shader's scalar register value");
|
|
allocations++;
|
|
}
|
|
}
|
|
Check(allocations == 1u, "fused shader omitted the back shader allocation");
|
|
}
|
|
}
|
|
|
|
void TestMeshExportStorage() {
|
|
using ShaderRecompiler::IR::PushData;
|
|
const uint32_t front[] = {
|
|
EncodeSMovB32(12, 255), 0x1003u,
|
|
EncodeSop1(0x20, 0, 6), // merged-stage handoff
|
|
};
|
|
const uint32_t back[] = {
|
|
EncodeSMovB32(124, 12), EncodeSopp(0x10, 9), // GS allocation
|
|
EncodeVop1(0x01, 0, 128),
|
|
EncodeVop1(0x01, 9, 13), // retain user s13
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(9, 2, 5), // store using s[8:11] and vertex ID
|
|
EncodeDs0(0x0d), EncodeDs1(0, 0, 0), // retain guest LDS
|
|
EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 0, 0, 0),
|
|
EncodeExp0(0x20, 0xf, false), EncodeExp1(0, 0, 0, 0),
|
|
EncodeExp0(0x21, 0xf, false), EncodeExp1(0, 0, 0, 0),
|
|
EncodeExp0(0x22, 0xf, false), EncodeExp1(0, 0, 0, 0),
|
|
EncodeExp0(0x0d, 0x4, false), EncodeExp1(0, 0, 0, 0), // Layer
|
|
EncodeExp0(0x0e, 0x9, false), EncodeExp1(0, 0, 0, 0), // Clip distances 0, 3
|
|
EncodeExp0(0x0f, 0x6, false), EncodeExp1(0, 0, 0, 0), // Cull distances 1, 2
|
|
EncodeExp0(0x14, 0x1), EncodeExp1(0, 0, 0, 0), // primitive
|
|
EncodeSopp(0x01),
|
|
};
|
|
std::vector<uint32_t> monolithic(std::begin(front), std::begin(front) + 2);
|
|
monolithic.insert(monolithic.end(), std::begin(back), std::end(back));
|
|
ShaderVertexInputInfo input{};
|
|
input.pa_cl_vs_out_cntl = (1u << 21u) | (1u << 18u) | 0x00c0f00fu;
|
|
auto &mesh = input.mesh;
|
|
mesh.threads_num[0] = 192;
|
|
mesh.threads_num[1] = mesh.threads_num[2] = 1;
|
|
mesh.lds_size_dwords = 3840;
|
|
mesh.primitives_per_group = 62;
|
|
mesh.vertices_per_group = 64;
|
|
mesh.max_vertices = 192;
|
|
mesh.max_primitives = 176;
|
|
std::array<uint32_t, 14> user_data{};
|
|
user_data[8] = 0x10000008u;
|
|
user_data[9] = 4u << 16u;
|
|
user_data[10] = 64u;
|
|
user_data[11] = 0x00027000u;
|
|
user_data[13] = 0x3f800000u;
|
|
ShaderRecompiler::CompileOptions options{};
|
|
options.stage = ShaderType::Mesh;
|
|
options.input_info.vertex = &input;
|
|
options.user_data = user_data;
|
|
struct Case {
|
|
uint32_t subgroup_size, push_data_start;
|
|
bool split;
|
|
};
|
|
const Case cases[] = {
|
|
{32u, PushData::MeshDrawDwordCount, true}, {64u, PushData::MeshDrawDwordCount, true},
|
|
{32u, PushData::DwordCount, true}, {64u, PushData::DwordCount, true},
|
|
{32u, PushData::MeshDrawDwordCount, false}, {64u, PushData::MeshDrawDwordCount, false},
|
|
{32u, PushData::DwordCount, false}, {64u, PushData::DwordCount, false},
|
|
};
|
|
for (const auto [subgroup_size, push_data_start, split] : cases) {
|
|
mesh.host_subgroup_size = subgroup_size;
|
|
options.back_code = split ? std::span{back} : std::span<const uint32_t>{};
|
|
const auto result = RecompileForTest(
|
|
split ? std::span{front} : std::span<const uint32_t>{monolithic},
|
|
options, nullptr, nullptr, push_data_start);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto &layout = result.program.bindings;
|
|
Check(layout.UsesPushData() == (push_data_start == PushData::MeshDrawDwordCount) &&
|
|
layout.memory_offset_count == 1,
|
|
"mesh shader did not retain its user-data placement and buffer offset");
|
|
const auto reg = std::ranges::find(layout.user_data_registers, 13u);
|
|
Check(reg != layout.user_data_registers.end(), "mesh shader lost user s13");
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
for (const auto dword :
|
|
{static_cast<uint32_t>(reg - layout.user_data_registers.begin()),
|
|
layout.memory_offset_dword}) {
|
|
const auto operand =
|
|
std::string(layout.UsesPushData() ? "vsharp" : "shader_data") +
|
|
" %uint_0 %uint_" +
|
|
std::to_string(dword + (layout.UsesPushData() ? push_data_start : 0u));
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", operand.c_str()),
|
|
"mesh user SGPR or buffer offset loaded from the wrong storage");
|
|
}
|
|
Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"vsharp %uint_0 %uint_0") &&
|
|
!SpirvSourceHasInstructionUsing(source, "OpAccessChain",
|
|
"vsharp %uint_0 %uint_4294967295"),
|
|
"mesh draw prefix was lost or spilled shader data wrapped into push constants");
|
|
const auto &binary = result.spirv;
|
|
for (const auto builtin : {3u, 4u}) {
|
|
auto expected = builtin == 3u ? std::vector<uint32_t>{0, 3}
|
|
: std::vector<uint32_t>{1, 2};
|
|
if (subgroup_size == 32) {
|
|
expected.push_back(expected[0]);
|
|
expected.push_back(expected[1]);
|
|
std::sort(expected.begin(), expected.end());
|
|
}
|
|
Check(SpirvDecorationValueCount(binary, 11u, builtin) == 1 &&
|
|
SpirvStoredBuiltInElements(binary, builtin) == expected &&
|
|
SpirvContainsCapability(binary, builtin == 3u ? 32u : 33u),
|
|
"mesh distances lost their shared BuiltIn array, plane index or logical lane");
|
|
}
|
|
std::vector<uint32_t> sizes(binary[3]), constants(binary[3]);
|
|
uint32_t shared_bytes = 0, private_bytes = 0;
|
|
for (size_t i = 5; i < binary.size(); i += binary[i] >> 16u) {
|
|
switch (binary[i] & 0xffffu) {
|
|
case 21u: // OpTypeInt
|
|
case 22u: // OpTypeFloat
|
|
sizes[binary[i + 1]] = binary[i + 2] / 8u;
|
|
break;
|
|
case 23u: // OpTypeVector
|
|
sizes[binary[i + 1]] = sizes[binary[i + 2]] * binary[i + 3];
|
|
break;
|
|
case 28u: // OpTypeArray
|
|
sizes[binary[i + 1]] = sizes[binary[i + 2]] * constants[binary[i + 3]];
|
|
break;
|
|
case 32u: // OpTypePointer
|
|
sizes[binary[i + 1]] = sizes[binary[i + 3]];
|
|
break;
|
|
case 43u: // OpConstant
|
|
constants[binary[i + 2]] = binary[i + 3];
|
|
break;
|
|
case 59u: // OpVariable
|
|
if (binary[i + 3] == 4u || binary[i + 3] == 6u) {
|
|
Check(sizes[binary[i + 1]] != 0, "unmeasured mesh staging type");
|
|
(binary[i + 3] == 4u ? shared_bytes : private_bytes) += sizes[binary[i + 1]];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// The Pathless shader 35869c17ce783c88 exceeds the host's 28 KiB budget
|
|
// when its four vertex exports and primitive exports are shared arrays.
|
|
Check(shared_bytes == 3840u * 4u + 192u * 4u + 8u && shared_bytes <= 28672u,
|
|
"mesh staging must retain guest LDS, shared Layer and allocation within the host budget");
|
|
Check(private_bytes == (4u * 16u + 4u + 4u * 4u) * (64u / subgroup_size),
|
|
"mesh vertex and primitive exports lost their separate logical-lane storage");
|
|
}
|
|
}
|
|
|
|
void TestMergedShaderUserDataSnapshot() {
|
|
using namespace ShaderRecompiler;
|
|
const uint32_t front[] = {
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 2, 1), // consume front s[8:11]
|
|
EncodeSop1(0x20, 0, 6), // merged-stage handoff
|
|
};
|
|
const uint32_t back[] = {
|
|
EncodeSmem0(0x02, 8, 0), 125u << 25u, // s_load_dwordx4 s[8:11], s[0:1]
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 2, 1), EncodeSopp(0x01),
|
|
};
|
|
const std::array<uint32_t, 4> first_table = {0x12340000, 0, 64, 0x00027000};
|
|
const std::array<uint32_t, 4> second_table = {0x56780000, 0, 64, 0x00027000};
|
|
HW::VertexShaderInfo regs{};
|
|
regs.es_regs.data_addr = reinterpret_cast<uint64_t>(front);
|
|
regs.gs_regs.data_addr = reinterpret_cast<uint64_t>(back);
|
|
regs.gs_regs.user_data_addr = reinterpret_cast<uint64_t>(first_table.data());
|
|
regs.gs_regs.rsrc1.gs_vgpr_component_count = 3;
|
|
regs.gs_regs.rsrc2.es_vgpr_component_count = 3;
|
|
regs.gs_regs.rsrc2.user_sgpr = 4;
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
regs.gs_user_sgpr.value[i] = 0x10001000u + i;
|
|
}
|
|
const std::array<uint32_t, 4> front_data = {regs.gs_user_sgpr.value[0],
|
|
regs.gs_user_sgpr.value[1], regs.gs_user_sgpr.value[2], regs.gs_user_sgpr.value[3]};
|
|
ShaderMappedData mapped{};
|
|
mapped.type = Prospero::ShaderBinaryType::kGsFront;
|
|
mapped.code_size_bytes = sizeof(front);
|
|
mapped.scratch_size_dwords = 3;
|
|
ShaderMapUserData(regs.es_regs.data_addr, mapped);
|
|
mapped.type = Prospero::ShaderBinaryType::kGsBack;
|
|
mapped.code_size_bytes = sizeof(back);
|
|
mapped.scratch_size_dwords = 7;
|
|
ShaderMapUserData(regs.gs_regs.data_addr, mapped);
|
|
HW::Context context;
|
|
context.SetShaderStages(0x20);
|
|
context.SetMaxOutputPerSubgroup(252);
|
|
context.SetGsMaxVertOut(18);
|
|
context.SetGsOutPrimType(2);
|
|
HW::UserConfig user_config;
|
|
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriList);
|
|
user_config.SetGeControl({14, 14});
|
|
ShaderVertexInputInfo first_input{};
|
|
const auto first = PrepareProgram(regs, context, user_config, first_input);
|
|
regs.gs_regs.user_data_addr = reinterpret_cast<uint64_t>(second_table.data());
|
|
regs.gs_user_sgpr.value[0]++;
|
|
ShaderVertexInputInfo second_input{};
|
|
const auto second = PrepareProgram(regs, context, user_config, second_input);
|
|
Check(first.user_data_count == 12 && second.user_data_count == 12 &&
|
|
std::equal(front_data.begin(), front_data.end(), first.user_data.begin() + 8) &&
|
|
second.user_data[8] == front_data[0] + 1,
|
|
"merged shader parameters did not snapshot ordinary user SGPRs at s8");
|
|
Check(first.hash == second.hash &&
|
|
MakeStageStaticKey(first_input) == MakeStageStaticKey(second_input) &&
|
|
first_input.mesh.scratch_size_dwords == 7,
|
|
"merged shader identity or maximum scratch size changed with dynamic user data");
|
|
CompileOptions options{};
|
|
options.stage = ShaderType::Mesh;
|
|
options.user_data_base = 0;
|
|
options.user_data = std::span(first.user_data).first(first.user_data_count);
|
|
options.input_info.vertex = &first_input;
|
|
options.back_code = first.back_code;
|
|
const auto translated = TranslateProgram(first.code, options);
|
|
const auto &program = translated.program;
|
|
Check(program.info.buffers.size() == 2 && program.srt_reads.size() == 4,
|
|
"merged shader lost front user SGPRs or the back-stage SRT load");
|
|
for (const auto *params : {&first, &second}) {
|
|
const auto user_data = std::span(params->user_data).first(params->user_data_count);
|
|
const IR::SrtRuntime runtime{.user_data = user_data,
|
|
.read_memory = ReadHostTestMemory};
|
|
IR::DescriptorValue front_descriptor, back_descriptor;
|
|
const auto &table = params == &first ? first_table : second_table;
|
|
Check(IR::SrtWalker(program, runtime).EvaluateDescriptor(program.info.buffers[0].source, front_descriptor) &&
|
|
IR::SrtWalker(program, runtime).EvaluateDescriptor(program.info.buffers[1].source, back_descriptor) &&
|
|
std::equal(user_data.begin() + 8, user_data.end(),
|
|
front_descriptor.dwords.begin()) &&
|
|
std::equal(table.begin(), table.end(), back_descriptor.dwords.begin()),
|
|
"merged shader resource plan did not follow the current s0:s1 pointer and s8 data");
|
|
}
|
|
const uint32_t monolithic[] = {
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 2, 1), EncodeSopp(0x01),
|
|
};
|
|
regs.es_regs.data_addr = reinterpret_cast<uint64_t>(monolithic);
|
|
mapped.type = Prospero::ShaderBinaryType::kGs;
|
|
mapped.code_size_bytes = sizeof(monolithic);
|
|
mapped.scratch_size_dwords = 3;
|
|
ShaderMapUserData(regs.es_regs.data_addr, mapped);
|
|
for (const auto back_address : {regs.gs_regs.data_addr, uint64_t{0}}) {
|
|
regs.gs_regs.data_addr = back_address;
|
|
ShaderVertexInputInfo input{};
|
|
const auto params = PrepareProgram(regs, context, user_config, input);
|
|
Check(params.back_code.empty() && params.hash == XXH3_64bits(monolithic, sizeof(monolithic)) &&
|
|
input.mesh.scratch_size_dwords == 3 && params.user_data_count == 12 &&
|
|
params.user_data[0] == 0 && params.user_data[1] == 0 &&
|
|
std::equal(second.user_data.begin() + 8, second.user_data.begin() + second.user_data_count,
|
|
params.user_data.begin() + 8),
|
|
"monolithic NGG shader used stale GS-back state or lost its s8 user data");
|
|
}
|
|
regs.gs_regs.rsrc2.user_sgpr = HW::UserSgprInfo::SGPRS_MAX;
|
|
for (uint32_t i = 4; i < HW::UserSgprInfo::SGPRS_MAX; ++i) {
|
|
regs.gs_user_sgpr.value[i] = 0x10001000u + i;
|
|
}
|
|
ShaderVertexInputInfo full_input{};
|
|
const auto full = PrepareProgram(regs, context, user_config, full_input);
|
|
Check(full.user_data_count == 8u + HW::UserSgprInfo::SGPRS_MAX &&
|
|
std::ranges::all_of(std::span(full.user_data).first(8),
|
|
[](uint32_t word) { return word == 0; }) &&
|
|
std::equal(std::begin(regs.gs_user_sgpr.value), std::end(regs.gs_user_sgpr.value),
|
|
full.user_data.begin() + 8),
|
|
"merged shader did not preserve the full user-SGPR range after its reserved prefix");
|
|
regs.gs_user_sgpr.value[HW::UserSgprInfo::SGPRS_MAX - 1]++;
|
|
Check(full.user_data.back() == 0x10001000u + HW::UserSgprInfo::SGPRS_MAX - 1,
|
|
"merged shader parameters borrowed mutable register storage");
|
|
context.SetMaxOutputPerSubgroup(256);
|
|
context.SetGsMaxVertOut(8);
|
|
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriFan);
|
|
user_config.SetGeControl({32, 32});
|
|
ShaderVertexInputInfo fan_input{};
|
|
PrepareProgram(regs, context, user_config, fan_input);
|
|
Check(fan_input.mesh.primitives_per_group == 30 && fan_input.mesh.vertices_per_group == 32 &&
|
|
fan_input.mesh.max_vertices == 256 && fan_input.mesh.max_primitives == 192,
|
|
"captured triangle-fan GS configuration lost its subgroup assembly limits");
|
|
context.SetShaderStages(0x00482030u);
|
|
context.SetMaxOutputPerSubgroup(32);
|
|
context.SetGsMaxVertOut(32);
|
|
user_config.SetPrimitiveType(Prospero::PrimitiveType::kPointList);
|
|
user_config.SetGeControl({1, 1});
|
|
regs.gs_regs.rsrc1.gs_vgpr_component_count = 0;
|
|
regs.gs_regs.rsrc2.es_vgpr_component_count = 1;
|
|
ShaderVertexInputInfo fast_input{};
|
|
PrepareProgram(regs, context, user_config, fast_input);
|
|
Check(fast_input.mesh.fast_launch && fast_input.mesh.threads_num[0] == 32 &&
|
|
fast_input.mesh.primitives_per_group == 1 && fast_input.mesh.vertices_per_group == 1 &&
|
|
fast_input.mesh.max_vertices == 32 && fast_input.mesh.max_primitives == 32,
|
|
"captured fast-launch shader lost its per-group launch and output limits");
|
|
auto ordinary_input = fast_input;
|
|
ordinary_input.mesh.fast_launch = false;
|
|
Check(MakeStageStaticKey(fast_input) != MakeStageStaticKey(ordinary_input),
|
|
"fast launch shared the ordinary input-assembly shader key");
|
|
|
|
const uint32_t fast_code[] = {
|
|
EncodeSMovB32(124, 255), 0x20020u, // allocate the full 32 vertices and primitives
|
|
EncodeSopp(0x10, 9),
|
|
EncodeVop1(0x01, 0, 256 + 5),
|
|
EncodeVop1(0x01, 1, 256 + 6),
|
|
EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 0, 1),
|
|
EncodeExp0(0x14, 0x1), EncodeExp1(0, 0, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
options.input_info.vertex = &fast_input;
|
|
options.wave_size = fast_input.mesh.wave_size;
|
|
options.back_code = {};
|
|
const auto fast_result = RecompileForTest(fast_code, options);
|
|
CheckSpirvBinaryValidates(fast_result.spirv);
|
|
Check(fast_result.program.memory_info.empty(),
|
|
"compiled fast-launch entry retained an unused index-buffer resource");
|
|
const auto fast_source = DisassembleSpirvBinary(fast_result.spirv);
|
|
Check(SpirvSourceHasInstructionUsing(fast_source, "OpExecutionMode", "OutputVertices 32") &&
|
|
SpirvSourceHasInstructionUsing(fast_source, "OpExecutionMode", "OutputPrimitivesEXT 32"),
|
|
"fast-launch SPIR-V truncated the mesh output capacity");
|
|
|
|
}
|
|
|
|
void TestEmbeddedFetchPreservesSharedScalarLoad() {
|
|
using namespace ShaderRecompiler;
|
|
const uint32_t code[] = {
|
|
EncodeSMovB32(5, 255), 0x47f,
|
|
EncodeSmem0(0x03, 0, 11), (125u << 25u) | 4u, // s_load_dwordx8 s[0:7], s[22:23], 4
|
|
EncodeSop2(0x1e, 9, 0, 132), // s_lshl_b32 s9, s0, 4
|
|
EncodeSop2(0x0e, 9, 9, 255), 0x1f0,
|
|
EncodeSmem0(0x02, 24, 10), 9u << 25u,
|
|
EncodeVop2(0x01, 0, 256 + 8, 5), // vertex/instance index selection
|
|
EncodeMubuf0(0x02), EncodeMubuf1(9, 6, 0), // replaced vertex fetch
|
|
EncodeSop2(0x1e, 9, 5, 132), // another component of the same scalar load
|
|
EncodeSop2(0x0e, 9, 9, 255), 0x1f0,
|
|
EncodeSmem0(0x02, 28, 10), 9u << 25u,
|
|
EncodeMubuf0(0x0c), EncodeMubuf1(12, 7, 1),
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(9, 10, 11, 12), EncodeSopp(0x01),
|
|
};
|
|
std::array<std::array<uint32_t, 4>, 32> buffers{};
|
|
buffers[2] = {0x12340000, 0, 64, 0x00027000};
|
|
buffers[31] = {0x56780000, 0, 64, 0x00027000};
|
|
const std::array<uint32_t, 9> attributes = {0, 0, 0, 0, 0, 0, 2, 0, 0};
|
|
std::array<uint32_t, 16> user_data{};
|
|
const uint64_t tables[] = {reinterpret_cast<uint64_t>(buffers.data()),
|
|
reinterpret_cast<uint64_t>(attributes.data())};
|
|
std::memcpy(user_data.data() + 12, tables, sizeof(tables));
|
|
ShaderVertexInputInfo input{};
|
|
input.fetch_embedded = true;
|
|
input.fetch_buffer_reg = 12;
|
|
input.fetch_attrib_reg = 14;
|
|
input.resources_num = 1;
|
|
input.resources_dst[0].attr_id = 1;
|
|
input.resources[0].fields[1] = 12u << 16u;
|
|
input.resources[0].fields[2] = 1;
|
|
input.resources[0].fields[3] =
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32Float) << 12u) |
|
|
DstSel(4, 5, 6, 7);
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.user_data_base = 8;
|
|
options.user_data = user_data;
|
|
options.input_info.vertex = &input;
|
|
auto result = RecompileForTest(code, options);
|
|
Check(result.program.info.vertex_fetch_components[0] == 3 &&
|
|
result.resources.buffers.size() == 1 &&
|
|
std::equal(buffers[2].begin(), buffers[2].end(),
|
|
result.resources.buffers[0].dwords.begin()),
|
|
"embedded fetch discarded a scalar-load component used by another buffer read");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto key = MakeStageStaticKey(input);
|
|
const auto compile = [&](const ShaderVertexInputInfo &vertex) {
|
|
auto variant_options = options;
|
|
variant_options.input_info.vertex = &vertex;
|
|
return RecompileForTest(code, variant_options).spirv;
|
|
};
|
|
auto layout = input;
|
|
layout.resources_dst[0].register_start = 32;
|
|
layout.resources_dst[0].fetch_index = 1;
|
|
layout.resources[0].UpdateAddress48(0x56780000u);
|
|
layout.resources[0].fields[1] = 52u << 16u;
|
|
layout.resources[0].fields[2] = 128;
|
|
layout.resources[0].fields[3] |= 3u << 28u;
|
|
layout.buffers_num = 1;
|
|
layout.buffers[0] = {.addr = 0x56780000u, .stride = 52, .num_records = 128,
|
|
.fetch_index = 1};
|
|
Check(compile(layout) == result.spirv,
|
|
"pipeline-only vertex layout changed the embedded-fetch shader");
|
|
Check(MakeStageStaticKey(layout) == key,
|
|
"pipeline-only vertex layout fragmented the shader module cache key");
|
|
|
|
auto formatted = input;
|
|
formatted.resources[0].fields[3] =
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32UInt) << 12u) |
|
|
DstSel(4, 5, 6, 7);
|
|
Check(compile(formatted) != result.spirv && MakeStageStaticKey(formatted) != key,
|
|
"vertex input numeric type lost its shader module specialization");
|
|
auto swizzled = input;
|
|
swizzled.resources[0].fields[3] =
|
|
(swizzled.resources[0].fields[3] & ~0xfffu) | DstSel(5, 4, 6, 7);
|
|
Check(compile(swizzled) != result.spirv && MakeStageStaticKey(swizzled) != key,
|
|
"vertex input destination selectors lost their shader module specialization");
|
|
}
|
|
|
|
void TestEmbeddedVertexFormatSwizzle() {
|
|
using namespace ShaderRecompiler;
|
|
using namespace ShaderRecompiler::IR;
|
|
struct Case {
|
|
Prospero::BufferFormat format;
|
|
uint32_t swizzle, opcode, components, source_width;
|
|
std::array<uint32_t, 4> expected;
|
|
Prospero::VertexAttribFormat attribute_format = Prospero::VertexAttribFormat::kInvalid;
|
|
};
|
|
const Case cases[] = {
|
|
{Prospero::BufferFormat::k32_32_32Float, DstSel(4, 5, 6, 7), 3, 4, 3,
|
|
{0x3e800000, 0x3f000000, 0x3f400000, 0x3e800000}},
|
|
{Prospero::BufferFormat::k32_32_32Float, DstSel(4, 5, 6, 1), 3, 4, 3,
|
|
{0x3e800000, 0x3f000000, 0x3f400000, 0x3f800000}},
|
|
{Prospero::BufferFormat::k32_32_32_32UInt, DstSel(7, 6, 5, 0), 3, 4, 4,
|
|
{0x40000000, 0x3f400000, 0x3f000000, 0}},
|
|
{Prospero::BufferFormat::k32_32_32_32UInt, DstSel(7, 0, 0, 0), 0, 1, 4,
|
|
{0x40000000, 0, 0, 0}},
|
|
{Prospero::BufferFormat::k32UInt, DstSel(1, 0, 1, 0), 3, 4, 0,
|
|
{1, 0, 1, 0}},
|
|
{Prospero::BufferFormat::k32Float, DstSel(1, 0, 1, 0), 3, 4, 0,
|
|
{0x3f800000, 0, 0x3f800000, 0}},
|
|
{Prospero::BufferFormat::k32_32_32_32Float, DstSel(7, 6, 5, 4), 0x0e, 4, 4,
|
|
{0x3e800000, 0x3f000000, 0x3f400000, 0x40000000}},
|
|
{Prospero::BufferFormat::k32UInt, DstSel(7, 6, 5, 4), 3, 4, 1,
|
|
{0x3e800000, 0, 0, 1}, Prospero::VertexAttribFormat::k32UInt},
|
|
{Prospero::BufferFormat::k32_32Float, DstSel(7, 6, 5, 4), 3, 4, 2,
|
|
{0x3e800000, 0x3f000000, 0, 0x3f800000}, Prospero::VertexAttribFormat::k32_32Float},
|
|
{Prospero::BufferFormat::k32_32_32Float, DstSel(7, 6, 5, 4), 3, 4, 3,
|
|
{0x3e800000, 0x3f000000, 0x3f400000, 0x3f800000},
|
|
Prospero::VertexAttribFormat::k32_32_32Float},
|
|
{Prospero::BufferFormat::k32_32_32_32Float, DstSel(7, 6, 5, 4), 3, 4, 4,
|
|
{0x3e800000, 0x3f000000, 0x3f400000, 0x40000000},
|
|
Prospero::VertexAttribFormat::k32_32_32_32Float},
|
|
};
|
|
std::array<uint16_t, static_cast<size_t>(AgcDirectResourceType::Last) + 1> offsets;
|
|
offsets.fill(AGC_ILLEGAL_DIRECT_OFFSET);
|
|
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 0;
|
|
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexAttribDescTable)] = 2;
|
|
ShaderUserData user_data{};
|
|
user_data.direct_resource_offset = offsets.data();
|
|
user_data.direct_resource_count = static_cast<uint16_t>(offsets.size());
|
|
for (const auto &test : cases) {
|
|
const uint32_t code[] = {EncodeMubuf0(test.opcode), EncodeMubuf1(9, 0, 0),
|
|
EncodeExp0(0x20, (1u << test.components) - 1u),
|
|
EncodeExp1(9, 10, 11, 12), EncodeSopp(0x01)};
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(code, decoded);
|
|
const uint32_t attribute = static_cast<uint32_t>(test.attribute_format) << 5u;
|
|
uint32_t buffer[] = {0x10000000u, 16u << 16u, 1u,
|
|
(static_cast<uint32_t>(test.format) << 12u) | test.swizzle};
|
|
HW::VertexShaderInfo regs{};
|
|
regs.es_regs.data_addr = reinterpret_cast<uint64_t>(code);
|
|
regs.gs_regs.rsrc2.user_sgpr = 4;
|
|
const uint64_t tables[] = {reinterpret_cast<uint64_t>(buffer),
|
|
reinterpret_cast<uint64_t>(&attribute)};
|
|
std::memcpy(regs.gs_user_sgpr.value, tables, sizeof(tables));
|
|
ShaderSemantic semantic{};
|
|
semantic.hardware_mapping = 9;
|
|
semantic.size_in_elements = test.components;
|
|
ShaderMappedData mapped{};
|
|
mapped.user_data = &user_data;
|
|
mapped.input_semantics = &semantic;
|
|
mapped.num_input_semantics = 1;
|
|
mapped.code_size_bytes = sizeof(code);
|
|
ShaderMapUserData(regs.es_regs.data_addr, mapped);
|
|
ShaderVertexInputInfo input{};
|
|
PrepareProgram(regs, HW::Context{}, HW::UserConfig{}, input);
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
if (&test == cases) {
|
|
for (const auto [word, flag] : {std::pair{1u, 1u << 31u}, std::pair{3u, 1u << 23u}}) {
|
|
buffer[word] |= flag;
|
|
ExpectFatal([&] { PrepareProgram(regs, HW::Context{}, HW::UserConfig{}, input); },
|
|
"unsupported vertex fetch flags bypassed acquisition validation");
|
|
buffer[word] &= ~flag;
|
|
}
|
|
}
|
|
#endif
|
|
Frontend::EmbeddedFetchPlan fetch;
|
|
fetch.loads.push_back({.pc = 0, .attrib_id = 0, .components = test.components});
|
|
Frontend::TranslateOptions options{};
|
|
options.stage = ShaderType::Vertex;
|
|
options.wave_size = 32;
|
|
options.user_data_count = 0;
|
|
options.input_info.vertex = &input;
|
|
options.embedded_fetch = &fetch;
|
|
auto program = Frontend::TranslateProgram(decoded, CFG::BuildGraph(decoded), options);
|
|
Check(program.info.vertex_fetch_components[0] == test.source_width,
|
|
"vertex fetch width follows destination VGPR count instead of selected source channels");
|
|
const uint32_t source[] = {0x3e800000, 0x3f000000, 0x3f400000, 0x40000000};
|
|
for (auto *block : program.blocks) {
|
|
for (auto &inst : *block) {
|
|
if (inst.GetOpcode() == ValueOpcode::GetAttribute) {
|
|
Check(inst.Arg(0).U32() == 0 && inst.Arg(1).U32() < 4,
|
|
"vertex fetch selected an invalid attribute channel");
|
|
inst.ReplaceUsesWith(Value(source[inst.Arg(1).U32()]));
|
|
}
|
|
}
|
|
}
|
|
RewriteToSsa(program.blocks);
|
|
RemoveIdentities(program.blocks);
|
|
ConstantPropagationPass(program.blocks);
|
|
uint32_t exports = 0;
|
|
for (const auto *block : program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() != ValueOpcode::SetAttribute) continue;
|
|
++exports;
|
|
const auto *value = inst.Arg(0).ResolveInstruction();
|
|
Check(value != nullptr && value->GetOpcode() == ValueOpcode::CompositeConstructU32x4,
|
|
"vertex fetch export did not retain its four components");
|
|
for (uint32_t component = 0; component < 4; ++component) {
|
|
const auto actual = value->Arg(component).Resolve();
|
|
Check(actual.IsImmediate() && actual.U32() == test.expected[component],
|
|
"formatted vertex fetch did not apply the source-channel swizzle");
|
|
}
|
|
}
|
|
}
|
|
Check(exports == 1, "vertex fetch test did not reach its export");
|
|
}
|
|
}
|
|
|
|
void TestMeshInputAssembly() {
|
|
using namespace ShaderRecompiler;
|
|
using namespace ShaderRecompiler::IR;
|
|
struct Case {
|
|
Prospero::PrimitiveType topology;
|
|
uint32_t capacity, count, group, lane, width, address_low, base_vertex;
|
|
uint32_t wave_info, first, second, third, byte_offset, vertex_id;
|
|
bool fetch;
|
|
uint32_t wave_size = 64;
|
|
bool fast_launch = false;
|
|
uint32_t threads = 256;
|
|
};
|
|
std::vector<Case> cases = {
|
|
{Prospero::PrimitiveType::kTriList, 14, 177, 14, 2, 2, 0x1002, UINT32_MAX,
|
|
0x40000309, 6, 7, 8, 340, 0xabcc, true},
|
|
{Prospero::PrimitiveType::kTriList, 14, 177, 14, 9, 2, 0x1002, 0,
|
|
0x40000309, 27, 28, 29, 356, 0, false},
|
|
{Prospero::PrimitiveType::kTriList, 8, 180, 0, 64, 2, 0x1002, 0,
|
|
0x41000000, 192, 193, 194, 128, 0, false},
|
|
{Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 1, 0x1000, 5,
|
|
0x40000206, 3, 4, 5, 4, 0xb0, true},
|
|
{Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 4, 0x1000, 5,
|
|
0x40000206, 3, 4, 5, 28, 0xabcd0128, true},
|
|
{Prospero::PrimitiveType::kTriStrip, 5, 8, 1, 1, 0, 0, 11,
|
|
0x40000305, 1, 2, 3, 0, 15, false},
|
|
{Prospero::PrimitiveType::kTriStrip, 5, 8, 0, 1, 0, 0, 11,
|
|
0x40000305, 2, 1, 3, 0, 12, false},
|
|
{Prospero::PrimitiveType::kTriFan, 32, 6, 0, 3, 2, 0x1002, 0,
|
|
0x40000406, 0, 4, 5, 8, 0x0123, true},
|
|
{Prospero::PrimitiveType::kTriFan, 5, 8, 1, 0, 2, 0x1002, 0,
|
|
0x40000305, 0, 1, 2, 0, 0xabcd, true},
|
|
{Prospero::PrimitiveType::kTriFan, 5, 8, 1, 1, 0, 0, 11,
|
|
0x40000305, 0, 2, 3, 0, 15, false},
|
|
{Prospero::PrimitiveType::kTriFan, 5, 7, 1, 3, 2, 0x1002, 11,
|
|
0x40000204, 0, 4, 5, 12, 0xabd8, true},
|
|
{Prospero::PrimitiveType::kTriFan, 5, 7, 1, 4, 2, 0x1002, 0,
|
|
0x40000204, 0, 5, 6, 16, 0, false},
|
|
{Prospero::PrimitiveType::kTriFan, 5, 8, 1, 64, 4, 0x1000, 0,
|
|
0x41000000, 0, 65, 66, 268, 0, false},
|
|
{Prospero::PrimitiveType::kLineList, 5, 17, 3, 0, 0, 0, 11,
|
|
0x40000204, 0, 1, 0, 0, 23, false},
|
|
{Prospero::PrimitiveType::kLineList, 6, 17, 2, 4, 2, 0x1002, 11,
|
|
0x40000205, 8, 9, 0, 32, 0xabd8, true},
|
|
{Prospero::PrimitiveType::kLineList, 6, 17, 2, 5, 2, 0x1002, 0,
|
|
0x40000205, 10, 11, 0, 36, 0, false},
|
|
{Prospero::PrimitiveType::kLineList, 8, 180, 1, 64, 4, 0x1000, 0,
|
|
0x41000000, 128, 129, 0, 288, 0, false},
|
|
{Prospero::PrimitiveType::kPointList, 12, 265, 22, 0, 2, 0x1002, 0,
|
|
0x40000101, 0, 0, 0, 528, 0xabcd, true},
|
|
{Prospero::PrimitiveType::kPointList, 12, 265, 22, 1, 2, 0x1002, 0,
|
|
0x40000101, 1, 0, 0, 532, 0, false},
|
|
{Prospero::PrimitiveType::kPointList, 1, 1, 0, 0, 0, 0, 11,
|
|
0x40000101, 0, 0, 0, 0, 11, false},
|
|
{Prospero::PrimitiveType::kPointList, 12, 265, 1, 1, 4, 0x1000, 5,
|
|
0x40000c0c, 1, 0, 0, 52, 0xabcd0128, true},
|
|
{Prospero::PrimitiveType::kPointList, 12, 265, 1, 64, 4, 0x1000, 0,
|
|
0x41000000, 64, 0, 0, 304, 0, false},
|
|
{Prospero::PrimitiveType::kTriStrip, 40, 40, 0, 32, 0, 0, 11,
|
|
0x81000608, 32, 33, 34, 0, 43, false, 32},
|
|
};
|
|
for (const uint32_t wave_size : {32u, 64u}) {
|
|
for (const uint32_t threads : {wave_size, 2u * wave_size}) {
|
|
for (const uint32_t group : {0u, 1u, 11u}) {
|
|
for (const uint32_t lane : {0u, wave_size - 1u, threads - 1u}) {
|
|
const uint32_t wave_info = ((threads / wave_size) << 28u) |
|
|
((lane / wave_size) << 24u) |
|
|
(lane < wave_size ? 0x101u : 0u);
|
|
const uint32_t base_vertex = group == 11u ? UINT32_MAX : 11u;
|
|
cases.push_back({Prospero::PrimitiveType::kPointList, 1, 12, group, lane, 0, 0,
|
|
base_vertex, wave_info, 0, 0, 0, 0, group + base_vertex, false,
|
|
wave_size, true, threads});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const auto &test : cases) {
|
|
ShaderVertexInputInfo input{};
|
|
auto &mesh = input.mesh;
|
|
mesh.input_primitive = static_cast<uint32_t>(test.topology);
|
|
mesh.wave_size = test.wave_size;
|
|
mesh.fast_launch = test.fast_launch;
|
|
mesh.primitives_per_group = mesh.InputPrimitiveCount(test.capacity);
|
|
mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group);
|
|
mesh.threads_num[0] = test.threads;
|
|
mesh.threads_num[1] = mesh.threads_num[2] = 1;
|
|
Decoder::Program decoded;
|
|
CFG::Graph graph;
|
|
CFG::BasicBlock block;
|
|
block.id = 0;
|
|
block.terminator.kind = CFG::TerminatorKind::Return;
|
|
graph.blocks.push_back(std::move(block));
|
|
graph.entry_block = 0;
|
|
Frontend::TranslateOptions options{};
|
|
options.stage = ShaderType::Mesh;
|
|
options.wave_size = test.wave_size;
|
|
options.user_data_count = 0;
|
|
options.input_info.vertex = &input;
|
|
auto program = Frontend::TranslateProgram(decoded, graph, options);
|
|
const uint32_t draw[] = {test.count, test.base_vertex, 7, test.width,
|
|
test.address_low, 0x12};
|
|
Inst *load = nullptr;
|
|
for (auto &inst : *program.blocks.front()) {
|
|
if (inst.GetOpcode() == ValueOpcode::MeshDrawParameter) {
|
|
inst.ReplaceUsesWith(Value(draw[inst.Arg(0).U32()]));
|
|
} else if (inst.GetOpcode() == ValueOpcode::GetBuiltin) {
|
|
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
|
|
const uint32_t value = kind == StageInputKind::LocalInvocationIndex
|
|
? test.lane
|
|
: inst.Arg(1).U32() == 0 ? test.group : 2;
|
|
inst.ReplaceUsesWith(Value(value));
|
|
} else if (inst.GetOpcode() == ValueOpcode::LoadAddressU32) {
|
|
Check(load == nullptr, "mesh index fetch emitted duplicate loads");
|
|
load = &inst;
|
|
}
|
|
}
|
|
ConstantPropagationPass(program.blocks);
|
|
if (test.fast_launch) {
|
|
Check(load == nullptr && program.memory_info.empty(),
|
|
"fast launch emitted an ordinary input-assembly index resource");
|
|
} else {
|
|
Check(load != nullptr && load->Arg(1).Resolve().U32() == test.byte_offset &&
|
|
load->Arg(3).Resolve().U1() == test.fetch,
|
|
"mesh index fetch address or active-lane predicate is wrong");
|
|
const auto *resource = load->Arg(0).ResolveInstruction();
|
|
Check(resource != nullptr && resource->Arg(0).Resolve().U32() == (test.address_low & ~3u) &&
|
|
resource->Arg(1).Resolve().U32() == 0x12 &&
|
|
program.memory_info[load->Flags<MemoryFlags>().index].kind == ResourceKind::Global,
|
|
"mesh index fetch lost its aligned guest address resource");
|
|
load->ReplaceUsesWith(Value(test.fetch ? 0xabcd0123u : 0u));
|
|
ConstantPropagationPass(program.blocks);
|
|
}
|
|
std::array<uint32_t, 9> vgprs{};
|
|
uint32_t sgpr3 = 0;
|
|
for (const auto &inst : *program.blocks.front()) {
|
|
if (inst.GetOpcode() == ValueOpcode::SetVectorRegister) {
|
|
vgprs[RegIndex(inst.Arg(0).VectorRegister())] = inst.Arg(1).Resolve().U32();
|
|
} else if (inst.GetOpcode() == ValueOpcode::SetScalarRegister) {
|
|
sgpr3 = inst.Arg(1).Resolve().U32();
|
|
}
|
|
}
|
|
Check(sgpr3 == test.wave_info && vgprs[0] == ((test.first << 2) | (test.second << 18)) &&
|
|
vgprs[1] == test.third * 4 && vgprs[5] == test.vertex_id &&
|
|
vgprs[test.fast_launch ? 6 : 8] == 9u,
|
|
"mesh prolog changed input assembly, wave counts, vertex ID, or instance ID");
|
|
}
|
|
}
|
|
|
|
void TestNewShaderRecompilerSetpcJumpTable() {
|
|
const uint32_t shader[] = {
|
|
EncodeSop2(0x07, 0, 0, 129), // s_min_u32 s0, s0, 1
|
|
EncodeSop2(0x1e, 0, 0, 131), // s_lshl_b32 s0, s0, 3
|
|
EncodeSop1(0x1f, 8, 0), // s_getpc_b64 s[8:9]
|
|
EncodeSop2(0x00, 8, 8, 255), // s_add_u32 s8, s8, literal
|
|
0x00000038u,
|
|
EncodeSop2(0x04, 9, 9, 128), // s_addc_u32 s9, s9, 0
|
|
EncodeSmem0(0x01, 4, 4),
|
|
0u, // s_load_dwordx2 s[4:5], s[8:9], s0
|
|
EncodeSopp(0x0c, 0), // s_waitcnt 0
|
|
EncodeSop1(0x1f, 10, 0), // s_getpc_b64 s[10:11]
|
|
EncodeSop2(0x00, 10, 10, 4), // s_add_u32 s10, s10, s4
|
|
EncodeSop2(0x04, 11, 11, 5), // s_addc_u32 s11, s11, s5
|
|
EncodeSop1(0x20, 0, 10), // s_setpc_b64 s[10:11]
|
|
EncodeSMovB32(1, 129), // case 0
|
|
EncodeSopp(0x02, 1),
|
|
EncodeSMovB32(2, 129), // case 1
|
|
EncodeSopp(0x01, 0),
|
|
0x0000000cu,
|
|
0x00000000u, // table: target case 0 relative to pc 0x28
|
|
0x00000014u,
|
|
0x00000000u, // table: target case 1 relative to pc 0x28
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=dispatcher") != std::string::npos),
|
|
"S_SETPC_B64 jump table did not select dispatcher fallback");
|
|
const auto jump = std::find_if(
|
|
result.program.block_info.begin(),
|
|
result.program.block_info.end(), [](const auto &block) {
|
|
return !block.terminator.indirect_targets.empty();
|
|
});
|
|
Check(jump != result.program.block_info.end() &&
|
|
jump->terminator.indirect_targets.size() == 2,
|
|
"S_SETPC_B64 jump table did not retain both targets");
|
|
Check(jump->terminator.indirect_selector_values.size() == 2,
|
|
"S_SETPC_B64 jump table did not retain selector mapping");
|
|
Check(
|
|
(result.ir_dump.find("SLoadDword") == std::string::npos),
|
|
"S_SETPC_B64 jump table load was reflected as a raw scalar buffer load");
|
|
Check(SpirvContainsOpcode(result.spirv, 251),
|
|
"dispatcher SPIR-V lacks OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerPrunesUnreachableSetpcMetadata() {
|
|
const uint32_t shader[] = {
|
|
EncodeSop2(0x07, 0, 0, 129), // s_min_u32 s0, s0, 1
|
|
EncodeSop2(0x1e, 0, 0, 131), // s_lshl_b32 s0, s0, 3
|
|
EncodeSop1(0x1f, 8, 0), // s_getpc_b64 s[8:9]
|
|
EncodeSop2(0x00, 8, 8, 255), // s_add_u32 s8, s8, literal
|
|
0x0000003cu,
|
|
EncodeSop2(0x04, 9, 9, 128), // s_addc_u32 s9, s9, 0
|
|
EncodeSmem0(0x01, 4, 4),
|
|
0u, // reachable s_load_dwordx2 s[4:5]
|
|
EncodeSopp(0x02, 5), // skip the otherwise valid jump-table dispatch
|
|
EncodeSopp(0x0c, 0), // unreachable s_waitcnt 0
|
|
EncodeSop1(0x1f, 10, 0),
|
|
EncodeSop2(0x00, 10, 10, 4),
|
|
EncodeSop2(0x04, 11, 11, 5),
|
|
EncodeSop1(0x20, 0, 10), // unreachable s_setpc_b64 s[10:11]
|
|
EncodeSMovB32(1, 4), // reachable use of the loaded s4
|
|
EncodeSopp(0x02, 1),
|
|
EncodeSMovB32(2, 129),
|
|
EncodeSopp(0x01, 0),
|
|
0x0000000cu,
|
|
0x00000000u,
|
|
0x00000014u,
|
|
0x00000000u,
|
|
};
|
|
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
ShaderRecompiler::CFG::Graph graph;
|
|
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
Check(!graph.irreducible && graph.code_table_load_pcs.empty(),
|
|
"unreachable S_SETPC retained jump-table metadata or dispatcher mode");
|
|
|
|
ShaderRecompiler::IR::Program ir;
|
|
ShaderComputeInputInfo compute{};
|
|
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
|
|
translate_options.stage = ShaderType::Compute;
|
|
translate_options.wave_size = 64u;
|
|
translate_options.input_info.compute = &compute;
|
|
ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
|
|
translate_options);
|
|
size_t scalar_loads = 0;
|
|
for (const auto *block : ir.blocks) {
|
|
scalar_loads += std::ranges::count_if(*block, [](const auto &inst) {
|
|
return inst.GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::LoadAddressU32 &&
|
|
inst.template Flags<ShaderRecompiler::IR::MemoryFlags>().pc ==
|
|
0x18u;
|
|
});
|
|
}
|
|
Check(scalar_loads == 2u,
|
|
"unreachable S_SETPC metadata suppressed a reachable scalar load component");
|
|
}
|
|
|
|
void TestNewShaderRecompilerSetpcDwordJumpTable() {
|
|
const uint32_t shader[] = {
|
|
EncodeSopp(0x02, 1), // skip one unreachable block
|
|
EncodeSMovB32(100, 129), // unreachable
|
|
EncodeSop2(0x07, 106, 0, 130), // s_min_u32 vcc_lo, s0, 2
|
|
EncodeSop2(0x1e, 106, 106, 130), // s_lshl_b32 vcc_lo, vcc_lo, 2
|
|
EncodeSop1(0x1f, 4, 0), // s_getpc_b64 s[4:5]
|
|
EncodeSop2(0x00, 4, 4, 255), // s_add_u32 s4, s4, literal
|
|
0x00000034u,
|
|
EncodeSop2(0x04, 5, 5, 128), // s_addc_u32 s5, s5, 0
|
|
EncodeSmem0(0x00, 6, 2),
|
|
(106u << 25u) | 4u, // s_load_dword s6, s[4:5], vcc_lo offset:4
|
|
EncodeSopp(0x0c, 0), // s_waitcnt 0
|
|
EncodeSop2(0x01, 4, 4, 6), // s_sub_u32 s4, s4, s6
|
|
EncodeSop2(0x05, 5, 5, 128), // s_subb_u32 s5, s5, 0
|
|
EncodeSop1(0x20, 0, 4), // s_setpc_b64 s[4:5]
|
|
EncodeSMovB32(1, 129), // case 0
|
|
EncodeSopp(0x02, 1),
|
|
EncodeSMovB32(2, 129), // case 1
|
|
EncodeSopp(0x01, 0),
|
|
0u,
|
|
0x00000010u,
|
|
0x00000008u,
|
|
0x00000010u, // table at pc 0x44, targets relative backward from pc 0x40
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("mode=dispatcher") != std::string::npos),
|
|
"subtractive S_SETPC_B64 table did not select dispatcher fallback");
|
|
const auto jump = std::find_if(
|
|
result.program.block_info.begin(),
|
|
result.program.block_info.end(), [](const auto &block) {
|
|
return !block.terminator.indirect_targets.empty();
|
|
});
|
|
const auto block_pc = [&](uint32_t id) {
|
|
const auto block =
|
|
std::find_if(result.program.block_info.begin(),
|
|
result.program.block_info.end(),
|
|
[=](const auto &info) { return info.id == id; });
|
|
return block != result.program.block_info.end() ? block->start_pc
|
|
: UINT32_MAX;
|
|
};
|
|
Check(jump != result.program.block_info.end() &&
|
|
jump->terminator.indirect_targets.size() == 2 &&
|
|
jump->terminator.indirect_target_pcs ==
|
|
std::vector<uint32_t>({0x38u, 0x40u}) &&
|
|
jump->terminator.indirect_selector_values ==
|
|
std::vector<uint32_t>({0u, 4u, 8u}) &&
|
|
jump->terminator.indirect_selector_targets.size() == 3 &&
|
|
block_pc(jump->terminator.indirect_selector_targets[0]) == 0x38u &&
|
|
block_pc(jump->terminator.indirect_selector_targets[1]) == 0x40u &&
|
|
block_pc(jump->terminator.indirect_selector_targets[2]) == 0x38u &&
|
|
std::ranges::none_of(
|
|
result.program.block_info,
|
|
[](const auto &info) { return info.start_pc == 0x04u; }),
|
|
"subtractive S_SETPC_B64 table targets or selector mapping changed");
|
|
Check((result.ir_dump.find("SLoadDword") == std::string::npos),
|
|
"subtractive S_SETPC_B64 table load reached normal IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 251),
|
|
"subtractive S_SETPC_B64 dispatcher lacks OpSwitch");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerExpVertexOutputs() {
|
|
const uint32_t shader[] = {
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0
|
|
EncodeExp0(0x20, 0xf), EncodeExp1(4, 5, 6, 7), // PARAM0
|
|
EncodeExp0(0x14, 0x1), EncodeExp1(8, 0, 0, 0), // PRIM
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.decoded_dump.find("target=0x0c") != std::string::npos),
|
|
"POS export was not decoded");
|
|
Check((result.decoded_dump.find("target=0x20") != std::string::npos),
|
|
"PARAM export was not decoded");
|
|
Check((result.decoded_dump.find("target=0x14") != std::string::npos),
|
|
"PRIM export was not decoded");
|
|
Check((result.ir_dump.find("position") != std::string::npos),
|
|
"POS export did not reach IR");
|
|
Check((result.ir_dump.find("parameter") != std::string::npos),
|
|
"PARAM export did not reach IR");
|
|
Check((result.ir_dump.find("primitive") != std::string::npos),
|
|
"PRIM export did not reach IR");
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"vertex export SPIR-V lacks OpStore");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerClipDisabledPosition() {
|
|
const uint32_t shader[] = {
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), 0xbf810000u,
|
|
};
|
|
const auto compile = [&](const ShaderVertexInputInfo &vertex) {
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.input_info.vertex = &vertex;
|
|
return RecompileForTest(shader, options);
|
|
};
|
|
|
|
ShaderVertexInputInfo regular{};
|
|
auto clipped = regular;
|
|
clipped.clip_space = {
|
|
.scale = {640.0f, 360.0f},
|
|
.offset = {640.0f, 360.0f},
|
|
.half_extent = {8192.0f, 8192.0f},
|
|
.enabled = true,
|
|
};
|
|
const auto regular_result = compile(regular);
|
|
const auto clipped_result = compile(clipped);
|
|
CheckSpirvBinaryValidates(clipped_result.spirv);
|
|
Check(SpirvInstructionOpcodeCount(regular_result.spirv, 136u) == 0u &&
|
|
SpirvInstructionOpcodeCount(clipped_result.spirv, 133u) == 2u &&
|
|
SpirvInstructionOpcodeCount(clipped_result.spirv, 129u) == 2u &&
|
|
SpirvInstructionOpcodeCount(clipped_result.spirv, 136u) == 2u &&
|
|
SpirvInstructionOpcodeCount(clipped_result.spirv, 131u) == 2u,
|
|
"clip-disabled position export did not convert both screen coordinates to NDC");
|
|
Check(MakeStageStaticKey(regular) != MakeStageStaticKey(clipped),
|
|
"clip-disabled vertex shader shared the regular shader cache key");
|
|
auto disabled = clipped;
|
|
disabled.clip_space.enabled = false;
|
|
Check(MakeStageStaticKey(regular) == MakeStageStaticKey(disabled),
|
|
"disabled clip-space payload affected the shader cache key");
|
|
auto shifted = clipped;
|
|
shifted.clip_space.offset[0] += 1.0f;
|
|
Check(MakeStageStaticKey(clipped) != MakeStageStaticKey(shifted),
|
|
"clip-disabled viewport transform is absent from the shader cache key");
|
|
}
|
|
|
|
void TestNewShaderRecompilerAuxPositionExports() {
|
|
const auto compile = [](std::span<const uint32_t> shader, uint32_t control) {
|
|
ShaderVertexInputInfo vertex{};
|
|
vertex.pa_cl_vs_out_cntl = control;
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.input_info.vertex = &vertex;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
return result;
|
|
};
|
|
|
|
const uint32_t all_outputs[] = {
|
|
EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3),
|
|
EncodeExp0(0x0d, 0x5, false), EncodeExp1(4, 5, 6, 7),
|
|
EncodeExp0(0x0e, 0xb, false), EncodeExp1(8, 9, 10, 11),
|
|
EncodeExp0(0x0f, 0x6), EncodeExp1(12, 13, 14, 15),
|
|
0xbf810000u,
|
|
};
|
|
const auto all = compile(all_outputs, 0x00e5aa55u);
|
|
CheckSpirvBinaryValidates(all.spirv);
|
|
Check(SpirvStoredBuiltInElements(all.spirv, 0u) ==
|
|
std::vector<uint32_t>({UINT32_MAX}),
|
|
"POS1-POS3 overwrote gl_Position");
|
|
Check(SpirvStoredBuiltInElements(all.spirv, 1u) ==
|
|
std::vector<uint32_t>({UINT32_MAX}) &&
|
|
SpirvStoredBuiltInElements(all.spirv, 9u) ==
|
|
std::vector<uint32_t>({UINT32_MAX}),
|
|
"MISC point-size/layer stores are missing");
|
|
Check(SpirvStoredBuiltInElements(all.spirv, 3u) ==
|
|
std::vector<uint32_t>({0u, 3u, 4u}) &&
|
|
SpirvStoredBuiltInElements(all.spirv, 4u) ==
|
|
std::vector<uint32_t>({0u, 1u, 2u}),
|
|
"partial clip/cull exports or the appended clipping-error plane used the wrong elements");
|
|
Check(SpirvHasDecorationValue(all.spirv, 11u, 1u) &&
|
|
SpirvHasDecorationValue(all.spirv, 11u, 3u) &&
|
|
SpirvHasDecorationValue(all.spirv, 11u, 4u) &&
|
|
SpirvHasDecorationValue(all.spirv, 11u, 9u) &&
|
|
SpirvContainsCapability(all.spirv, 32u) &&
|
|
SpirvContainsCapability(all.spirv, 33u) &&
|
|
SpirvContainsCapability(all.spirv, 69u),
|
|
"auxiliary vertex BuiltIns or capabilities are missing");
|
|
const auto all_source = DisassembleSpirvBinary(all.spirv);
|
|
Check(all.spirv[1] == 0x00010500u &&
|
|
(all_source.find("SPV_EXT_shader_viewport_index_layer") == std::string::npos) &&
|
|
SpirvBuiltInStoreUsesOperation(all.spirv, 9u, 199u, {0x7ffu}),
|
|
"layer export module version or GFX10 layer mask is incorrect");
|
|
const auto positions = std::count_if(
|
|
all.program.info.outputs.begin(), all.program.info.outputs.end(),
|
|
[](const auto &output) {
|
|
return output.kind == ShaderRecompiler::IR::StageOutputKind::Position;
|
|
});
|
|
Check(positions == 1 &&
|
|
std::ranges::find_if(all.program.info.outputs, [](const auto &output) {
|
|
return output.kind == ShaderRecompiler::IR::StageOutputKind::Position;
|
|
})->index == 0,
|
|
"shader info did not reserve Position exclusively for POS0");
|
|
|
|
const uint32_t cc1_only[] = {
|
|
EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3),
|
|
EncodeExp0(0x0d, 0x6), EncodeExp1(4, 5, 6, 7),
|
|
0xbf810000u,
|
|
};
|
|
const auto dense = compile(cc1_only, 0x0080a050u);
|
|
CheckSpirvBinaryValidates(dense.spirv);
|
|
Check(SpirvStoredBuiltInElements(dense.spirv, 0u).size() == 1u &&
|
|
SpirvStoredBuiltInElements(dense.spirv, 3u) ==
|
|
std::vector<uint32_t>({1u, 2u}) &&
|
|
SpirvStoredBuiltInElements(dense.spirv, 4u) ==
|
|
std::vector<uint32_t>({0u}),
|
|
"CCDIST1 was not densely packed into POS1");
|
|
|
|
const uint32_t middle_hole[] = {
|
|
EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3),
|
|
EncodeExp0(0x0d, 0x1, false), EncodeExp1(4, 5, 6, 7),
|
|
EncodeExp0(0x0e, 0x3), EncodeExp1(8, 9, 10, 11),
|
|
0xbf810000u,
|
|
};
|
|
const auto shifted = compile(middle_hole, 0x00a12010u);
|
|
CheckSpirvBinaryValidates(shifted.spirv);
|
|
Check(SpirvStoredBuiltInElements(shifted.spirv, 1u).size() == 1u &&
|
|
SpirvStoredBuiltInElements(shifted.spirv, 3u) ==
|
|
std::vector<uint32_t>({0u, 1u}) &&
|
|
SpirvStoredBuiltInElements(shifted.spirv, 4u) ==
|
|
std::vector<uint32_t>({0u}),
|
|
"CCDIST1 did not shift across a disabled CCDIST0 vector");
|
|
|
|
const uint32_t full_distances[] = {
|
|
EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3),
|
|
EncodeExp0(0x0d, 0xf, false), EncodeExp1(4, 5, 6, 7),
|
|
EncodeExp0(0x0e, 0xf), EncodeExp1(8, 9, 10, 11), 0xbf810000u,
|
|
};
|
|
const auto full = compile(full_distances, 0x00c0f00fu);
|
|
CheckSpirvBinaryValidates(full.spirv);
|
|
Check(SpirvStoredBuiltInElements(full.spirv, 3u) ==
|
|
std::vector<uint32_t>({0u, 1u, 2u, 3u}) &&
|
|
SpirvStoredBuiltInElements(full.spirv, 4u) ==
|
|
std::vector<uint32_t>({0u, 1u, 2u, 3u}),
|
|
"clipping-error plane exceeded eight combined components or replaced a guest distance");
|
|
|
|
const uint32_t unmapped[] = {
|
|
EncodeExp0(0x0d, 0x4, false), EncodeExp1(0, 0, 8, 0),
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3),
|
|
0xbf810000u,
|
|
};
|
|
const auto ignored = compile(unmapped, 0u);
|
|
CheckSpirvBinaryValidates(ignored.spirv);
|
|
Check(ignored.program.info.outputs.size() == 1u &&
|
|
ignored.program.info.outputs[0].kind ==
|
|
ShaderRecompiler::IR::StageOutputKind::Position,
|
|
"unmapped auxiliary position export was not ignored");
|
|
|
|
// R-Type Final 3 packs a dynamic viewport index into POS1.z bits 16..19.
|
|
// The same component can carry an independent render-target layer in bits 0..10.
|
|
for (const uint32_t control : {0x01280000u, 0x012c0000u}) {
|
|
const auto indexed = compile(unmapped, control);
|
|
CheckSpirvBinaryValidates(indexed.spirv);
|
|
Check(indexed.spirv[1] == 0x00010500u &&
|
|
SpirvContainsCapability(indexed.spirv, 70u) &&
|
|
SpirvBuiltInStoreUsesOperation(indexed.spirv, 10u, 203u, {16u, 4u}),
|
|
"viewport export did not store the GFX10 four-bit index");
|
|
const bool layered = (control & (1u << 18u)) != 0;
|
|
Check(SpirvBuiltInStoreUsesOperation(indexed.spirv, 9u, 199u, {0x7ffu}) == layered,
|
|
"packed layer/viewport export dropped or invented a layer store");
|
|
Check(std::ranges::any_of(indexed.program.info.outputs, [](const auto &output) {
|
|
return output.kind == ShaderRecompiler::IR::StageOutputKind::ViewportIndex;
|
|
}),
|
|
"viewport export was omitted from shader reflection");
|
|
}
|
|
|
|
ShaderVertexInputInfo key0{};
|
|
ShaderVertexInputInfo key1{};
|
|
key1.pa_cl_vs_out_cntl = 1;
|
|
Check(MakeStageStaticKey(key0) != MakeStageStaticKey(key1),
|
|
"PA_CL_VS_OUT_CNTL is absent from the vertex shader cache key");
|
|
}
|
|
|
|
void TestTypedSpirvSerialization() {
|
|
// Parameters exercise runtime enums, which cannot narrow implicitly to a
|
|
// uint32_t initializer list when the enum uses the Windows signed ABI.
|
|
const auto check_serialization = [](spv::StorageClass storage_class,
|
|
spv::FunctionControlMask control,
|
|
spv::Decoration decoration,
|
|
GLSLstd450 glsl_opcode,
|
|
int32_t signed_literal,
|
|
uint32_t high_bit_literal) {
|
|
ShaderRecompiler::Spirv::Builder builder;
|
|
builder.RequireCapability(spv::CapabilityShader);
|
|
const auto glsl = builder.Import("GLSL.std.450");
|
|
builder.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
|
|
const auto void_type = builder.Type(spv::OpTypeVoid);
|
|
const std::vector<uint32_t> uint_operands{32u, 0u};
|
|
const auto uint_type = builder.Type(spv::OpTypeInt, uint_operands);
|
|
Check(uint_type == builder.Type(spv::OpTypeInt, 32u, 0u) &&
|
|
uint_type == builder.Type(spv::OpTypeInt,
|
|
std::span<const uint32_t>{uint_operands}),
|
|
"typed, vector and span type operands produced different declarations");
|
|
const auto float_type = builder.Type(spv::OpTypeFloat, 32u);
|
|
const auto vector_type = builder.Type(spv::OpTypeVector, uint_type, 2u);
|
|
const auto pointer_type =
|
|
builder.Type(spv::OpTypePointer, storage_class, uint_type);
|
|
const auto function_type = builder.Type(spv::OpTypeFunction, void_type);
|
|
const auto all_bits =
|
|
builder.Constant(spv::OpConstant, uint_type, signed_literal);
|
|
Check(all_bits == builder.Constant(spv::OpConstant, uint_type, UINT32_MAX),
|
|
"signed and unsigned constant operands produced different declarations");
|
|
const auto high_bit =
|
|
builder.Constant(spv::OpConstant, uint_type, high_bit_literal);
|
|
const auto one = builder.Constant(spv::OpConstant, float_type, 0x3f800000u);
|
|
const std::vector<uint32_t> components{all_bits, high_bit};
|
|
const auto constant_vector = builder.Constant(
|
|
spv::OpConstantComposite, vector_type,
|
|
std::span<const uint32_t>{components});
|
|
Check(constant_vector == builder.Constant(spv::OpConstantComposite,
|
|
vector_type, components),
|
|
"vector and span constant operands produced different declarations");
|
|
const auto variable =
|
|
builder.DefineGlobalVariable(pointer_type, storage_class);
|
|
const auto function = builder.AllocateId();
|
|
const auto label = builder.AllocateId();
|
|
const auto absolute = builder.AllocateId();
|
|
const auto constructed = builder.AllocateId();
|
|
const auto shuffled = builder.AllocateId();
|
|
const auto loaded = builder.AllocateId();
|
|
builder.AddEntryPoint(spv::ExecutionModelGLCompute, function, "main", {});
|
|
builder.AddExecutionMode(function, spv::ExecutionModeLocalSize, 1u, 2u, 3u);
|
|
builder.AddAnnotation(spv::OpDecorate, one, decoration);
|
|
builder.AddFunction(spv::OpFunction, void_type, function, control,
|
|
function_type);
|
|
const std::vector<uint32_t> label_words{248u, label};
|
|
builder.AddFunction(label_words);
|
|
builder.AddFunction(spv::OpExtInst, float_type, absolute, glsl, glsl_opcode,
|
|
one);
|
|
builder.AddFunction(spv::OpCompositeConstruct, vector_type, constructed,
|
|
components);
|
|
const std::array<uint32_t, 2> selectors{1u, 2u};
|
|
builder.AddFunction(spv::OpVectorShuffle, vector_type, shuffled,
|
|
constant_vector, constructed,
|
|
std::span<const uint32_t>{selectors});
|
|
builder.AddFunction(spv::OpLoad, uint_type, loaded, variable,
|
|
spv::MemoryAccessAlignedMask, 4u);
|
|
builder.AddFunction(std::vector<uint32_t>{});
|
|
const std::array<uint32_t, 1> return_words{253u};
|
|
builder.AddFunction(std::span<const uint32_t>{return_words});
|
|
builder.AddFunction(spv::OpFunctionEnd);
|
|
|
|
// Literal headers and enum values keep this independent of the builder's
|
|
// instruction word counts, operand conversion and range flattening.
|
|
const std::vector<uint32_t> expected{
|
|
0x07230203u, 0x00010300u, 0u, loaded + 1u, 0u,
|
|
0x00020011u, 1u, // OpCapability Shader
|
|
0x0006000bu, glsl, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0u,
|
|
0x0003000eu, 0u, 1u, // OpMemoryModel Logical GLSL450
|
|
0x0005000fu, 5u, function, 0x6e69616du, 0u,
|
|
0x00060010u, function, 17u, 1u, 2u, 3u,
|
|
0x00030047u, one, 0u, // OpDecorate RelaxedPrecision
|
|
0x00020013u, void_type,
|
|
0x00040015u, uint_type, 32u, 0u,
|
|
0x00030016u, float_type, 32u,
|
|
0x00040017u, vector_type, uint_type, 2u,
|
|
0x00040020u, pointer_type, 6u, uint_type,
|
|
0x00030021u, function_type, void_type,
|
|
0x0004002bu, uint_type, all_bits, 0xffffffffu,
|
|
0x0004002bu, uint_type, high_bit, 0x80000000u,
|
|
0x0004002bu, float_type, one, 0x3f800000u,
|
|
0x0005002cu, vector_type, constant_vector, all_bits, high_bit,
|
|
0x0004003bu, pointer_type, variable, 6u,
|
|
0x00050036u, void_type, function, 0u, function_type,
|
|
0x000200f8u, label,
|
|
0x0006000cu, float_type, absolute, glsl, 4u, one,
|
|
0x00050050u, vector_type, constructed, all_bits, high_bit,
|
|
0x0007004fu, vector_type, shuffled, constant_vector, constructed, 1u, 2u,
|
|
0x0006003du, uint_type, loaded, variable, 2u, 4u,
|
|
0x000100fdu,
|
|
0x00010038u,
|
|
};
|
|
const auto binary = builder.Build();
|
|
Check(binary == expected,
|
|
"typed SPIR-V serialization changed instruction words or operand order");
|
|
CheckSpirvBinaryValidates(binary);
|
|
};
|
|
check_serialization(spv::StorageClassPrivate, spv::FunctionControlMaskNone,
|
|
spv::DecorationRelaxedPrecision, GLSLstd450FAbs, -1,
|
|
0x80000000u);
|
|
}
|
|
|
|
void TestDeferredSpirvPhiPatching() {
|
|
ShaderRecompiler::Spirv::Builder builder;
|
|
const auto type = builder.Type(spv::OpTypeInt, 32u, 0u);
|
|
const auto zero = builder.Constant(spv::OpConstant, type, 0u);
|
|
const auto one = builder.Constant(spv::OpConstant, type, 1u);
|
|
const auto left = builder.AllocateId();
|
|
const auto right = builder.AllocateId();
|
|
const auto result = builder.AllocateId();
|
|
const auto phi = builder.AddDeferredPhi(type, result, 2);
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
ExpectFatal([&] { (void)builder.Build(); }, "unpatched phi was serialized");
|
|
ExpectFatal([&] { builder.PatchDeferredPhi(phi, 2, one, right); },
|
|
"phi patch accepted an incoming outside its encoded length");
|
|
#endif
|
|
|
|
builder.PatchDeferredPhi(phi, 1, one, right);
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
ExpectFatal([&] { builder.PatchDeferredPhi(phi, 1, zero, left); },
|
|
"phi incoming was patched twice");
|
|
ExpectFatal([&] { (void)builder.Build(); },
|
|
"partially patched phi was serialized");
|
|
#endif
|
|
|
|
// Appending another instruction must not change the first phi's patch
|
|
// location.
|
|
const auto second_result = builder.AllocateId();
|
|
const auto second_phi = builder.AddDeferredPhi(type, second_result, 1);
|
|
builder.PatchDeferredPhi(phi, 0, zero, left);
|
|
builder.PatchDeferredPhi(second_phi, 0, one, right);
|
|
const auto binary = builder.Build();
|
|
uint32_t phis = 0;
|
|
for (size_t offset = 5; offset < binary.size();) {
|
|
const auto words = binary[offset] >> spv::WordCountShift;
|
|
if ((binary[offset] & spv::OpCodeMask) == spv::OpPhi) {
|
|
if (phis++ == 0) {
|
|
Check(words == 7 && binary[offset + 2] == result &&
|
|
binary[offset + 3] == zero && binary[offset + 4] == left &&
|
|
binary[offset + 5] == one && binary[offset + 6] == right,
|
|
"out-of-order phi patches changed incoming values or parents");
|
|
} else {
|
|
Check(words == 5 && binary[offset + 2] == second_result &&
|
|
binary[offset + 3] == one && binary[offset + 4] == right,
|
|
"patching one phi corrupted its neighbor");
|
|
}
|
|
}
|
|
offset += words;
|
|
}
|
|
Check(phis == 2, "deferred phi instructions were lost");
|
|
}
|
|
|
|
void TestDemandDrivenSpirvDeclarations() {
|
|
using ShaderRecompiler::Spirv::Builder;
|
|
|
|
Builder declarations;
|
|
const auto uint_type = declarations.Type(spv::OpTypeInt, 32u, 0u);
|
|
Check(uint_type == declarations.Type(spv::OpTypeInt, 32u, 0u),
|
|
"structural type interning returned different IDs");
|
|
const auto zero = declarations.Constant(spv::OpConstant, uint_type, 0u);
|
|
Check(zero == declarations.Constant(spv::OpConstant, uint_type, 0u),
|
|
"constant interning returned different IDs");
|
|
Check(declarations.Import("GLSL.std.450") ==
|
|
declarations.Import("GLSL.std.450"),
|
|
"extended-instruction import was duplicated");
|
|
declarations.RequireCapability(spv::CapabilityShader);
|
|
declarations.RequireCapability(spv::CapabilityShader);
|
|
const auto plain_array = declarations.Type(spv::OpTypeArray, uint_type, zero);
|
|
const auto decorated_array = declarations.DecoratedType(
|
|
spv::OpTypeArray,
|
|
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}},
|
|
uint_type, zero);
|
|
Check(decorated_array ==
|
|
declarations.DecoratedType(
|
|
spv::OpTypeArray,
|
|
{{spv::OpDecorate,
|
|
{spv::DecorationArrayStride, sizeof(uint32_t)}}},
|
|
uint_type, zero),
|
|
"decorated type interning returned different IDs");
|
|
Check(plain_array != decorated_array,
|
|
"decorated and undecorated aggregate types were aliased");
|
|
Check(declarations.DecoratedType(spv::OpTypeArray, {}, uint_type, zero) ==
|
|
plain_array,
|
|
"empty decorated type request bypassed structural interning");
|
|
const auto ptr = declarations.Type(spv::OpTypePointer,
|
|
spv::StorageClassFunction, uint_type);
|
|
Check(declarations.DefineGlobalVariable(ptr, spv::StorageClassFunction) !=
|
|
declarations.DefineGlobalVariable(ptr, spv::StorageClassFunction),
|
|
"distinct variables were structurally interned");
|
|
const auto declaration_binary = declarations.Build();
|
|
Check(SpirvInstructionOpcodeCount(declaration_binary, 17u) == 1u &&
|
|
SpirvInstructionOpcodeCount(declaration_binary, 11u) == 1u &&
|
|
SpirvInstructionOpcodeCount(declaration_binary, spv::OpTypeInt) ==
|
|
1u &&
|
|
SpirvInstructionOpcodeCount(declaration_binary, spv::OpTypeArray) ==
|
|
2u &&
|
|
SpirvInstructionOpcodeCount(declaration_binary, spv::OpConstant) ==
|
|
1u &&
|
|
SpirvInstructionOpcodeCount(declaration_binary, spv::OpDecorate) ==
|
|
1u &&
|
|
SpirvInstructionOpcodeCount(declaration_binary, 59u) == 2u,
|
|
"canonical builder emitted duplicate declarations");
|
|
|
|
const auto compile = [](std::span<const uint32_t> shader,
|
|
Prospero::BufferFormat format) {
|
|
auto user_data = ImageTestUserData();
|
|
SetImageTestFormat(&user_data, 0, format);
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.user_data = user_data;
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
return result.spirv;
|
|
};
|
|
|
|
const uint32_t query[] = {EncodeMimg0(0x60, 0x3), EncodeMimg1(6, 0, 0, 1),
|
|
EncodeExp0(0x00, 0x3), EncodeExp1(6, 7, 0, 0),
|
|
EncodeSopp(0x01)};
|
|
auto query_user_data = ImageTestUserData();
|
|
auto query_pixel_info = RegressionPixelInputInfo();
|
|
auto query_options = MakeCompileOptions(ShaderType::Pixel);
|
|
query_options.input_info.pixel = &query_pixel_info;
|
|
query_options.user_data = query_user_data;
|
|
auto query_result = RecompileForTest(query, query_options);
|
|
CheckSpirvBinaryValidates(query_result.spirv);
|
|
const auto &query_spirv = query_result.spirv;
|
|
Check(SpirvInstructionOpcodeCount(query_spirv, 105u) == 1u,
|
|
"image query fixture did not reach OpImageQueryLod");
|
|
const auto query_metrics = MeasureSpirv(query_spirv);
|
|
Check(query_metrics.type_images == 1u && query_metrics.type_samplers == 1u &&
|
|
query_metrics.type_sampled_images == 1u &&
|
|
query_metrics.image_query_capabilities == 1u &&
|
|
query_metrics.sampled_1d_capabilities == 0u &&
|
|
query_metrics.image_1d_capabilities == 0u,
|
|
"image query emitted unrelated or duplicate declarations");
|
|
|
|
const uint32_t store[] = {EncodeMimg0(0x08, 0xf), EncodeMimg1(0, 0, 0, 20),
|
|
EncodeSopp(0x01)};
|
|
const auto store_spirv = compile(store, Prospero::BufferFormat::k8UNorm);
|
|
Check(SpirvInstructionOpcodeCount(store_spirv, 99u) == 1u,
|
|
"storage image fixture did not reach OpImageWrite");
|
|
const auto store_metrics = MeasureSpirv(store_spirv);
|
|
Check(store_metrics.type_images == 1u && store_metrics.type_samplers == 0u &&
|
|
store_metrics.type_sampled_images == 0u &&
|
|
store_metrics.storage_read_without_format_capabilities == 0u &&
|
|
store_metrics.storage_write_without_format_capabilities == 1u,
|
|
"storage write emitted unrelated or duplicate declarations");
|
|
|
|
const uint32_t atomic[] = {EncodeMimg0(0x11, 0x1, true),
|
|
EncodeMimg1(0, 0, 0, 20), EncodeSopp(0x01)};
|
|
const auto atomic_spirv = compile(atomic, Prospero::BufferFormat::k32UInt);
|
|
Check(SpirvInstructionOpcodeCount(atomic_spirv, 60u) == 1u,
|
|
"image atomic fixture did not reach OpImageTexelPointer");
|
|
const auto atomic_metrics = MeasureSpirv(atomic_spirv);
|
|
Check(atomic_metrics.type_images == 1u &&
|
|
atomic_metrics.type_samplers == 0u &&
|
|
atomic_metrics.type_sampled_images == 0u &&
|
|
atomic_metrics.image_pointers == 1u &&
|
|
atomic_metrics.image_texel_pointers == 1u &&
|
|
atomic_metrics.storage_read_without_format_capabilities == 0u &&
|
|
atomic_metrics.storage_write_without_format_capabilities == 0u,
|
|
"image atomic emitted unrelated or duplicate declarations");
|
|
|
|
const uint32_t glsl[] = {EncodeVop1(0x24, 0, 1), EncodeVop1(0x24, 2, 3),
|
|
EncodeExp0(0x00, 0x3), EncodeExp1(0, 2, 0, 0),
|
|
EncodeSopp(0x01)};
|
|
auto glsl_pixel_info = RegressionPixelInputInfo();
|
|
auto glsl_options = MakeCompileOptions(ShaderType::Pixel);
|
|
glsl_options.input_info.pixel = &glsl_pixel_info;
|
|
auto glsl_result = RecompileForTest(glsl, glsl_options);
|
|
CheckSpirvBinaryValidates(glsl_result.spirv);
|
|
Check(MeasureSpirv(glsl_result.spirv).ext_inst_imports == 1u,
|
|
"multiple GLSL operations emitted duplicate imports");
|
|
}
|
|
|
|
void TestTypedEntryStateIsMinimal() {
|
|
using namespace ShaderRecompiler;
|
|
|
|
const auto check = [](uint32_t wave_size) {
|
|
Decoder::Program decoded;
|
|
CFG::Graph graph;
|
|
CFG::BasicBlock source_block;
|
|
source_block.id = 0;
|
|
source_block.terminator.kind = CFG::TerminatorKind::Return;
|
|
graph.blocks.push_back(std::move(source_block));
|
|
graph.entry_block = 0;
|
|
IR::Program values;
|
|
ShaderComputeInputInfo compute{};
|
|
Frontend::TranslateOptions translate_options{};
|
|
translate_options.stage = ShaderType::Compute;
|
|
translate_options.wave_size = wave_size;
|
|
translate_options.user_data_count = 0;
|
|
translate_options.input_info.compute = &compute;
|
|
values = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
|
|
translate_options);
|
|
|
|
uint32_t set_exec = 0;
|
|
uint32_t set_exec_lo = 0;
|
|
uint32_t set_exec_hi = 0;
|
|
uint32_t ballots = 0;
|
|
const IR::Inst* ballot = nullptr;
|
|
IR::Value exec;
|
|
IR::Value exec_lo;
|
|
IR::Value exec_hi;
|
|
for (const auto *block : values.blocks) {
|
|
for (const auto &inst : *block) {
|
|
switch (inst.GetOpcode()) {
|
|
case IR::ValueOpcode::Ballot:
|
|
ballots++;
|
|
ballot = &inst;
|
|
break;
|
|
case IR::ValueOpcode::SetExec:
|
|
set_exec++;
|
|
exec = inst.Arg(0).Resolve();
|
|
break;
|
|
case IR::ValueOpcode::SetExecLo:
|
|
set_exec_lo++;
|
|
exec_lo = inst.Arg(0).Resolve();
|
|
break;
|
|
case IR::ValueOpcode::SetExecHi:
|
|
set_exec_hi++;
|
|
exec_hi = inst.Arg(0).Resolve();
|
|
break;
|
|
case IR::ValueOpcode::SetScalarRegister:
|
|
case IR::ValueOpcode::SetThreadBitScalarRegister:
|
|
case IR::ValueOpcode::SetScalarMaskTag:
|
|
case IR::ValueOpcode::SetVectorRegister:
|
|
case IR::ValueOpcode::SetScc:
|
|
case IR::ValueOpcode::SetVcc:
|
|
case IR::ValueOpcode::SetVccLo:
|
|
case IR::ValueOpcode::SetVccHi:
|
|
case IR::ValueOpcode::SetM0:
|
|
Check(false, "minimal typed entry contains redundant state writes");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const auto is_mask_word = [&](IR::Value value, uint32_t word) {
|
|
const auto* extract = value.ResolveInstruction();
|
|
return extract != nullptr &&
|
|
extract->GetOpcode() == IR::ValueOpcode::CompositeExtractU32x4 &&
|
|
extract->Arg(0).ResolveInstruction() == ballot &&
|
|
extract->Arg(1).IsImmediate() && extract->Arg(1).U32() == word;
|
|
};
|
|
Check(set_exec == 1u && set_exec_lo == 1u && set_exec_hi == 1u &&
|
|
ballots == 1u && exec.IsImmediate() && exec.U1() &&
|
|
ballot->Arg(0) == exec && is_mask_word(exec_lo, 0u) &&
|
|
(wave_size == 64u ? is_mask_word(exec_hi, 1u)
|
|
: exec_hi.IsImmediate() && exec_hi.U32() == 0u),
|
|
"typed entry did not materialize the active invocation mask once");
|
|
|
|
IR::RewriteToSsa(values.blocks);
|
|
IR::RemoveIdentities(values.blocks);
|
|
IR::EliminateDeadCode(values.blocks);
|
|
IR::ValidateProgram(values, true);
|
|
};
|
|
|
|
check(32u);
|
|
check(64u);
|
|
}
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
void TestFinalSsaRejectsRegisterStatePseudos() {
|
|
using namespace ShaderRecompiler::IR;
|
|
|
|
const auto check_rejected = [](bool setter) {
|
|
Program program;
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.emplace_back();
|
|
IREmitter ir(program.blocks.front());
|
|
if (setter) {
|
|
ir.SetScalarReg(ScalarReg{0}, U32(Value(1u)));
|
|
} else {
|
|
ir.Emit(ValueOpcode::ReferenceU32, {ir.GetScalarReg(ScalarReg{0})});
|
|
}
|
|
ExpectFatal([&] { ValidateProgram(program, true); },
|
|
"final SSA accepted a register-state pseudo operation");
|
|
};
|
|
|
|
check_rejected(false);
|
|
check_rejected(true);
|
|
}
|
|
#endif
|
|
|
|
void TestValuePhiValidation() {
|
|
using namespace ShaderRecompiler::IR;
|
|
|
|
enum class InvalidPhi {
|
|
None,
|
|
Empty,
|
|
MissingParent,
|
|
DuplicateParent,
|
|
NonPredecessorParent,
|
|
WrongType,
|
|
AfterInstruction,
|
|
};
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
const auto expect_invalid = [](const Program &program, const char *text) {
|
|
ExpectFatal([&] { ValidateProgram(program, true); }, text);
|
|
};
|
|
#endif
|
|
const auto validate = [&](InvalidPhi invalid) {
|
|
Program program;
|
|
const auto add_block = [&](uint32_t id) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
auto *block = program.block_storage.back().get();
|
|
program.blocks.push_back(block);
|
|
program.block_info.push_back({.id = id});
|
|
return block;
|
|
};
|
|
|
|
auto *entry = add_block(0);
|
|
auto *left = add_block(1);
|
|
auto *right = add_block(2);
|
|
auto *join = add_block(3);
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
program.block_info[0].terminator.false_block = 2;
|
|
program.block_info[0].condition = Value(true);
|
|
program.block_info[1].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[1].terminator.true_block = 3;
|
|
program.block_info[2].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[2].terminator.true_block = 3;
|
|
entry->AddBranch(left);
|
|
entry->AddBranch(right);
|
|
left->AddBranch(join);
|
|
right->AddBranch(join);
|
|
|
|
if (invalid == InvalidPhi::AfterInstruction) {
|
|
join->AppendNewInst(ValueOpcode::IAdd32, {Value(1u), Value(2u)});
|
|
}
|
|
auto &phi = join->AppendNewInst(ValueOpcode::Phi);
|
|
phi.SetFlags(Type::U32);
|
|
if (invalid != InvalidPhi::Empty) {
|
|
phi.AddPhiOperand(
|
|
invalid == InvalidPhi::NonPredecessorParent ? entry : left,
|
|
invalid == InvalidPhi::WrongType ? Value(true) : Value(7u));
|
|
}
|
|
if (invalid != InvalidPhi::Empty && invalid != InvalidPhi::MissingParent &&
|
|
invalid != InvalidPhi::WrongType) {
|
|
phi.AddPhiOperand(invalid == InvalidPhi::DuplicateParent ? left : right,
|
|
Value(9u));
|
|
} else if (invalid == InvalidPhi::WrongType) {
|
|
phi.AddPhiOperand(right, Value(9u));
|
|
}
|
|
|
|
if (invalid == InvalidPhi::None) {
|
|
ValidateProgram(program, true);
|
|
}
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
else {
|
|
expect_invalid(program,
|
|
"malformed Phi did not terminate IR validation");
|
|
}
|
|
#endif
|
|
};
|
|
|
|
validate(InvalidPhi::None);
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
validate(InvalidPhi::Empty);
|
|
validate(InvalidPhi::MissingParent);
|
|
validate(InvalidPhi::DuplicateParent);
|
|
validate(InvalidPhi::NonPredecessorParent);
|
|
validate(InvalidPhi::WrongType);
|
|
validate(InvalidPhi::AfterInstruction);
|
|
|
|
const auto validate_cfg = [&](bool duplicate_id) {
|
|
Program program;
|
|
for (uint32_t id = 0; id < 2; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
auto *block = program.block_storage.back().get();
|
|
program.blocks.push_back(block);
|
|
program.block_info.push_back({.id = duplicate_id ? 0u : id});
|
|
}
|
|
if (!duplicate_id) {
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
}
|
|
expect_invalid(program,
|
|
"malformed Value CFG did not terminate IR validation");
|
|
};
|
|
validate_cfg(true);
|
|
validate_cfg(false);
|
|
|
|
{
|
|
Program program;
|
|
for (uint32_t id = 0; id < 2; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
program.block_info[1].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[1].terminator.true_block = 0;
|
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
|
program.blocks[1]->AddBranch(program.blocks[0]);
|
|
expect_invalid(program,
|
|
"entry predecessor did not terminate IR validation");
|
|
}
|
|
{
|
|
Program program;
|
|
for (uint32_t id = 0; id < 3; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
auto &indirect = program.block_info[1];
|
|
indirect.terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::IndirectBranch;
|
|
indirect.terminator.indirect_targets = {1};
|
|
indirect.terminator.indirect_selector_values = {0};
|
|
indirect.terminator.indirect_selector_targets = {2};
|
|
indirect.indirect_target = Value(0u);
|
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
|
program.blocks[1]->AddBranch(program.blocks[1]);
|
|
expect_invalid(program,
|
|
"non-successor indirect selector did not terminate IR "
|
|
"validation");
|
|
}
|
|
{
|
|
Program program;
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = UINT32_MAX});
|
|
expect_invalid(program,
|
|
"reserved exit block id did not terminate IR validation");
|
|
}
|
|
{
|
|
Program program;
|
|
for (uint32_t id = 0; id < 2; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
auto &indirect = program.block_info[1];
|
|
indirect.terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::IndirectBranch;
|
|
indirect.terminator.indirect_targets = {1, 1};
|
|
indirect.indirect_target = Value(0u);
|
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
|
program.blocks[1]->AddBranch(program.blocks[1]);
|
|
expect_invalid(program,
|
|
"duplicate indirect targets did not terminate IR validation");
|
|
}
|
|
{
|
|
Program program;
|
|
for (uint32_t id = 0; id < 2; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
expect_invalid(program,
|
|
"unreachable block did not terminate IR validation");
|
|
}
|
|
{
|
|
Program program;
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
auto *block = program.block_storage.back().get();
|
|
program.blocks.push_back(block);
|
|
program.block_info.push_back({.id = 0});
|
|
auto &use =
|
|
block->AppendNewInst(ValueOpcode::IAdd32, {Value(1u), Value(2u)});
|
|
auto &definition =
|
|
block->AppendNewInst(ValueOpcode::IAdd32, {Value(3u), Value(4u)});
|
|
use.SetArg(0, Value(&definition));
|
|
expect_invalid(program,
|
|
"same-block forward use did not terminate IR validation");
|
|
}
|
|
{
|
|
Program program;
|
|
for (uint32_t id = 0; id < 4; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
program.block_info[0].terminator.false_block = 2;
|
|
program.block_info[0].condition = Value(true);
|
|
for (uint32_t id : {1u, 2u}) {
|
|
program.block_info[id].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[id].terminator.true_block = 3;
|
|
}
|
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
|
program.blocks[0]->AddBranch(program.blocks[2]);
|
|
program.blocks[1]->AddBranch(program.blocks[3]);
|
|
program.blocks[2]->AddBranch(program.blocks[3]);
|
|
auto &definition = program.blocks[1]->AppendNewInst(ValueOpcode::IAdd32,
|
|
{Value(1u), Value(2u)});
|
|
program.blocks[2]->AppendNewInst(ValueOpcode::IAdd32,
|
|
{Value(&definition), Value(3u)});
|
|
expect_invalid(program,
|
|
"non-dominating definition did not terminate IR validation");
|
|
}
|
|
{
|
|
Program program;
|
|
for (uint32_t id = 0; id < 4; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
program.block_info[0].terminator.false_block = 2;
|
|
program.block_info[0].condition = Value(true);
|
|
for (uint32_t id : {1u, 2u}) {
|
|
program.block_info[id].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[id].terminator.true_block = 3;
|
|
}
|
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
|
program.blocks[0]->AddBranch(program.blocks[2]);
|
|
program.blocks[1]->AddBranch(program.blocks[3]);
|
|
program.blocks[2]->AddBranch(program.blocks[3]);
|
|
auto &definition = program.blocks[1]->AppendNewInst(ValueOpcode::IAdd32,
|
|
{Value(1u), Value(2u)});
|
|
auto &phi = program.blocks[3]->AppendNewInst(ValueOpcode::Phi);
|
|
phi.SetFlags(Type::U32);
|
|
phi.AddPhiOperand(program.blocks[1], Value(3u));
|
|
phi.AddPhiOperand(program.blocks[2], Value(&definition));
|
|
expect_invalid(program,
|
|
"unavailable Phi-edge definition did not terminate IR "
|
|
"validation");
|
|
}
|
|
{
|
|
Program program;
|
|
for (uint32_t id = 0; id < 5; id++) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
program.block_info[0].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
program.block_info[0].terminator.false_block = 2;
|
|
program.block_info[0].condition = Value(true);
|
|
program.block_info[1].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[1].terminator.true_block = 3;
|
|
program.block_info[2].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
|
program.block_info[2].terminator.true_block = 3;
|
|
program.block_info[2].terminator.false_block = 4;
|
|
program.block_info[3].terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[3].terminator.true_block = 4;
|
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
|
program.blocks[0]->AddBranch(program.blocks[2]);
|
|
program.blocks[1]->AddBranch(program.blocks[3]);
|
|
program.blocks[2]->AddBranch(program.blocks[3]);
|
|
program.blocks[2]->AddBranch(program.blocks[4]);
|
|
program.blocks[3]->AddBranch(program.blocks[4]);
|
|
auto &condition = program.blocks[1]->AppendNewInst(ValueOpcode::IEqual32,
|
|
{Value(1u), Value(2u)});
|
|
program.block_info[2].condition = Value(&condition);
|
|
expect_invalid(program,
|
|
"non-dominating branch condition did not terminate IR "
|
|
"validation");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void TestWave32MaskProjection() {
|
|
using namespace ShaderRecompiler::IR;
|
|
for (const uint32_t wave_size : {32u, 64u}) {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.wave_size = wave_size;
|
|
for (uint32_t id = 0; id < 3; ++id) {
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.push_back({.id = id});
|
|
}
|
|
auto *entry = program.blocks[0];
|
|
auto *loop = program.blocks[1];
|
|
entry->AddBranch(loop);
|
|
loop->AddBranch(loop);
|
|
loop->AddBranch(program.blocks[2]);
|
|
program.block_info[0].terminator.kind = ShaderRecompiler::CFG::TerminatorKind::Branch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
program.block_info[1].terminator.kind = ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
|
program.block_info[1].terminator.true_block = 1;
|
|
program.block_info[1].terminator.false_block = 2;
|
|
program.block_info[2].terminator.kind = ShaderRecompiler::CFG::TerminatorKind::Return;
|
|
IREmitter e(entry);
|
|
const auto zero = U32(Value(0u));
|
|
const auto raw = e.GetUserData(static_cast<ScalarReg>(0));
|
|
const auto active = e.INotEqual(raw, zero);
|
|
const auto selected = e.IEqual(raw, U32(Value(7u)));
|
|
const auto ballot = [&](IREmitter &ir, U1 predicate, uint32_t component = 0u) {
|
|
return ir.CompositeExtract(ir.Emit(ValueOpcode::Ballot, {predicate}), component);
|
|
};
|
|
const auto thread_bit = [&](IREmitter &ir, U32 word, bool own_lane = true) {
|
|
const auto bit = own_lane
|
|
? ir.BitwiseAnd(U32(ir.Emit(ValueOpcode::LaneId)), U32(Value(31u)))
|
|
: U32(Value(3u));
|
|
return ir.INotEqual(ir.BitwiseAnd(ir.ShiftRightLogical(word, bit), U32(Value(1u))), zero);
|
|
};
|
|
const auto active_mask = ballot(e, active);
|
|
const auto direct = thread_bit(e, active_mask);
|
|
const auto arbitrary = thread_bit(e, raw);
|
|
const auto mixed = thread_bit(e, e.BitwiseAnd(active_mask, raw));
|
|
const auto mixed_or = thread_bit(e, e.BitwiseOr(active_mask, raw));
|
|
const auto mixed_select = thread_bit(e, e.Select(selected, active_mask, raw));
|
|
const auto different_lane = thread_bit(e, active_mask, false);
|
|
const auto high_half = thread_bit(e, ballot(e, active, 1u));
|
|
const auto initial_mask = e.BitwiseAnd(active_mask, ballot(e, selected));
|
|
const auto raw_consumer = e.Emit(ValueOpcode::IAdd32, {initial_mask, Value(3u)});
|
|
const auto initial_predicate = e.LogicalAnd(active, selected);
|
|
const auto entry_size = entry->Instructions().size();
|
|
ConstantPropagationPass({entry}, wave_size);
|
|
Check(entry->Instructions().size() == entry_size,
|
|
"rejected mixed mask graph left unused projected instructions");
|
|
const auto combined_mask = e.BitwiseOr(active_mask, ballot(e, selected));
|
|
const auto combined = thread_bit(e, combined_mask);
|
|
const auto gated_mask = e.Select(selected, combined_mask, zero);
|
|
const auto gated = thread_bit(e, gated_mask);
|
|
const auto rejected = thread_bit(e, e.Select(selected, zero, combined_mask));
|
|
const auto combined_predicate = e.LogicalOr(active, selected);
|
|
const auto gated_predicate = e.LogicalAnd(selected, combined_predicate);
|
|
const auto rejected_predicate = e.Emit(ValueOpcode::SelectU1,
|
|
{selected, Value(false), combined_predicate});
|
|
const auto gated_raw_consumer = e.Emit(ValueOpcode::IAdd32, {gated_mask, Value(3u)});
|
|
auto &mask = loop->AppendNewInst(ValueOpcode::Phi);
|
|
mask.SetFlags(Type::U32);
|
|
auto &predicate = loop->AppendNewInst(ValueOpcode::Phi);
|
|
predicate.SetFlags(Type::U1);
|
|
IREmitter body(loop);
|
|
const auto matches = body.LogicalAnd(U1(Value(&predicate)), selected);
|
|
const auto remaining = body.BitwiseAnd(U32(Value(&mask)), body.BitwiseNot(ballot(body, matches)));
|
|
const auto remaining_predicate = body.LogicalAnd(U1(Value(&predicate)), body.LogicalNot(matches));
|
|
const auto remaining_live = thread_bit(body, remaining);
|
|
const auto live = thread_bit(body, U32(Value(&mask)));
|
|
mask.AddPhiOperand(entry, initial_mask);
|
|
mask.AddPhiOperand(loop, remaining);
|
|
predicate.AddPhiOperand(entry, initial_predicate);
|
|
predicate.AddPhiOperand(loop, remaining_predicate);
|
|
program.block_info[1].condition = remaining_predicate;
|
|
|
|
ConstantPropagationPass(program.blocks, wave_size);
|
|
if (wave_size == 32u) {
|
|
Check(direct.Resolve() == active.Resolve(), "wave32 ballot round trip lost its predicate");
|
|
Check(EquivalentValue(program, combined, combined_predicate),
|
|
"wave32 OR mask projection changed a lane predicate");
|
|
Check(EquivalentValue(program, gated, gated_predicate) &&
|
|
EquivalentValue(program, rejected, rejected_predicate),
|
|
"wave32 select mask projection changed a scalar gate or selected arm");
|
|
Check(EquivalentValue(program, live.Resolve(), Value(&predicate)),
|
|
"projected mask Phi differs from the existing per-lane EXEC Phi");
|
|
Check(EquivalentValue(program, remaining_live.Resolve(), remaining_predicate) &&
|
|
std::count_if(loop->begin(), loop->end(), [](const Inst &inst) {
|
|
return inst.GetOpcode() == ValueOpcode::LogicalAnd;
|
|
}) == 3,
|
|
"backedge-first projection duplicated the loop's logical mask operation");
|
|
const auto *projected = live.Resolve().TryInstruction();
|
|
Check(projected != nullptr && projected->GetOpcode() == ValueOpcode::Phi &&
|
|
projected->GetType() == Type::U1 && projected->PhiBlock(0) == entry &&
|
|
projected->PhiBlock(1) == loop,
|
|
"mask projection lost loop Phi type or predecessor order");
|
|
} else {
|
|
Check(direct.Resolve().TryInstruction()->GetOpcode() == ValueOpcode::INotEqual32 &&
|
|
live.Resolve().TryInstruction()->GetOpcode() == ValueOpcode::INotEqual32 &&
|
|
combined.Resolve().TryInstruction()->GetOpcode() == ValueOpcode::INotEqual32 &&
|
|
gated.Resolve().TryInstruction()->GetOpcode() == ValueOpcode::INotEqual32 &&
|
|
rejected.Resolve().TryInstruction()->GetOpcode() == ValueOpcode::INotEqual32,
|
|
"wave64 low-half mask was projected onto the wrong invocation");
|
|
}
|
|
for (const auto value : {arbitrary, mixed, mixed_or, mixed_select, different_lane, high_half}) {
|
|
Check(value.Resolve().TryInstruction()->GetOpcode() == ValueOpcode::INotEqual32,
|
|
"mask projection accepted an arbitrary mask, lane or ballot half");
|
|
}
|
|
Check(raw_consumer.Resolve().TryInstruction()->Arg(0).Resolve() == initial_mask.Resolve() &&
|
|
gated_raw_consumer.Resolve().TryInstruction()->Arg(0).Resolve() == gated_mask.Resolve(),
|
|
"mask projection changed a raw scalar mask consumer");
|
|
ResolveControlFlowIdentities(program);
|
|
RemoveIdentities(program.blocks);
|
|
ValidateProgram(program, true);
|
|
}
|
|
}
|
|
|
|
void TestU64ShiftConstantPropagation() {
|
|
using namespace ShaderRecompiler::IR;
|
|
|
|
Program shifts;
|
|
shifts.block_storage.push_back(std::make_unique<Block>());
|
|
shifts.blocks.push_back(shifts.block_storage.back().get());
|
|
shifts.block_info.emplace_back();
|
|
IREmitter shift_ir(shifts.blocks.front());
|
|
struct ShiftCase {
|
|
Value value;
|
|
uint64_t expected;
|
|
};
|
|
std::vector<ShiftCase> shift_cases;
|
|
for (const uint64_t source :
|
|
{uint64_t{0x0123456789abcdefull}, uint64_t{0xf123456789abcdefull}}) {
|
|
for (const uint32_t count : {0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u}) {
|
|
const uint32_t amount = count & 63u;
|
|
shift_cases.push_back({shift_ir.Emit(ValueOpcode::ShiftLeftLogical64,
|
|
{Value(source), Value(count)}),
|
|
source << amount});
|
|
shift_cases.push_back({shift_ir.Emit(ValueOpcode::ShiftRightLogical64,
|
|
{Value(source), Value(count)}),
|
|
source >> amount});
|
|
shift_cases.push_back(
|
|
{shift_ir.Emit(ValueOpcode::ShiftRightArithmetic64,
|
|
{Value(source), Value(count)}),
|
|
static_cast<uint64_t>(std::bit_cast<int64_t>(source) >> amount)});
|
|
}
|
|
}
|
|
ConstantPropagationPass(shifts.blocks);
|
|
for (const auto &test : shift_cases) {
|
|
const auto value = test.value.Resolve();
|
|
Check(value.IsImmediate() && value.GetType() == Type::U64 &&
|
|
value.U64() == test.expected,
|
|
"pair-U64 shift propagation violated the masked RDNA2 count");
|
|
}
|
|
}
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
void TestNativeWideValueValidation() {
|
|
using namespace ShaderRecompiler::IR;
|
|
|
|
const auto make_program = [] {
|
|
Program program;
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
program.blocks.push_back(program.block_storage.back().get());
|
|
program.block_info.emplace_back();
|
|
program.block_info.front().id = 0;
|
|
program.block_info.front().terminator.kind =
|
|
ShaderRecompiler::CFG::TerminatorKind::Return;
|
|
return program;
|
|
};
|
|
const auto append_load = [](Program &program, ValueOpcode opcode,
|
|
MemoryFlags flags) {
|
|
IREmitter ir(program.blocks.front());
|
|
const auto resource = ir.Emit(ValueOpcode::GetBufferResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
ir.Emit(opcode, {resource, Value(0u), Value(0u), Value(0u), Value(true)},
|
|
flags);
|
|
};
|
|
const auto append_scalar_read = [](Program &program, ValueOpcode opcode,
|
|
MemoryFlags flags) {
|
|
IREmitter ir(program.blocks.front());
|
|
if (opcode == ValueOpcode::ReadConstBuffer) {
|
|
const auto resource =
|
|
ir.Emit(ValueOpcode::GetBufferResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
ir.Emit(opcode, {resource, Value(0u)}, flags);
|
|
} else {
|
|
const auto resource =
|
|
ir.Emit(ValueOpcode::GetAddressResource, {Value(0u), Value(0u)});
|
|
ir.Emit(opcode, {resource, Value(0u), Value(0u), Value(true)}, flags);
|
|
}
|
|
};
|
|
const auto check_rejected = [](const Program &program, const char *text) {
|
|
ExpectFatal([&] { ValidateProgram(program, true); }, text);
|
|
};
|
|
|
|
{
|
|
auto program = make_program();
|
|
append_scalar_read(program, ValueOpcode::ReadConstBuffer,
|
|
MemoryFlags{.index = 1});
|
|
check_rejected(program, "invalid scalar memory-info index was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::ScalarAddress;
|
|
program.memory_info.push_back(memory);
|
|
append_scalar_read(program, ValueOpcode::ReadConstBuffer, {});
|
|
check_rejected(program, "invalid scalar-memory resource kind was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::ScalarAddress;
|
|
memory.data_bits = 16;
|
|
program.memory_info.push_back(memory);
|
|
append_scalar_read(program, ValueOpcode::LoadAddressU32, {});
|
|
check_rejected(program, "inconsistent address-memory bits were accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::ScalarBuffer;
|
|
memory.data_dwords = 2;
|
|
program.memory_info.push_back(memory);
|
|
append_scalar_read(program, ValueOpcode::ReadConstBuffer, {});
|
|
check_rejected(program, "inconsistent scalar-memory width was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::ScalarBuffer;
|
|
memory.component_count = 3;
|
|
program.memory_info.push_back(memory);
|
|
append_scalar_read(program, ValueOpcode::ReadConstBuffer, {});
|
|
check_rejected(program,
|
|
"inconsistent scalar-memory component count was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::ScalarAddress;
|
|
memory.component_count = 4;
|
|
memory.component_index = 4;
|
|
program.memory_info.push_back(memory);
|
|
append_scalar_read(program, ValueOpcode::LoadAddressU32, {});
|
|
check_rejected(program,
|
|
"inconsistent address-memory component index was accepted");
|
|
}
|
|
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Buffer;
|
|
memory.data_dwords = 3;
|
|
program.memory_info.push_back(memory);
|
|
append_load(program, ValueOpcode::LoadBufferU32x3, MemoryFlags{.index = 1});
|
|
check_rejected(program, "invalid buffer memory-info index was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Lds;
|
|
memory.data_dwords = 3;
|
|
program.memory_info.push_back(memory);
|
|
append_load(program, ValueOpcode::LoadBufferU32x3, {});
|
|
check_rejected(program, "non-buffer resource kind was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Buffer;
|
|
memory.data_dwords = 2;
|
|
program.memory_info.push_back(memory);
|
|
append_load(program, ValueOpcode::LoadBufferU32x3, {});
|
|
check_rejected(program, "inconsistent native-wide metadata was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Buffer;
|
|
memory.data_dwords = 3;
|
|
program.memory_info.push_back(memory);
|
|
append_load(program, ValueOpcode::LoadBufferU32, {});
|
|
check_rejected(program, "scalar-sibling width metadata was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
IREmitter ir(program.blocks.front());
|
|
const auto vector = ir.Emit(ValueOpcode::CompositeConstructU32x3,
|
|
{Value(1u), Value(2u), Value(3u)});
|
|
ir.Emit(ValueOpcode::CompositeExtractU32x3, {vector, Value(3u)});
|
|
check_rejected(program, "invalid U32x3 component index was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
IREmitter ir(program.blocks.front());
|
|
const auto vector =
|
|
ir.Emit(ValueOpcode::CompositeConstructU64, {Value(1u), Value(2u)});
|
|
ir.Emit(ValueOpcode::CompositeExtractU64, {vector, Value(2u)});
|
|
check_rejected(program, "invalid U64 component index was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
IREmitter ir(program.blocks.front());
|
|
const auto vector =
|
|
ir.Emit(ValueOpcode::CompositeConstructU64, {Value(1u), Value(2u)});
|
|
const auto dynamic_index =
|
|
ir.Emit(ValueOpcode::IAdd32, {Value(0u), Value(1u)});
|
|
ir.Emit(ValueOpcode::CompositeExtractU64, {vector, dynamic_index});
|
|
check_rejected(program, "dynamic U64 component index was accepted");
|
|
}
|
|
const auto append_shared = [](Program &program, ValueOpcode opcode,
|
|
MemoryFlags flags) {
|
|
IREmitter ir(program.blocks.front());
|
|
ir.Emit(opcode, {Value(0u), Value(true)}, flags);
|
|
};
|
|
{
|
|
auto program = make_program();
|
|
append_shared(program, ValueOpcode::LoadSharedU32, MemoryFlags{.index = 1});
|
|
check_rejected(program, "invalid shared memory-info index was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Buffer;
|
|
program.memory_info.push_back(memory);
|
|
append_shared(program, ValueOpcode::LoadSharedU32, {});
|
|
check_rejected(program, "invalid shared-memory resource kind was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Gds;
|
|
memory.resource = 1;
|
|
program.memory_info.push_back(memory);
|
|
append_shared(program, ValueOpcode::LoadSharedU32, {});
|
|
check_rejected(program, "invalid GDS resource metadata was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Lds;
|
|
memory.data_dwords = 2;
|
|
memory.component_count = 2;
|
|
program.memory_info.push_back(memory);
|
|
append_shared(program, ValueOpcode::LoadSharedU32x3, {});
|
|
check_rejected(program, "inconsistent shared-memory width was accepted");
|
|
}
|
|
const auto append_address = [](Program &program, ValueOpcode opcode,
|
|
MemoryFlags flags) {
|
|
IREmitter ir(program.blocks.front());
|
|
const auto resource =
|
|
ir.Emit(ValueOpcode::GetAddressResource, {Value(0u), Value(0u)});
|
|
if (opcode == ValueOpcode::StoreAddressU32) {
|
|
ir.Emit(opcode, {resource, Value(0u), Value(0u), Value(0u), Value(true)},
|
|
flags);
|
|
} else {
|
|
ir.Emit(opcode, {resource, Value(0u), Value(0u), Value(true)}, flags);
|
|
}
|
|
};
|
|
{
|
|
auto program = make_program();
|
|
append_address(program, ValueOpcode::StoreAddressU32,
|
|
MemoryFlags{.index = 1});
|
|
check_rejected(program, "invalid address memory-info index was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Buffer;
|
|
program.memory_info.push_back(memory);
|
|
append_address(program, ValueOpcode::StoreAddressU32, {});
|
|
check_rejected(program, "invalid address resource kind was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Global;
|
|
program.memory_info.push_back(memory);
|
|
append_address(program, ValueOpcode::LoadAddressU8, {});
|
|
check_rejected(program, "inconsistent address load metadata was accepted");
|
|
}
|
|
const auto append_image = [](Program &program, ValueOpcode opcode,
|
|
MemoryFlags flags) {
|
|
IREmitter ir(program.blocks.front());
|
|
const auto image = ir.Emit(ValueOpcode::GetImageResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
const auto address =
|
|
ir.Emit(ValueOpcode::MakeImageAddress,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u), Value(0u)});
|
|
const auto image_info = ImageOpcodeInfoOf(opcode);
|
|
if (opcode == ValueOpcode::ImageWrite) {
|
|
const auto data = ir.Emit(ValueOpcode::CompositeConstructU32x4,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
ir.Emit(opcode, {image, address, data, Value(true)}, flags);
|
|
} else if (image_info.access == ImageAccess::Atomic) {
|
|
ir.Emit(opcode, {image, address, Value(0u), Value(true)}, flags);
|
|
} else if (image_info.needs_sampler) {
|
|
const auto sampler =
|
|
ir.Emit(ValueOpcode::GetSamplerResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
ir.Emit(opcode, {image, sampler, address}, flags);
|
|
} else if (opcode == ValueOpcode::ImageQueryDimensions) {
|
|
ir.Emit(opcode, {image, address}, flags);
|
|
} else {
|
|
ir.Emit(opcode, {image, address, Value(true)}, flags);
|
|
}
|
|
};
|
|
{
|
|
auto program = make_program();
|
|
append_image(program, ValueOpcode::ImageRead, MemoryFlags{.index = 1});
|
|
check_rejected(program, "invalid image memory-info index was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Buffer;
|
|
program.memory_info.push_back(memory);
|
|
append_image(program, ValueOpcode::ImageRead, {});
|
|
check_rejected(program, "buffer kind for image read was accepted");
|
|
}
|
|
{
|
|
auto program = make_program();
|
|
IREmitter ir(program.blocks.front());
|
|
const auto data = ir.Emit(ValueOpcode::CompositeConstructU32x4,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
ir.Emit(ValueOpcode::SetAttribute, {data, Value(true)},
|
|
ExportFlags{.index = 1});
|
|
check_rejected(program, "invalid export-info index was accepted");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void TestNewShaderRecompilerZeroInitialRegisterState() {
|
|
const uint32_t shader[] = {
|
|
EncodeVop1(0x01, 0, 100), // v_mov_b32 v0, s100
|
|
EncodeExp0(0x0c, 0xf),
|
|
EncodeExp1(0, 1, 2, 3), // POS0
|
|
EncodeSopp(0x01),
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check((result.ir_dump.find("UndefU32") == std::string::npos),
|
|
"SSA initial state left guest registers undefined");
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check((source.find("OpUndef") == std::string::npos),
|
|
"zero-initialized guest registers became SPIR-V undef values");
|
|
Check((source.find("%s100") == std::string::npos) &&
|
|
(source.find("%v0") == std::string::npos),
|
|
"final SPIR-V retained a guest register mirror");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t explicit_zero_shader[] = {
|
|
EncodeSMovB32(100, 128), // s_mov_b32 s100, 0
|
|
EncodeVop1(0x01, 0, 100), // v_mov_b32 v0, s100
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto explicit_zero =
|
|
RecompileForTest(explicit_zero_shader, options);
|
|
Check(result.spirv == explicit_zero.spirv,
|
|
"implicit and explicit zero register state emitted different SPIR-V");
|
|
}
|
|
|
|
void TestNewShaderRecompilerVertexSystemInputsWithoutMirrors() {
|
|
using StageInputKind = ShaderRecompiler::IR::StageInputKind;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeVop1(0x01, 0, 5 + 256), // v_mov_b32 v0, v5
|
|
EncodeVop1(0x01, 1, 8 + 256), // v_mov_b32 v1, v8
|
|
EncodeExp0(0x0c, 0xf),
|
|
EncodeExp1(0, 1, 2, 3), // POS0
|
|
EncodeExp0(0x20, 0x3),
|
|
EncodeExp1(0, 1, 0, 0), // PARAM0
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(ProgramHasInput(result.program, StageInputKind::VertexIndex),
|
|
"vertex shader missing VertexIndex input");
|
|
Check(ProgramHasInput(result.program, StageInputKind::InstanceIndex),
|
|
"vertex shader missing InstanceIndex input");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(CountSourceOccurrences(source, "OpLoad %int %gl_VertexIndex") == 1u,
|
|
"vertex SPIR-V does not load gl_VertexIndex");
|
|
Check(CountSourceOccurrences(source, "OpLoad %int %gl_InstanceIndex") == 1u,
|
|
"vertex SPIR-V does not load gl_InstanceIndex");
|
|
Check((source.find("%v5") == std::string::npos) &&
|
|
(source.find("%v8") == std::string::npos),
|
|
"vertex system values were routed through guest VGPR mirrors");
|
|
}
|
|
|
|
void TestNewShaderRecompilerVertexExportUsesInvocationExecMask() {
|
|
const uint32_t shader[] = {
|
|
EncodeSop1(0x04, 126, 129), // s_mov_b64 exec, 1
|
|
EncodeExp0(0x0c, 0xf),
|
|
EncodeExp1(0, 1, 2, 3), // POS0
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check((source.find("OpLoad %uint %gl_SubgroupInvocationID") != std::string::npos),
|
|
"raw EXEC=1 did not select guest lane zero");
|
|
Check((source.find("OpBranchConditional") != std::string::npos),
|
|
"vertex export lost its per-invocation EXEC guard");
|
|
}
|
|
|
|
void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
|
|
for (uint32_t wave_size : {32u, 64u}) {
|
|
for (uint32_t opcode : {0x09u, 0x0au}) {
|
|
for (uint32_t source : {126u, 8u}) {
|
|
const uint32_t pixel_shader[] = {
|
|
EncodeSop1(0x04, 8, 126), // Save the entry live mask.
|
|
EncodeSop1(opcode, 126, source),
|
|
EncodeVop1(0x01, 0, 242), // v_mov_b32 v0, 1.0
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
auto pixel_options = MakeCompileOptions(ShaderType::Pixel);
|
|
pixel_options.wave_size = wave_size;
|
|
const auto pixel_result = RecompileForTest(pixel_shader, pixel_options);
|
|
CheckSpirvBinaryValidates(pixel_result.spirv);
|
|
Check(DisassembleSpirvBinary(pixel_result.spirv).find("OpGroupNonUniformBallot") ==
|
|
std::string::npos,
|
|
"entry WQM lost the known live predicate through a scalar ballot");
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint32_t local_shader[] = {
|
|
EncodeVopc(0xc1, 5 + 256, 8), // v_cmp_lt_u32 vcc, v5, v8
|
|
EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc
|
|
EncodeSop1(0x24, 4, 126), // s_and_saveexec_b64 s[4:5], exec
|
|
EncodeSop2(0x25, 126, 132, 128), // s_bfm_b64 exec, 4, 0
|
|
EncodeSop1(0x08, 126, 126), // s_not_b64 exec, exec
|
|
EncodeSop1(0x08, 126, 126), // s_not_b64 exec, exec
|
|
EncodeExp0(0x0c, 0xf),
|
|
EncodeExp1(0, 1, 2, 3), // POS0
|
|
EncodeSopp(0x01),
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(local_shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(
|
|
(source.find("OpGroupNonUniformBallot") == std::string::npos),
|
|
"per-invocation VCC producer still materialized a shared subgroup mask");
|
|
Check((source.find("OpLoad %uint %gl_SubgroupInvocationID") != std::string::npos),
|
|
"BFM EXEC prefix did not select the four requested guest lanes");
|
|
Check((source.find("%vcc_lo") == std::string::npos) &&
|
|
(source.find("%vcc_hi") == std::string::npos),
|
|
"per-invocation comparison retained VCC register mirrors");
|
|
|
|
const uint32_t wqm_shader[] = {
|
|
EncodeSop1(0x0a, 2, 126), // s_wqm_b64 s[2:3], exec
|
|
EncodeVop1(0x01, 0, 2), // v_mov_b32 v0, s2
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0
|
|
EncodeSopp(0x01),
|
|
};
|
|
result = RecompileForTest(wqm_shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto wqm_source = DisassembleSpirvBinary(result.spirv);
|
|
Check((wqm_source.find("OpCapability GroupNonUniformBallot") != std::string::npos) &&
|
|
(wqm_source.find("OpGroupNonUniformBallot") != std::string::npos),
|
|
"per-invocation scalar WQM omitted its subgroup ballot capability");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 132u) == 1u,
|
|
"wave64 WQM did not expand its scalar word pair together");
|
|
|
|
auto wave32_options = options;
|
|
wave32_options.wave_size = 32u;
|
|
result = RecompileForTest(wqm_shader, wave32_options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 132u) == 1u,
|
|
"wave32 WQM retained the unused high ballot-word expansion");
|
|
|
|
const uint32_t cross_lane_shader[] = {
|
|
EncodeSop2(0x25, 126, 132, 128), // s_bfm_b64 exec, 4, 0
|
|
EncodeVop1(0x01, 0, 250),
|
|
EncodeVop1Dpp(1), // v_mov_b32 v0, v1 dpp
|
|
EncodeExp0(0x0c, 0xf),
|
|
EncodeExp1(0, 1, 2, 3), // POS0
|
|
EncodeSopp(0x01),
|
|
};
|
|
result = RecompileForTest(cross_lane_shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
Check((DisassembleSpirvBinary(result.spirv).find("OpGroupNonUniformBallot") != std::string::npos),
|
|
"per-invocation cross-lane EXEC was not reconstructed as a subgroup "
|
|
"ballot");
|
|
}
|
|
|
|
void TestNewShaderRecompilerPerInvocationU64Complement() {
|
|
const uint32_t shader[] = {
|
|
EncodeVop1(0x01, 0, 0), // v_mov_b32 v0, s0
|
|
EncodeVop1(0x01, 1, 1), // v_mov_b32 v1, s1
|
|
EncodeVopc(0xc1, 0 + 256, 1), // v_cmp_lt_u32 vcc, v0, v1
|
|
EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc
|
|
EncodeSop1(0x08, 4, 2), // s_not_b64 s[4:5], s[2:3]
|
|
EncodeSop2(0x0f, 126, 126, 4), // s_and_b64 exec, exec, s[4:5]
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), EncodeSopp(0x01),
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Vertex);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check((source.find("OpLogicalNot") != std::string::npos),
|
|
"per-invocation s_not_b64 did not complement the lane predicate");
|
|
Check((source.find("OpNot %uint") == std::string::npos),
|
|
"per-invocation s_not_b64 emitted raw complemented mask words");
|
|
}
|
|
|
|
void TestNewShaderRecompilerExpPixelOutputs() {
|
|
const uint32_t shader[] = {
|
|
EncodeExp0(0x00, 0xf),
|
|
EncodeExp1(0, 1, 2, 3), // MRT0
|
|
EncodeExp0(0x08, 0x1),
|
|
EncodeExp1(4, 0, 0, 0), // MRTZ depth
|
|
EncodeExp0(0x09, 0x0),
|
|
EncodeExp1(0, 0, 0, 0), // NULL
|
|
EncodeExp0(0x00, 0xf, true, true),
|
|
EncodeExp1(5, 6, 0, 0), // compressed MRT0
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(SpirvContainsOpcode(result.spirv, 62),
|
|
"pixel export SPIR-V lacks OpStore");
|
|
Check(SpirvContainsExtInst(result.spirv, 62),
|
|
"compressed pixel export SPIR-V lacks GLSL.std.450 UnpackHalf2x16");
|
|
Check(SpirvContainsOpcode(result.spirv, 81),
|
|
"compressed pixel export SPIR-V lacks OpCompositeExtract");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
ShaderPixelInputInfo uint16_info;
|
|
uint16_info.target_output_mode[0] = 7;
|
|
options.input_info.pixel = &uint16_info;
|
|
auto uint16_result = RecompileForTest(shader, options);
|
|
const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv);
|
|
Check((uint16_source.find("OpVariable %_ptr_Output_v4uint Output") != std::string::npos),
|
|
"UINT16 MRT export did not use an unsigned integer output");
|
|
Check(
|
|
CountSourceOccurrences(uint16_source, "OpBitFieldUExtract") == 4u &&
|
|
(uint16_source.find("%uint_0 %uint_16") != std::string::npos) &&
|
|
(uint16_source.find("%uint_16 %uint_16") != std::string::npos),
|
|
"compressed UINT16 MRT export did not extract all low/high 16-bit lanes");
|
|
Check(!SpirvContainsExtInst(uint16_result.spirv, 62),
|
|
"compressed UINT16 MRT export was incorrectly decoded as FP16");
|
|
CheckSpirvBinaryValidates(uint16_result.spirv);
|
|
|
|
ShaderPixelInputInfo unorm16_info;
|
|
unorm16_info.target_output_mode[0] = 5;
|
|
options.input_info.pixel = &unorm16_info;
|
|
auto unorm16_result = RecompileForTest(shader, options);
|
|
const auto unorm16_source = DisassembleSpirvBinary(unorm16_result.spirv);
|
|
Check(CountSourceOccurrences(unorm16_source, "UnpackUnorm2x16") == 2u,
|
|
"compressed UNORM16 MRT export did not unpack two normalized pairs");
|
|
Check(!SpirvContainsExtInst(unorm16_result.spirv, 62),
|
|
"compressed UNORM16 MRT export was incorrectly decoded as FP16");
|
|
CheckSpirvBinaryValidates(unorm16_result.spirv);
|
|
|
|
const uint32_t partial_shader[] = {
|
|
EncodeExp0(0x00, 0x7),
|
|
EncodeExp1(0, 1, 2, 3),
|
|
0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo default_info;
|
|
options.input_info.pixel = &default_info;
|
|
auto partial_result = RecompileForTest(partial_shader, options);
|
|
const auto partial_source = DisassembleSpirvBinary(partial_result.spirv);
|
|
Check(SpirvSourceHasInstructionOperand(partial_source, "OpBitcast",
|
|
"%uint_1065353216"),
|
|
"disabled float alpha export did not default to 1.0f bits");
|
|
CheckSpirvBinaryValidates(partial_result.spirv);
|
|
|
|
ShaderPixelInputInfo uint_info;
|
|
uint_info.target_output_mode[0] = 7;
|
|
options.input_info.pixel = &uint_info;
|
|
auto partial_uint_result =
|
|
RecompileForTest(partial_shader, options);
|
|
const auto partial_uint_source =
|
|
DisassembleSpirvBinary(partial_uint_result.spirv);
|
|
Check(SpirvSourceHasInstructionOperand(partial_uint_source,
|
|
"OpCompositeConstruct", "%uint_1"),
|
|
"disabled integer alpha export did not default to integer one");
|
|
CheckSpirvBinaryValidates(partial_uint_result.spirv);
|
|
|
|
const uint32_t compressed_ba_shader[] = {
|
|
EncodeExp0(0x00, 0xc, true, true),
|
|
EncodeExp1(0, 1, 0, 0),
|
|
0xbf810000u,
|
|
};
|
|
auto compressed_ba_result =
|
|
RecompileForTest(compressed_ba_shader, options);
|
|
const auto compressed_ba_source =
|
|
DisassembleSpirvBinary(compressed_ba_result.spirv);
|
|
Check(CountSourceOccurrences(compressed_ba_source, "OpCompositeExtract") ==
|
|
1u &&
|
|
CountSourceOccurrences(compressed_ba_source,
|
|
"OpBitFieldUExtract") == 2u,
|
|
"compressed UINT16 BA-only export did not read and unpack VSRC1");
|
|
CheckSpirvBinaryValidates(compressed_ba_result.spirv);
|
|
|
|
unorm16_info.target_output_mode[0] = 5;
|
|
options.input_info.pixel = &unorm16_info;
|
|
auto unorm16_ba_result =
|
|
RecompileForTest(compressed_ba_shader, options);
|
|
const auto unorm16_ba_source =
|
|
DisassembleSpirvBinary(unorm16_ba_result.spirv);
|
|
Check(CountSourceOccurrences(unorm16_ba_source, "UnpackUnorm2x16") == 1u,
|
|
"compressed UNORM16 BA-only export did not unpack VSRC1");
|
|
CheckSpirvBinaryValidates(unorm16_ba_result.spirv);
|
|
}
|
|
|
|
void TestRenderTargetReverseExportMapping() {
|
|
const auto format = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat,
|
|
Prospero::ChannelOrder::kReversed);
|
|
Check(format.format == vk::Format::eR16G16B16A16Sfloat &&
|
|
format.bytes_per_element == 8u,
|
|
"reverse RGBA16F render target did not retain its native host format "
|
|
"and size");
|
|
Check(format.export_mapping == Prospero::ColorMappingAbgr &&
|
|
format.export_mapping.ApplyMask(0x1u) == 0x8u &&
|
|
format.export_mapping.ApplyMask(0x2u) == 0x4u &&
|
|
format.export_mapping.ApplyMask(0x4u) == 0x2u &&
|
|
format.export_mapping.ApplyMask(0x8u) == 0x1u &&
|
|
format.export_mapping.ApplyMask(0xfu) == 0xfu,
|
|
"reverse RGBA16F render-target export or write-mask mapping is "
|
|
"incorrect");
|
|
const auto legacy_alt = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k8_8_8_8, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kAlt);
|
|
Check(legacy_alt.format == vk::Format::eB8G8R8A8Unorm &&
|
|
legacy_alt.export_mapping.IsIdentity(),
|
|
"legacy BGRA render target acquired a duplicate shader export mapping");
|
|
const auto gr32 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k32_32, Prospero::ChannelType::kFloat,
|
|
Prospero::ChannelOrder::kReversed);
|
|
Check(gr32.format == vk::Format::eR32G32Sfloat &&
|
|
gr32.bytes_per_element == 8u &&
|
|
gr32.export_mapping == Prospero::ColorMappingGr,
|
|
"reverse GR32F render target did not retain its native host format, "
|
|
"size, and mapping");
|
|
|
|
struct MappingCase {
|
|
Prospero::ChannelLayout layout;
|
|
Prospero::ChannelType type;
|
|
vk::Format format;
|
|
uint32_t bytes;
|
|
std::array<Prospero::ColorComponentMapping, 4> mappings;
|
|
};
|
|
const MappingCase mapping_cases[] = {
|
|
{Prospero::ChannelLayout::k8,
|
|
Prospero::ChannelType::kUInt,
|
|
vk::Format::eR8Uint,
|
|
1,
|
|
{Prospero::ColorMappingRgba, Prospero::ColorMappingGr,
|
|
Prospero::ColorMappingBgra, Prospero::ColorMappingAgba}},
|
|
{Prospero::ChannelLayout::k32_32,
|
|
Prospero::ChannelType::kFloat,
|
|
vk::Format::eR32G32Sfloat,
|
|
8,
|
|
{Prospero::ColorMappingRgba, Prospero::ColorMappingRabg,
|
|
Prospero::ColorMappingGr, Prospero::ColorMappingArbg}},
|
|
{Prospero::ChannelLayout::k5_6_5,
|
|
Prospero::ChannelType::kUNorm,
|
|
vk::Format::eB5G6R5UnormPack16,
|
|
2,
|
|
{Prospero::ColorMappingRgba, Prospero::ColorMappingRgab,
|
|
Prospero::ColorMappingBgra, Prospero::ColorMappingAgbr}},
|
|
{Prospero::ChannelLayout::k16_16_16_16,
|
|
Prospero::ChannelType::kFloat,
|
|
vk::Format::eR16G16B16A16Sfloat,
|
|
8,
|
|
{Prospero::ColorMappingRgba, Prospero::ColorMappingBgra,
|
|
Prospero::ColorMappingAbgr, Prospero::ColorMappingArgb}},
|
|
};
|
|
for (const auto &mapping_case : mapping_cases) {
|
|
for (uint32_t order = 0; order < 4u; order++) {
|
|
const auto info = TextureGetRenderTargetFormat(
|
|
mapping_case.layout, mapping_case.type,
|
|
static_cast<Prospero::ChannelOrder>(order));
|
|
Check(info.format == mapping_case.format &&
|
|
info.bytes_per_element == mapping_case.bytes &&
|
|
info.export_mapping == mapping_case.mappings[order],
|
|
"render-target channel order did not use the component-count "
|
|
"mapping");
|
|
for (uint32_t physical = 0; physical < 4u; physical++) {
|
|
const auto logical = info.export_mapping.Map(physical);
|
|
Check(info.export_mapping.ApplyMask(1u << logical) == (1u << physical),
|
|
"logical color write mask was not mapped to the inverse physical "
|
|
"component");
|
|
}
|
|
}
|
|
}
|
|
|
|
const auto alt_1010102 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k10_10_10_2, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kAlt);
|
|
Check(alt_1010102.format == vk::Format::eA2R10G10B10UnormPack32 &&
|
|
alt_1010102.export_mapping.IsIdentity(),
|
|
"native alternate 10:10:10:2 render target acquired a shader mapping");
|
|
const auto reversed_4444 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k4_4_4_4, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kReversed);
|
|
Check(reversed_4444.format == vk::Format::eR4G4B4A4UnormPack16 &&
|
|
reversed_4444.export_mapping.IsIdentity(),
|
|
"reversed 4:4:4:4 render target did not compose storage and channel "
|
|
"order");
|
|
const auto standard_4444 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k4_4_4_4, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kStandard);
|
|
Check(standard_4444.format == vk::Format::eR4G4B4A4UnormPack16 &&
|
|
standard_4444.export_mapping == Prospero::ColorMappingAbgr,
|
|
"standard 4:4:4:4 render target did not compensate host packed-bit "
|
|
"order");
|
|
const auto standard_5551 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k5_5_5_1, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kStandard);
|
|
Check(standard_5551.format == vk::Format::eA1R5G5B5UnormPack16 &&
|
|
standard_5551.export_mapping == Prospero::ColorMappingBgra,
|
|
"5:5:5:1 render target did not preserve its high one-bit component");
|
|
const auto standard_1555 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k1_5_5_5, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kStandard);
|
|
Check(standard_1555.format == vk::Format::eR5G5B5A1UnormPack16 &&
|
|
standard_1555.export_mapping == Prospero::ColorMappingAbgr,
|
|
"1:5:5:5 render target did not preserve its low one-bit component");
|
|
|
|
const auto argb = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat,
|
|
Prospero::ChannelOrder::kAltReversed);
|
|
Check(argb.export_mapping == Prospero::ColorMappingArgb &&
|
|
argb.export_mapping.ApplyMask(0x1u) == 0x2u &&
|
|
argb.export_mapping.ApplyMask(0x2u) == 0x4u &&
|
|
argb.export_mapping.ApplyMask(0x4u) == 0x8u &&
|
|
argb.export_mapping.ApplyMask(0x8u) == 0x1u,
|
|
"alternate-reversed render-target mapping did not invert its "
|
|
"non-involutive cycle");
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeExp0(0x00, 0xf),
|
|
EncodeExp1(0, 1, 2, 3), // MRT0
|
|
0xbf810000u,
|
|
};
|
|
ShaderPixelInputInfo identity_info;
|
|
ShaderPixelInputInfo reversed_info;
|
|
reversed_info.target_export_mapping[0] = format.export_mapping;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &identity_info;
|
|
auto identity_result = RecompileForTest(shader, options);
|
|
Check(SpirvInstructionOpcodeCount(identity_result.spirv, 79u) == 0u,
|
|
"identity MRT export unexpectedly added a component shuffle");
|
|
|
|
options.input_info.pixel = &reversed_info;
|
|
auto reversed_result = RecompileForTest(shader, options);
|
|
Check(SpirvContainsVectorShuffle(reversed_result.spirv, {3u, 2u, 1u, 0u}),
|
|
"reverse MRT export did not emit a WZYX component shuffle");
|
|
CheckSpirvBinaryValidates(reversed_result.spirv);
|
|
|
|
reversed_info.target_export_mapping[0] = gr32.export_mapping;
|
|
auto gr32_result = RecompileForTest(shader, options);
|
|
Check(SpirvContainsVectorShuffle(gr32_result.spirv, {1u, 0u, 2u, 3u}),
|
|
"reverse GR32F MRT export did not emit a YXZW component shuffle");
|
|
CheckSpirvBinaryValidates(gr32_result.spirv);
|
|
|
|
reversed_info.target_export_mapping[0] = argb.export_mapping;
|
|
auto argb_result = RecompileForTest(shader, options);
|
|
Check(SpirvContainsVectorShuffle(argb_result.spirv, {3u, 0u, 1u, 2u}),
|
|
"alternate-reversed MRT export did not emit an ARGB component shuffle");
|
|
CheckSpirvBinaryValidates(argb_result.spirv);
|
|
reversed_info.target_export_mapping[0] = gr32.export_mapping;
|
|
|
|
HW::PixelShaderInfo regs{};
|
|
Check(MakeStageStaticKey(identity_info) != MakeStageStaticKey(reversed_info),
|
|
"pixel shader cache identity omitted the render-target export mapping");
|
|
|
|
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
|
|
ShaderMappedData mapped{};
|
|
mapped.code_size_bytes = sizeof(shader);
|
|
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
|
HW::ShaderRegisters sh{};
|
|
std::array<Prospero::ColorComponentMapping, 8> mappings{};
|
|
mappings[0] = gr32.export_mapping;
|
|
ShaderPixelInputInfo compiled_info{};
|
|
PrepareProgram(regs, sh, mappings, compiled_info);
|
|
Check(compiled_info.target_export_mapping[0].IsIdentity(),
|
|
"inactive reverse MRT mapping was not normalized out of the shader "
|
|
"cache key");
|
|
sh.target_output_mode[0] = 4;
|
|
PrepareProgram(regs, sh, mappings, compiled_info);
|
|
Check(compiled_info.target_export_mapping[0] == gr32.export_mapping,
|
|
"active reverse MRT mapping was lost before shader specialization");
|
|
}
|
|
|
|
void TestBlendMappingClassification() {
|
|
using Factor = Prospero::BlendFactor;
|
|
using Support = BlendMappingSupport;
|
|
HW::BlendControl blend{};
|
|
blend.separate_alpha_blend = false;
|
|
const auto classify = [&](Prospero::ColorComponentMapping mapping) {
|
|
return ClassifyBlendMapping(blend, mapping);
|
|
};
|
|
for (const auto factor : {Factor::kConstantAlpha, Factor::kOneMinusConstantAlpha}) {
|
|
blend.color_srcblend = static_cast<uint8_t>(factor);
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::Direct,
|
|
"scalar blend constant incorrectly required physical alpha");
|
|
}
|
|
for (const auto factor : {Factor::kConstantColor, Factor::kOneMinusConstantColor}) {
|
|
blend.color_srcblend = static_cast<uint8_t>(factor);
|
|
Check(classify(Prospero::ColorMappingRgba) == Support::Direct &&
|
|
classify(Prospero::ColorMappingBgra) == Support::Unsupported &&
|
|
classify(Prospero::ColorMappingAbgr) == Support::Unsupported,
|
|
"logical blend constants were accepted with shuffled color components");
|
|
}
|
|
blend.color_srcblend = static_cast<uint8_t>(Factor::kOne);
|
|
blend.alpha_srcblend = static_cast<uint8_t>(Factor::kConstantColor);
|
|
blend.separate_alpha_blend = true;
|
|
Check(classify(Prospero::ColorMappingBgra) == Support::Direct &&
|
|
classify(Prospero::ColorMappingAbgr) == Support::Unsupported,
|
|
"separate constant-color alpha equation ignored the physical alpha location");
|
|
blend.color_srcblend = static_cast<uint8_t>(Factor::kSrcAlpha);
|
|
blend.separate_alpha_blend = false;
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlpha,
|
|
"reversed source-alpha blending did not request logical alpha");
|
|
blend.separate_alpha_blend = true;
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::Unsupported,
|
|
"different alpha equations were accepted on a reversed target");
|
|
blend.separate_alpha_blend = false;
|
|
blend.color_srcblend = static_cast<uint8_t>(Factor::kDstAlpha);
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::Unsupported,
|
|
"destination alpha incorrectly used the physical alpha channel");
|
|
|
|
blend.separate_alpha_blend = true;
|
|
blend.color_srcblend = static_cast<uint8_t>(Factor::kSrcAlpha);
|
|
blend.alpha_srcblend = static_cast<uint8_t>(Factor::kOne);
|
|
blend.color_destblend = blend.alpha_destblend =
|
|
static_cast<uint8_t>(Factor::kOneMinusSrcAlpha);
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlphaOne,
|
|
"separate alpha accumulation did not preserve its unit source factor");
|
|
blend.alpha_srcblend = static_cast<uint8_t>(Factor::kZero);
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlphaZero,
|
|
"separate alpha attenuation did not preserve its zero source factor");
|
|
blend.alpha_srcblend = blend.color_srcblend;
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlpha,
|
|
"equal separate alpha equations did not reuse the broadcast alpha mode");
|
|
blend.alpha_srcblend = static_cast<uint8_t>(Factor::kOne);
|
|
const auto exact = blend;
|
|
for (uint32_t changed = 0; changed < 6; changed++) {
|
|
blend = exact;
|
|
switch (changed) {
|
|
case 0: blend.color_srcblend = static_cast<uint8_t>(Factor::kZero); break;
|
|
case 1: blend.color_destblend = static_cast<uint8_t>(Factor::kOne); break;
|
|
case 2: blend.alpha_srcblend = static_cast<uint8_t>(Factor::kDstAlpha); break;
|
|
case 3: blend.alpha_destblend = static_cast<uint8_t>(Factor::kOne); break;
|
|
case 4: blend.color_comb_fcn = static_cast<uint8_t>(Prospero::BlendOp::kSubtract); break;
|
|
case 5: blend.alpha_comb_fcn = static_cast<uint8_t>(Prospero::BlendOp::kSubtract); break;
|
|
}
|
|
Check(classify(Prospero::ColorMappingAbgr) == Support::Unsupported,
|
|
"separate alpha lowering accepted a different blend equation");
|
|
}
|
|
}
|
|
|
|
void TestLogicalAlphaBlendExport() {
|
|
ShaderPixelInputInfo pixel{};
|
|
pixel.target_output_mode[0] = 4;
|
|
pixel.target_export_mapping[0] = Prospero::ColorMappingAbgr;
|
|
const auto ordinary_key = MakeStageStaticKey(pixel);
|
|
pixel.dual_source_blending = true;
|
|
const auto guest_key = MakeStageStaticKey(pixel);
|
|
pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlpha;
|
|
const auto remapped_key = MakeStageStaticKey(pixel);
|
|
pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaOne;
|
|
const auto separate_key = MakeStageStaticKey(pixel);
|
|
pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaZero;
|
|
const auto zero_key = MakeStageStaticKey(pixel);
|
|
Check(ordinary_key != guest_key && guest_key != remapped_key &&
|
|
ordinary_key != remapped_key && separate_key != ordinary_key &&
|
|
separate_key != guest_key && separate_key != remapped_key &&
|
|
zero_key != ordinary_key && zero_key != guest_key &&
|
|
zero_key != remapped_key && zero_key != separate_key,
|
|
"ordinary, guest dual-source, and logical-alpha modes share a cache key");
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &pixel;
|
|
for (const auto mode : {ShaderAlphaBlendSource::SourceAlpha,
|
|
ShaderAlphaBlendSource::SourceAlphaOne}) {
|
|
pixel.alpha_blend_source = mode;
|
|
for (const bool compressed : {false, true}) {
|
|
const uint32_t shader[] = {
|
|
// RGBA = (1000, 2, 3, 0.25), packed into two half pairs when compressed.
|
|
EncodeVop1(0x01, 0, 255), compressed ? 0x400063d0u : 0x447a0000u,
|
|
EncodeVop1(0x01, 1, 255), compressed ? 0x34004200u : 0x40000000u,
|
|
EncodeVop1(0x01, 2, 255), 0x40400000u,
|
|
EncodeVop1(0x01, 3, 255), 0x3e800000u,
|
|
EncodeExp0(0, 0xf, false, compressed), EncodeExp1(0, 1, 2, 3),
|
|
EncodeExp0(1, 0xf, false), EncodeExp1(0, 0, 0, 0),
|
|
EncodeExp0(2, 0xf, true, false, true), EncodeExp1(0, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
|
Check(source.find("OpDecorate %out_mrt_1 Location 0") != std::string::npos &&
|
|
source.find("OpDecorate %out_mrt_1 Index 1") != std::string::npos &&
|
|
CountSourceOccurrences(source, "OpStore %out_mrt_1 ") == 1 &&
|
|
source.find("out_mrt_2") == std::string::npos,
|
|
"inactive MRT exports overwrote the logical-alpha output");
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 252u) != 0,
|
|
"ignoring inactive MRT stores discarded their valid-mask export");
|
|
uint32_t alpha_input = 0;
|
|
uint32_t alpha_broadcast = 0;
|
|
uint32_t color_input = 0;
|
|
uint32_t one = 0;
|
|
bool unit_alpha_factor = false;
|
|
for (size_t i = 5; i < result.spirv.size(); i += result.spirv[i] >> 16u) {
|
|
const auto opcode = result.spirv[i] & 0xffffu;
|
|
if (opcode == 43u && (result.spirv[i] >> 16u) == 4u &&
|
|
result.spirv[i + 3] == 0x3f800000u) {
|
|
one = result.spirv[i + 2];
|
|
}
|
|
if (opcode == 82u && (result.spirv[i] >> 16u) == 6u &&
|
|
result.spirv[i + 3] == one && result.spirv[i + 4] == alpha_broadcast &&
|
|
result.spirv[i + 5] == 0u) {
|
|
unit_alpha_factor = true;
|
|
}
|
|
if ((result.spirv[i] & 0xffffu) != 79u || (result.spirv[i] >> 16u) != 9u) {
|
|
continue;
|
|
}
|
|
const auto selectors = std::span(result.spirv).subspan(i + 5, 4);
|
|
if (std::ranges::equal(selectors, std::array{3u, 3u, 3u, 3u})) {
|
|
alpha_input = result.spirv[i + 3];
|
|
alpha_broadcast = result.spirv[i + 2];
|
|
} else if (std::ranges::equal(selectors, std::array{3u, 2u, 1u, 0u})) {
|
|
color_input = result.spirv[i + 3];
|
|
}
|
|
}
|
|
Check(alpha_input != 0 && alpha_input == color_input,
|
|
"blend source did not broadcast logical alpha before the physical export swizzle");
|
|
Check(unit_alpha_factor == (mode == ShaderAlphaBlendSource::SourceAlphaOne),
|
|
"secondary ABGR output must be (1, Sa, Sa, Sa) only for separate alpha accumulation");
|
|
}
|
|
}
|
|
|
|
const uint32_t guest_shader[] = {
|
|
EncodeExp0(0, 0xf, false), EncodeExp1(0, 1, 2, 3),
|
|
EncodeExp0(1, 0xf), EncodeExp1(4, 5, 6, 7), EncodeSopp(0x01),
|
|
};
|
|
pixel.alpha_blend_source = ShaderAlphaBlendSource::None;
|
|
pixel.target_output_mode[1] = pixel.target_output_mode[0];
|
|
pixel.target_export_mapping = {};
|
|
const auto guest = RecompileForTest(guest_shader, options);
|
|
CheckSpirvBinaryValidates(guest.spirv);
|
|
const auto source = DisassembleSpirvBinary(guest.spirv);
|
|
Check(source.find("OpDecorate %out_mrt_1 Location 0") != std::string::npos &&
|
|
source.find("OpDecorate %out_mrt_1 Index 1") != std::string::npos &&
|
|
CountSourceOccurrences(source, "OpStore %out_mrt_1 ") == 1 &&
|
|
SpirvInstructionOpcodeCount(guest.spirv, 81u) == 8u,
|
|
"guest dual-source export was replaced by synthetic alpha");
|
|
}
|
|
|
|
void TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled() {
|
|
constexpr uint32_t ExecutionModeEarlyFragmentTests = 9;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeExp0(0x00, 0xf, true, false, true),
|
|
EncodeExp1(0, 1, 2, 3),
|
|
0xbf810000u,
|
|
};
|
|
|
|
ShaderPixelInputInfo ps_info;
|
|
ps_info.ps_early_z = true;
|
|
ps_info.ps_pixel_kill_enable = true;
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &ps_info;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(SpirvInstructionOpcodeCount(result.spirv, 252) != 0,
|
|
"pixel valid-mask export should still lower to OpKill");
|
|
Check(!SpirvContainsExecutionMode(result.spirv,
|
|
ExecutionModeEarlyFragmentTests),
|
|
"pixel shaders that may kill fragments must not request "
|
|
"EarlyFragmentTests");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
const uint32_t ordinary_shader[] = {
|
|
EncodeExp0(0x00, 0xf),
|
|
EncodeExp1(0, 1, 2, 3),
|
|
0xbf810000u,
|
|
};
|
|
ps_info.ps_pixel_kill_enable = false;
|
|
auto ordinary_result = RecompileForTest(ordinary_shader, options);
|
|
const auto ordinary_source = DisassembleSpirvBinary(ordinary_result.spirv);
|
|
Check(SpirvInstructionOpcodeCount(ordinary_result.spirv, 252) == 0,
|
|
"ordinary pixel shader unexpectedly contains OpKill");
|
|
Check((ordinary_source.find("pixel_valid_mask_active") == std::string::npos),
|
|
"ordinary pixel shader allocated pixel-valid state");
|
|
Check(SpirvContainsExecutionMode(ordinary_result.spirv,
|
|
ExecutionModeEarlyFragmentTests),
|
|
"ordinary early-Z pixel shader lost EarlyFragmentTests");
|
|
CheckSpirvBinaryValidates(ordinary_result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerNativeBindingPlan() {
|
|
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
|
|
|
|
const uint32_t shader[] = {
|
|
EncodeSmem0(0x08, 0, 4),
|
|
0u, // s_buffer_load_dword s0
|
|
EncodeVop1(0x01, 0, 0),
|
|
EncodeMubuf0(0x1c, 8),
|
|
EncodeMubuf1(0, 1, 1), // buffer_store_dword
|
|
EncodeMimg0(0x20, 0xf),
|
|
EncodeMimg1(8, 2, 3, 1), // image_sample
|
|
EncodeMubuf0(0x1c, 12),
|
|
EncodeMubuf1(8, 1, 1), // keep sampled value live
|
|
EncodeMimg0(0x08, 0xf),
|
|
EncodeMimg1(12, 5, 0, 1), // image_store
|
|
EncodeMimg0(0x11, 0x1),
|
|
EncodeMimg1(16, 6, 0, 1), // image_atomic_add
|
|
0xbf810000u,
|
|
};
|
|
|
|
auto user_data = ImageTestUserData();
|
|
SetImageTestFormat(&user_data, 6, Prospero::BufferFormat::k32UInt);
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.dump_ir = true;
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
const auto *buffers = ShaderRecompiler::IR::FindBinding(
|
|
result.program.bindings, BindingKind::Buffers);
|
|
const ShaderRecompiler::IR::DescriptorBinding *sampled = nullptr;
|
|
const ShaderRecompiler::IR::DescriptorBinding *storage = nullptr;
|
|
const ShaderRecompiler::IR::DescriptorBinding *atomic_storage = nullptr;
|
|
for (const auto &image : result.program.info.images) {
|
|
const auto kind = ShaderRecompiler::IR::DescriptorBindingForImage(image);
|
|
if (!kind.has_value()) {
|
|
continue;
|
|
}
|
|
const auto *binding =
|
|
ShaderRecompiler::IR::FindBinding(result.program.bindings, *kind);
|
|
if (image.resource_class ==
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled) {
|
|
sampled = binding;
|
|
} else if (image.atomic) {
|
|
atomic_storage = binding;
|
|
} else {
|
|
storage = binding;
|
|
}
|
|
}
|
|
const auto *samplers = ShaderRecompiler::IR::FindBinding(
|
|
result.program.bindings, BindingKind::Samplers);
|
|
Check(buffers != nullptr && buffers->resources.size() == 2,
|
|
"native binding plan did not allocate scalar/vector buffers");
|
|
Check(sampled != nullptr && sampled->resources.size() == 1,
|
|
"native binding plan did not allocate the sampled image");
|
|
Check(storage != nullptr && storage->resources.size() == 1,
|
|
"native binding plan did not allocate the float storage image");
|
|
Check(atomic_storage != nullptr && atomic_storage->resources.size() == 1,
|
|
"native binding plan did not allocate the atomic storage image");
|
|
Check(samplers != nullptr && samplers->resources.size() == 1,
|
|
"native binding plan did not allocate the sampler");
|
|
Check(SpirvContainsOpcode(result.spirv, 86),
|
|
"SPIR-V binary does not combine separate image/sampler descriptors");
|
|
Check(SpirvHasDecorationValue(result.spirv, 33u,
|
|
ShaderRecompiler::IR::NativeBinding(
|
|
result.program.stage, buffers->kind)),
|
|
"SPIR-V lacks storage-buffer Binding decoration");
|
|
Check(SpirvHasDecorationValue(result.spirv, 33u,
|
|
ShaderRecompiler::IR::NativeBinding(
|
|
result.program.stage, sampled->kind)),
|
|
"SPIR-V lacks sampled-image Binding decoration");
|
|
Check(SpirvHasDecorationValue(result.spirv, 33u,
|
|
ShaderRecompiler::IR::NativeBinding(
|
|
result.program.stage, samplers->kind)),
|
|
"SPIR-V lacks sampler Binding decoration");
|
|
Check(SpirvDecorationValueCount(result.spirv, 34u, 0u) ==
|
|
result.program.bindings.descriptors.size(),
|
|
"SPIR-V resources do not all use descriptor set zero");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
ExpectFatal(
|
|
[&] {
|
|
auto malformed = RecompileForTest(shader, options);
|
|
auto malformed_buffers = std::ranges::find_if(
|
|
malformed.program.bindings.descriptors, [](const auto &binding) {
|
|
return binding.kind == BindingKind::Buffers;
|
|
});
|
|
malformed_buffers->resources.clear();
|
|
(void)ShaderRecompiler::Spirv::EmitProgram(malformed.program,
|
|
options.input_info);
|
|
},
|
|
"malformed native binding topology did not terminate SPIR-V emission");
|
|
|
|
ShaderRecompiler::IR::Inst *buffer_handle = nullptr;
|
|
for (auto *block : result.program.blocks) {
|
|
const auto found = std::ranges::find_if(*block, [](const auto &inst) {
|
|
return inst.GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::GetBufferResource;
|
|
});
|
|
if (found != block->end()) {
|
|
buffer_handle = &*found;
|
|
break;
|
|
}
|
|
}
|
|
Check(buffer_handle != nullptr,
|
|
"native validation fixture lacks a buffer handle");
|
|
ExpectFatal(
|
|
[&] {
|
|
buffer_handle->SetFlags(UINT32_MAX);
|
|
(void)ShaderRecompiler::Spirv::EmitProgram(result.program,
|
|
options.input_info);
|
|
},
|
|
"invalid dense buffer resource did not terminate SPIR-V emission");
|
|
#endif
|
|
}
|
|
|
|
void BuildTypedPlan(const uint32_t *code, uint32_t words,
|
|
ShaderRecompiler::IR::Program &ir) {
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{code, words}, decoded);
|
|
auto cfg = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
ShaderComputeInputInfo compute;
|
|
ShaderRecompiler::Frontend::TranslateOptions options{};
|
|
options.stage = ShaderType::Compute;
|
|
options.wave_size = 64u;
|
|
options.input_info.compute = &compute;
|
|
ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options);
|
|
ShaderRecompiler::IR::RewriteToSsa(ir.blocks);
|
|
ShaderRecompiler::IR::ConstantPropagationPass(ir.blocks);
|
|
ShaderRecompiler::IR::ResolveControlFlowIdentities(ir);
|
|
ShaderRecompiler::IR::RemoveIdentities(ir.blocks);
|
|
ShaderRecompiler::IR::EliminateDeadCode(ir.blocks);
|
|
const auto read_lane =
|
|
ShaderRecompiler::IR::EliminateReadLane(ir, ir.wave_size);
|
|
if (read_lane.rewritten_reads != 0) {
|
|
ShaderRecompiler::IR::ConstantPropagationPass(ir.blocks);
|
|
ShaderRecompiler::IR::ResolveControlFlowIdentities(ir);
|
|
ShaderRecompiler::IR::RemoveIdentities(ir.blocks);
|
|
ShaderRecompiler::IR::EliminateDeadCode(ir.blocks);
|
|
}
|
|
ShaderRecompiler::IR::TrackResources(ir, decoded, cfg);
|
|
ShaderRecompiler::IR::EliminateDeadCode(ir.blocks);
|
|
}
|
|
|
|
const ShaderRecompiler::IR::DescriptorSource *
|
|
TypedDescriptorSource(const ShaderRecompiler::IR::Program &program,
|
|
uint32_t source) {
|
|
return source < program.descriptor_sources.size()
|
|
? &program.descriptor_sources[source]
|
|
: nullptr;
|
|
}
|
|
|
|
bool ValueDependsOn(ShaderRecompiler::IR::Value value,
|
|
ShaderRecompiler::IR::ValueOpcode opcode,
|
|
std::vector<const ShaderRecompiler::IR::Inst *> &visited) {
|
|
value = value.Resolve();
|
|
const auto *inst = value.TryInstruction();
|
|
if (inst == nullptr) {
|
|
return false;
|
|
}
|
|
if (inst->GetOpcode() == opcode) {
|
|
return true;
|
|
}
|
|
if (std::ranges::find(visited, inst) != visited.end()) {
|
|
return false;
|
|
}
|
|
visited.push_back(inst);
|
|
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
|
if (ValueDependsOn(inst->Arg(index), opcode, visited)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ValueDependsOn(ShaderRecompiler::IR::Value value,
|
|
ShaderRecompiler::IR::ValueOpcode opcode) {
|
|
std::vector<const ShaderRecompiler::IR::Inst *> visited;
|
|
return ValueDependsOn(value, opcode, visited);
|
|
}
|
|
|
|
void CheckFlattenedReadSlots(const ShaderRecompiler::IR::Program &program,
|
|
uint32_t expected, const char *message) {
|
|
std::vector<uint32_t> slots;
|
|
for (const auto *block : program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() == ShaderRecompiler::IR::ValueOpcode::ReadConst) {
|
|
const auto slot = inst.Arg(1).Resolve();
|
|
Check(slot.IsImmediate() &&
|
|
slot.GetType() == ShaderRecompiler::IR::Type::U32,
|
|
message);
|
|
slots.push_back(slot.U32());
|
|
}
|
|
}
|
|
}
|
|
std::ranges::sort(slots);
|
|
Check(slots.size() == expected, message);
|
|
for (uint32_t index = 0; index < expected; index++) {
|
|
Check(slots[index] == index, message);
|
|
}
|
|
}
|
|
|
|
bool ReadSrtHostDword(void *, uint64_t address, std::span<uint32_t> values) {
|
|
if (address == 0 || values.empty()) {
|
|
return false;
|
|
}
|
|
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
|
|
return true;
|
|
}
|
|
|
|
struct SrtHostRange {
|
|
const uint32_t *data;
|
|
size_t count;
|
|
};
|
|
|
|
bool ReadSrtHostRangeDword(void *userdata, uint64_t address, std::span<uint32_t> values) {
|
|
const auto *range = static_cast<const SrtHostRange *>(userdata);
|
|
if (range == nullptr || range->data == nullptr || range->count == 0 || values.empty()) {
|
|
return false;
|
|
}
|
|
const auto base = reinterpret_cast<uint64_t>(range->data);
|
|
const auto size = range->count * sizeof(uint32_t);
|
|
if (address < base || values.size_bytes() > size || address - base > size - values.size_bytes()) {
|
|
return false;
|
|
}
|
|
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
|
|
return true;
|
|
}
|
|
|
|
void TestTypedDescriptorRealWideMoveTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeSop1(0x04, 0, 20), // s_mov_b64 s[0:1], s[20:21]
|
|
EncodeSop1(0x04, 2, 22), // s_mov_b64 s[2:3], s[22:23]
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 0, 1), // buffer_store_dword via copied s[0:3]
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
|
const auto native = ShaderRecompiler::CFG::BuildGraph(decoded);
|
|
ShaderComputeInputInfo compute;
|
|
auto ir = ShaderRecompiler::Frontend::TranslateProgram(
|
|
decoded, native, {.stage = ShaderType::Compute, .wave_size = 64u,
|
|
.input_info = {.compute = &compute}});
|
|
for (uint32_t reg = 0; reg < 4; ++reg) {
|
|
Check(std::ranges::any_of(ir.scalar_writes, [&](const auto &write) {
|
|
return write.pc == (reg / 2u) * 4u &&
|
|
ShaderRecompiler::IR::RegIndex(write.reg) == reg;
|
|
}), "wide scalar move lost a native descriptor component write");
|
|
}
|
|
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
|
Check(ir.info.buffers.size() == 1,
|
|
"real wide-move shader did not track one buffer use");
|
|
const auto *source = TypedDescriptorSource(ir, ir.info.buffers[0].source);
|
|
Check(source != nullptr && source->dword_count == 4,
|
|
"real wide-move descriptor source was unresolved");
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
const auto *value = source->dwords[i].ResolveInstruction();
|
|
Check(value != nullptr &&
|
|
value->GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::GetUserData &&
|
|
ShaderRecompiler::IR::RegIndex(value->Arg(0).ScalarRegister()) ==
|
|
20 + i,
|
|
"real s_mov_b64 translation did not copy both descriptor pairs");
|
|
}
|
|
}
|
|
|
|
void TestComputeImageFill() {
|
|
using namespace ShaderRecompiler::IR;
|
|
// Captured GTA3 stencil clear: one scalar load and one IMAGE_STORE, with
|
|
// x/y = local + 8 * group and array layer = group.z.
|
|
const uint32_t shader[] = {
|
|
0xd7460000u, 0x0401060cu, 0xf4201a84u, 0xfa000000u,
|
|
0xbf8cc07fu, 0x7e06026au, 0xd7460001u, 0x0405060du,
|
|
0x7e04020eu, 0xf0200128u, 0x00000300u, 0xbf810000u,
|
|
};
|
|
uint32_t value = 0x7bu;
|
|
const auto address = reinterpret_cast<uint64_t>(&value);
|
|
const std::array<uint32_t, 12> userdata = {
|
|
0x20021000u, 0xc0500000u, 0x01d8c347u, 0xd1800204u,
|
|
0u, 0x00700000u, 0u, 0u,
|
|
static_cast<uint32_t>(address),
|
|
static_cast<uint32_t>(address >> 32) | (4u << 16), 1u, 0x14204u,
|
|
};
|
|
ShaderComputeInputInfo compute{};
|
|
compute.threads_num[0] = compute.threads_num[1] = 8;
|
|
compute.threads_num[2] = 1;
|
|
compute.workgroup_register = 12;
|
|
compute.thread_ids_num = 2;
|
|
compute.group_id[0] = compute.group_id[1] = compute.group_id[2] = true;
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.input_info.compute = &compute;
|
|
options.user_data = userdata;
|
|
enum class Mutation { None, Offset, WrongAxis, VaryingValue, Predicate, ExtraStore, A16 };
|
|
const auto Run = [&](Mutation mutation, bool clean = true) {
|
|
auto translated = ShaderRecompiler::TranslateProgram(shader, options);
|
|
auto &program = translated.program;
|
|
for (auto *block : program.blocks) {
|
|
const auto found = std::ranges::find(*block, ValueOpcode::ImageWrite, &Inst::GetOpcode);
|
|
if (found == block->end()) continue;
|
|
auto &store = *found;
|
|
auto *coordinates = store.Arg(1).ResolveInstruction();
|
|
auto *values = store.Arg(2).ResolveInstruction();
|
|
Check(coordinates && values, "stencil fill fixture lost its image operands");
|
|
const auto x = coordinates->Arg(0);
|
|
switch (mutation) {
|
|
case Mutation::None: break;
|
|
case Mutation::Offset:
|
|
coordinates->SetArg(0, Value(&*block->PrependNewInst(found, ValueOpcode::IAdd32,
|
|
{x, Value(1u)})));
|
|
break;
|
|
case Mutation::WrongAxis: coordinates->SetArg(1, x); break;
|
|
case Mutation::VaryingValue: values->SetArg(0, x); break;
|
|
case Mutation::Predicate:
|
|
store.SetArg(3, Value(&*block->PrependNewInst(found, ValueOpcode::ULessThan32,
|
|
{x, Value(32u)})));
|
|
break;
|
|
case Mutation::ExtraStore:
|
|
block->PrependNewInst(found, ValueOpcode::ImageWrite,
|
|
{store.Arg(0), store.Arg(1), store.Arg(2), store.Arg(3)}, store.Flags<uint64_t>());
|
|
break;
|
|
case Mutation::A16:
|
|
program.memory_info[store.Flags<MemoryFlags>().index].image_sample_flags |=
|
|
ShaderRecompiler::Decoder::ImageSampleFlagA16;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
auto plan = ExtractResourcePlan(program);
|
|
ResourceSnapshot snapshot;
|
|
ResourceSpecialization specialization;
|
|
Check(MaterializeResources(plan,
|
|
{.user_data = userdata, .read_memory = ReadHostTestMemory,
|
|
.read_specialization_memory = clean ? ReadHostTestMemory : nullptr},
|
|
snapshot, specialization),
|
|
"captured stencil clear did not materialize");
|
|
const auto &fill = snapshot.uniform_fill;
|
|
const bool expected = mutation == Mutation::None && clean;
|
|
Check((fill.kind == UniformFillKind::Image) == expected,
|
|
"image fill proof accepted an unsafe store or missed the captured clear");
|
|
if (expected) {
|
|
Check(fill.resource == 0 && fill.words == 1 && fill.value == value &&
|
|
fill.group_stride == std::array<uint32_t, 3>{8, 8, 1},
|
|
"image fill lost its scalar value or axis coverage");
|
|
}
|
|
};
|
|
Run(Mutation::None);
|
|
Run(Mutation::None, false);
|
|
for (const auto mutation : {Mutation::Offset, Mutation::WrongAxis, Mutation::VaryingValue,
|
|
Mutation::Predicate, Mutation::ExtraStore, Mutation::A16})
|
|
Run(mutation);
|
|
}
|
|
|
|
void TestGpuProducedWritableDescriptor() {
|
|
using namespace ShaderRecompiler::IR;
|
|
// Reduced from 994af59d50bedca8: the GPU-built BVH header supplies a relative
|
|
// output address and record count. CMPX changes EXEC without clobbering VCC.
|
|
const uint32_t shader[] = {
|
|
0xf4041a81u, 0xfa000028u, // s_load_dwordx2 vcc, s[2:3], 0x28
|
|
0xf4000281u, 0xfa000074u, // s_load_dword s10, s[2:3], 0x74
|
|
0xbe8b03ffu, 0x00016204u, // s_mov_b32 s11, 0x16204
|
|
0xbf8cc07fu, // s_waitcnt lgkmcnt(0)
|
|
0x8008026au, // s_add_u32 s8, vcc_lo, s2
|
|
EncodeVopc(0xd5, 129, 0), // v_cmpx_ne_u32 1, v0
|
|
0x8209036bu, // s_addc_u32 s9, vcc_hi, s3
|
|
0xbe891d97u, // s_bitset1_b32 s9, 23 (stride = 128)
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 2, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const std::array<uint32_t, 4> user_data{0u, 0u, 0x00315000u, 3u};
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.user_data = user_data;
|
|
auto translated = ShaderRecompiler::TranslateProgram(shader, options);
|
|
auto plan = ExtractResourcePlan(translated.program);
|
|
Check(plan.info.buffers.size() == 1 && plan.info.buffers[0].written &&
|
|
!plan.capture_specialization_reads && plan.srt_reads.size() == 3,
|
|
"native GPU-built writable descriptor acquired an unnecessary alias proof");
|
|
struct Reads {
|
|
std::array<uint32_t, 3> header{0x26c0u, 0u, 151u};
|
|
uint32_t ordinary = 0;
|
|
uint32_t strict = 0;
|
|
} reads;
|
|
const SrtRuntime runtime{
|
|
.user_data = user_data,
|
|
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
|
if (words.size() != 1) return false;
|
|
auto &reads = *static_cast<Reads *>(data);
|
|
const uint32_t index = address == 0x300315028ull ? 0u
|
|
: address == 0x30031502cull ? 1u
|
|
: address == 0x300315074ull ? 2u : 3u;
|
|
if (index == 3u) return false;
|
|
++reads.ordinary;
|
|
words[0] = reads.header[index];
|
|
return true;
|
|
},
|
|
.userdata = &reads,
|
|
.read_specialization_memory = +[](void *data, uint64_t, std::span<uint32_t>) {
|
|
++static_cast<Reads *>(data)->strict;
|
|
return false; // Current GPU bytes are available only through the ordinary reader.
|
|
}};
|
|
ResourceSnapshot snapshot;
|
|
ResourceSpecialization specialization;
|
|
const auto check = [&](uint32_t low, uint32_t high, uint32_t count, uint32_t reads_expected) {
|
|
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
|
"GPU-produced direct writable descriptor was rejected");
|
|
Check(snapshot.buffers.size() == 1 &&
|
|
snapshot.buffers[0].dwords[0] == low &&
|
|
snapshot.buffers[0].dwords[1] == high &&
|
|
snapshot.buffers[0].dwords[2] == count &&
|
|
snapshot.buffers[0].dwords[3] == 0x16204u &&
|
|
snapshot.specialization_reads.empty() && reads.strict == 0 &&
|
|
reads.ordinary == reads_expected,
|
|
"GPU-produced descriptor lost current scalar data or repeated its reads");
|
|
};
|
|
check(0x003176c0u, 0x00800003u, 151u, 3u);
|
|
const auto previous_specialization = specialization;
|
|
reads.header = {0xffd00000u, 0u, 302u}; // Exercise the low-address carry on refresh.
|
|
check(0x00015000u, 0x00800004u, 302u, 6u);
|
|
Check(specialization == previous_specialization,
|
|
"GPU-updated address or record count created a shader permutation");
|
|
}
|
|
|
|
void TestTypedDescriptorRealCarryAndScalarLoads() {
|
|
const uint32_t carry_shader[] = {
|
|
EncodeSop1(0x1f, 0, 0), // s_getpc_b64 s[0:1]
|
|
EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
|
|
EncodeSop2(0x04, 1, 1, 128), // s_addc_u32 s1, s1, 0
|
|
EncodeSMovB32(2, 128), // descriptor dword 2 = 0
|
|
EncodeSMovB32(3, 128), // descriptor dword 3 = 0
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 0, 1), // buffer_store_dword via computed s[0:3]
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program carry_ir;
|
|
BuildTypedPlan(carry_shader, static_cast<uint32_t>(std::size(carry_shader)),
|
|
carry_ir);
|
|
const ShaderRecompiler::IR::Inst *carry_handle = nullptr;
|
|
for (const auto *block : carry_ir.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::StoreBufferU32) {
|
|
carry_handle = inst.Arg(0).ResolveInstruction();
|
|
}
|
|
}
|
|
}
|
|
Check(carry_handle != nullptr &&
|
|
carry_handle->GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::GetBufferResource &&
|
|
carry_handle->NumArgs() == 4,
|
|
"real add/addc descriptor source was not attached");
|
|
ShaderRecompiler::IR::DescriptorSource carry_descriptor;
|
|
carry_descriptor.dword_count = 4;
|
|
for (uint32_t index = 0; index < 4; index++) {
|
|
carry_descriptor.dwords[index] = carry_handle->Arg(index).Resolve();
|
|
}
|
|
const auto carry_source_index =
|
|
static_cast<uint32_t>(carry_ir.descriptor_sources.size());
|
|
carry_ir.descriptor_sources.push_back(carry_descriptor);
|
|
const auto *carry_source =
|
|
TypedDescriptorSource(carry_ir, carry_source_index);
|
|
Check(carry_source != nullptr && carry_source->dword_count == 4 &&
|
|
ValueDependsOn(carry_source->dwords[0],
|
|
ShaderRecompiler::IR::ValueOpcode::GetShaderBase) &&
|
|
ValueDependsOn(carry_source->dwords[1],
|
|
ShaderRecompiler::IR::ValueOpcode::GetShaderBase) &&
|
|
ValueDependsOn(carry_source->dwords[1],
|
|
ShaderRecompiler::IR::ValueOpcode::IAddCarry32),
|
|
"real s_add_u32/s_addc_u32 translation lost SCC carry provenance");
|
|
std::array<uint32_t, 64> carry_user_data{};
|
|
const uint64_t shader_base = 0x00000012fffffffbull;
|
|
const uint64_t expected_pc = shader_base + 5u;
|
|
ShaderRecompiler::IR::SrtRuntime carry_runtime{carry_user_data, shader_base,
|
|
nullptr, nullptr};
|
|
ShaderRecompiler::IR::DescriptorValue carry_value;
|
|
Check(ShaderRecompiler::IR::SrtWalker(carry_ir, carry_runtime).EvaluateDescriptor(carry_source_index, carry_value) &&
|
|
carry_value.dwords[0] == static_cast<uint32_t>(expected_pc) &&
|
|
carry_value.dwords[1] == static_cast<uint32_t>(expected_pc >> 32u),
|
|
"S_GETPC shader-base or add/addc carry evaluation was incorrect");
|
|
|
|
const uint32_t load_shader[] = {
|
|
EncodeSmem0(0x02, 0, 4),
|
|
125u << 25u, // s_load_dwordx4 s[0:3], null SOFFSET
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 0, 1),
|
|
EncodeSmem0(0x0a, 4, 4),
|
|
125u << 25u, // s_buffer_load_dwordx4 s[4:7], null SOFFSET
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 1, 1),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program load_ir;
|
|
BuildTypedPlan(load_shader, static_cast<uint32_t>(std::size(load_shader)),
|
|
load_ir);
|
|
Check(load_ir.srt_reads.size() == 8,
|
|
"real scalar loads did not build eight flattened reads");
|
|
uint32_t address_reads = 0;
|
|
uint32_t buffer_reads = 0;
|
|
for (const auto &read : load_ir.srt_reads) {
|
|
const auto *raw = read.value.ResolveInstruction();
|
|
Check(raw != nullptr,
|
|
"real scalar-load SRT entry lost its raw typed value");
|
|
address_reads +=
|
|
raw->GetOpcode() == ShaderRecompiler::IR::ValueOpcode::LoadAddressU32;
|
|
buffer_reads +=
|
|
raw->GetOpcode() == ShaderRecompiler::IR::ValueOpcode::ReadConstBuffer;
|
|
}
|
|
Check(address_reads == 4 && buffer_reads == 4,
|
|
"real scalar loads used the wrong raw typed operations");
|
|
Check(load_ir.info.buffers.size() == 2,
|
|
"real scalar-load descriptor sources were not attached");
|
|
for (const auto &buffer : load_ir.info.buffers) {
|
|
const auto *source = TypedDescriptorSource(load_ir, buffer.source);
|
|
Check(source != nullptr && source->dword_count == 4,
|
|
"real scalar-load descriptor source was not attached");
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
const auto *value = source->dwords[i].ResolveInstruction();
|
|
Check(value != nullptr &&
|
|
value->GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::ReadConst,
|
|
"real scalar load was not flattened in the descriptor source");
|
|
}
|
|
}
|
|
|
|
const uint32_t inline_sampler_shader[] = {
|
|
EncodeSop2(0x25, 12, 128 + 12, 128 + 44), // s_bfm_b64 s[12:13], 12, 44
|
|
EncodeSop1(0x04, 14, 255),
|
|
0x09500000u, // s_mov_b64 s[14:15], 0x09500000
|
|
EncodeMimg0(0xa0, 0xf),
|
|
EncodeMimg1(2, 1, 3, 6), // image_sample_a v2, v6, s[4:11], s[12:15]
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(2, 1,
|
|
1), // keep the sampled value live through a buffer store
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program inline_sampler_ir;
|
|
BuildTypedPlan(inline_sampler_shader,
|
|
static_cast<uint32_t>(std::size(inline_sampler_shader)),
|
|
inline_sampler_ir);
|
|
ShaderRecompiler::IR::DescriptorValue sampler;
|
|
ShaderRecompiler::IR::SrtRuntime runtime;
|
|
Check(inline_sampler_ir.info.samplers.size() == 1 &&
|
|
TypedDescriptorSource(inline_sampler_ir,
|
|
inline_sampler_ir.info.samplers[0].source) !=
|
|
nullptr &&
|
|
ShaderRecompiler::IR::SrtWalker(inline_sampler_ir, runtime).EvaluateDescriptor(inline_sampler_ir.info.samplers[0].source, sampler) &&
|
|
sampler.dwords[0] == 0 && sampler.dwords[1] == 0x00fff000u &&
|
|
sampler.dwords[2] == 0x09500000u && sampler.dwords[3] == 0,
|
|
"real inline sampler construction was unresolved or evaluated "
|
|
"incorrectly");
|
|
|
|
auto user_data = ImageTestUserData();
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.user_data = user_data;
|
|
auto result = RecompileForTest(inline_sampler_shader, options);
|
|
}
|
|
|
|
void TestSrtWalkerRealSmemTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(124, 130), // m0 = 2
|
|
EncodeSmem0(0x02, 0, 4),
|
|
(124u << 25u) | 2u, // s_load_dwordx4 s[0:3], s[8:9], m0 offset +
|
|
// immediate 2
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 0, 1),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
|
Check(ir.srt_reads.size() == 4,
|
|
"real SMEM translation did not build four compact SRT reads");
|
|
|
|
const std::array<uint32_t, 4> table = {0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x44444444u};
|
|
std::array<uint32_t, 16> user_data = {};
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
user_data[8] = static_cast<uint32_t>(address);
|
|
user_data[9] = static_cast<uint32_t>(address >> 32u);
|
|
std::vector<uint32_t> flat;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
|
nullptr};
|
|
const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
|
Check(walked, "SRT walk failed");
|
|
Check(flat.size() == table.size() &&
|
|
std::equal(flat.begin(), flat.end(), table.begin()),
|
|
"real SMEM SRT walk did not apply component-level alignment");
|
|
CheckFlattenedReadSlots(ir, 4, "real SMEM patch used the wrong flat offsets");
|
|
}
|
|
|
|
void TestSrtWalkerVccBaseTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(106, 27), EncodeSMovB32(107, 28),
|
|
EncodeSmem0(0x02, 0, 53), 125u << 25u,
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
|
Check(ir.srt_reads.size() == 4,
|
|
"VCC-based SMEM translation did not build four SRT reads");
|
|
|
|
const std::array<uint32_t, 4> table = {0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x44444444u};
|
|
std::array<uint32_t, 32> user_data{};
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
user_data[27] = static_cast<uint32_t>(address);
|
|
user_data[28] = static_cast<uint32_t>(address >> 32u);
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
|
nullptr};
|
|
std::vector<uint32_t> flat;
|
|
Check(ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
|
Check(flat.size() == table.size() &&
|
|
std::equal(flat.begin(), flat.end(), table.begin()),
|
|
"typed SSA lost an SMEM base copied through VCC");
|
|
}
|
|
|
|
void TestSrtWalkerRealSBufferTranslation() {
|
|
const uint32_t shader[] = {
|
|
EncodeSMovB32(124, 130), // m0 = 2
|
|
EncodeSmem0(0x0a, 0, 4),
|
|
(124u << 25u) | 2u, // s_buffer_load_dwordx4 s[0:3], s[8:11], m0 + 2
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
|
Check(ir.srt_reads.size() == 4,
|
|
"real S_BUFFER_LOAD translation did not build four compact reads");
|
|
|
|
const std::array<uint32_t, 5> table = {0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x44444444u, 0x55555555u};
|
|
std::array<uint32_t, 16> user_data = {};
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
user_data[8] = static_cast<uint32_t>(address);
|
|
user_data[9] = static_cast<uint32_t>(address >> 32u);
|
|
user_data[10] = sizeof(table);
|
|
std::vector<uint32_t> flat;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
|
nullptr};
|
|
const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
|
Check(walked, "SRT walk failed");
|
|
Check(flat.size() == 4 &&
|
|
std::equal(flat.begin(), flat.end(), table.begin()),
|
|
"real S_BUFFER_LOAD walk did not align offset components independently");
|
|
|
|
user_data[10] = 3 * sizeof(uint32_t);
|
|
const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
|
Check(!bounds_walked, "real S_BUFFER_LOAD walk ignored descriptor bounds");
|
|
CheckFlattenedReadSlots(
|
|
ir, 4, "real S_BUFFER_LOAD patch used the wrong flat offsets");
|
|
|
|
const uint32_t negative_shader[] = {
|
|
EncodeSmem0(0x0a, 0, 4),
|
|
(125u << 25u) | 0x1ffffeu, // illegal negative S_BUFFER immediate
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program negative_ir;
|
|
BuildTypedPlan(negative_shader,
|
|
static_cast<uint32_t>(std::size(negative_shader)),
|
|
negative_ir);
|
|
user_data[10] = sizeof(table);
|
|
Check(!ShaderRecompiler::IR::SrtWalker(negative_ir, runtime).RefreshFlatBuffer(flat),
|
|
"real S_BUFFER_LOAD walk accepted a negative immediate");
|
|
}
|
|
|
|
void TestScalarMemorySourcesCapturedBeforeWrites() {
|
|
const auto CheckOverlap = [](uint32_t opcode, bool overlap_offset) {
|
|
const uint32_t base_field = overlap_offset ? 4u : 0u;
|
|
const uint32_t soffset = overlap_offset ? 0u : 125u;
|
|
std::vector<uint32_t> shader;
|
|
if (overlap_offset) {
|
|
shader.push_back(
|
|
EncodeSMovB32(0, 128)); // s0 = 0 before it is overwritten
|
|
}
|
|
shader.insert(shader.end(),
|
|
{EncodeSmem0(opcode, 0, base_field),
|
|
soffset << 25u, // load s[0:3] with overlapping source
|
|
EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1),
|
|
EncodeSopp(0x01)});
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(shader.data(), static_cast<uint32_t>(shader.size()), ir);
|
|
Check(ir.srt_reads.size() == 4,
|
|
opcode == 0x02 ? "overlapping S_LOAD operands were evaluated after a "
|
|
"component write"
|
|
: "overlapping S_BUFFER_LOAD operands were evaluated "
|
|
"after a component write");
|
|
const std::array<uint32_t, 4> table = {0x11111111u, 0x22222222u,
|
|
0x33333333u, 0x44444444u};
|
|
std::array<uint32_t, 12> user_data = {};
|
|
const auto address = reinterpret_cast<uint64_t>(table.data());
|
|
const uint32_t base = overlap_offset ? 8u : 0u;
|
|
user_data[base] = static_cast<uint32_t>(address);
|
|
user_data[base + 1u] = static_cast<uint32_t>(address >> 32u);
|
|
if (opcode == 0x0a) {
|
|
user_data[base + 2u] = sizeof(table);
|
|
}
|
|
SrtHostRange range{table.data(), table.size()};
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
user_data, 0, ReadSrtHostRangeDword, &range};
|
|
std::vector<uint32_t> flat;
|
|
Check(ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
|
Check(flat.size() == table.size() &&
|
|
std::equal(flat.begin(), flat.end(), table.begin()),
|
|
"overlapping scalar-memory load did not capture its sources before "
|
|
"writes");
|
|
CheckFlattenedReadSlots(
|
|
ir, 4, "overlapping scalar-memory patch used the wrong flat offsets");
|
|
};
|
|
CheckOverlap(0x02, false); // s_load_dwordx4 overlapping SBASE
|
|
CheckOverlap(0x0a, false); // s_buffer_load_dwordx4 overlapping SBASE
|
|
CheckOverlap(0x02, true); // s_load_dwordx4 overlapping SOFFSET
|
|
CheckOverlap(0x0a, true); // s_buffer_load_dwordx4 overlapping SOFFSET
|
|
}
|
|
|
|
void TestScalarMemoryLoadCrossesIntoVcc() {
|
|
const uint32_t shader[] = {
|
|
EncodeSmem0(0x02, 104, 4),
|
|
125u << 25u, // s_load_dwordx4 s[104:105], vcc_lo, vcc_hi
|
|
EncodeSopp(0x07,
|
|
2), // consume both VCC dwords and skip the store when nonzero
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 26, 1),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
|
Check(ir.srt_reads.size() == 4,
|
|
"wide SMEM destination crossing into VCC lost scalar provenance");
|
|
CheckFlattenedReadSlots(
|
|
ir, 4, "wide SMEM destination crossing into VCC used wrong flat offsets");
|
|
Check(ir.info.buffers.size() == 1,
|
|
"wide SMEM destination crossing into VCC lost its buffer use");
|
|
const auto *source =
|
|
TypedDescriptorSource(ir, ir.info.buffers.front().source);
|
|
Check(source != nullptr && source->dword_count == 4,
|
|
"wide SMEM destination crossing into VCC lost its descriptor source");
|
|
for (uint32_t dword = 0; dword < 4; dword++) {
|
|
const auto *value = source->dwords[dword].ResolveInstruction();
|
|
Check(value != nullptr && value->GetOpcode() ==
|
|
ShaderRecompiler::IR::ValueOpcode::ReadConst,
|
|
"wide SMEM descriptor lost a dword crossing into VCC");
|
|
}
|
|
}
|
|
|
|
void TestScalarMemoryUnusedTailDce() {
|
|
const uint32_t shader[] = {
|
|
EncodeSmem0(0x0c, 16, 0),
|
|
4u << 25u, // s_buffer_load_dwordx16 s[16:31], s[0:3], s4
|
|
EncodeVop1(0x01, 0, 16), // v_mov_b32 v0, s16
|
|
EncodeMubuf0(0x1c),
|
|
EncodeMubuf1(0, 2, 1), // keep only the first loaded component live
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
|
uint32_t reads = 0;
|
|
for (const auto *block : ir.blocks) {
|
|
for (const auto &inst : *block) {
|
|
if (inst.GetOpcode() !=
|
|
ShaderRecompiler::IR::ValueOpcode::ReadConstBuffer) {
|
|
continue;
|
|
}
|
|
reads++;
|
|
const auto flags = inst.Flags<ShaderRecompiler::IR::MemoryFlags>();
|
|
Check(flags.index < ir.memory_info.size() &&
|
|
ir.memory_info[flags.index].component_index == 0u &&
|
|
ir.memory_info[flags.index].component_count == 16u,
|
|
"live x16 SMEM component lost its native-width metadata");
|
|
}
|
|
}
|
|
Check(
|
|
reads == 1u,
|
|
"grouped x16 SMEM prevented dead component reads from being eliminated");
|
|
}
|
|
|
|
void TestResourceTrackingRealDensePatching() {
|
|
const uint32_t shader[] = {
|
|
EncodeMubuf0(0x0c),
|
|
EncodeMubuf1(0, 0, 1), // buffer load s[0:3]
|
|
EncodeMubuf0(0x0c),
|
|
EncodeMubuf1(1, 0, 1), // same buffer
|
|
EncodeMubuf0(0x1d),
|
|
EncodeMubuf1(0, 1, 1), // keep both loads live in s[4:7]
|
|
EncodeMimg0(0x20, 0xf),
|
|
EncodeMimg1(8, 0, 2, 1), // sampled image s[0:7], sampler s[8:11]
|
|
EncodeMimg0(0x08, 0xf),
|
|
EncodeMimg1(8, 0, 0, 1), // keep sample live in storage view
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
|
Check(ir.info.buffers.size() == 2 && ir.info.images.size() == 2 &&
|
|
ir.info.samplers.size() == 1,
|
|
"real resource tracking produced the wrong dense list sizes");
|
|
|
|
uint32_t buffer_use = 0;
|
|
uint32_t image_use = 0;
|
|
for (const auto *block : ir.blocks) {
|
|
for (const auto &inst : *block) {
|
|
const auto op = inst.GetOpcode();
|
|
switch (op) {
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU8:
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU16:
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32:
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32x2:
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32x3:
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32x4:
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU8:
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU16:
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32:
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32x2:
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32x3:
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32x4: {
|
|
const auto &memory =
|
|
ir.memory_info
|
|
[inst.Flags<ShaderRecompiler::IR::MemoryFlags>().index];
|
|
const uint32_t expected = buffer_use++ < 2 ? 0 : 1;
|
|
const auto *handle = inst.Arg(0).ResolveInstruction();
|
|
Check(memory.resource == expected && handle != nullptr &&
|
|
handle->Flags<uint32_t>() == expected,
|
|
"real buffer operand was not patched densely");
|
|
break;
|
|
}
|
|
case ShaderRecompiler::IR::ValueOpcode::ImageSampleRaw:
|
|
case ShaderRecompiler::IR::ValueOpcode::ImageWrite: {
|
|
const auto &memory =
|
|
ir.memory_info
|
|
[inst.Flags<ShaderRecompiler::IR::MemoryFlags>().index];
|
|
const auto expected = image_use++;
|
|
const auto *handle = inst.Arg(0).ResolveInstruction();
|
|
Check(memory.resource == expected && handle != nullptr &&
|
|
handle->Flags<uint32_t>() == expected,
|
|
"real image operand was not patched by view class");
|
|
if (op == ShaderRecompiler::IR::ValueOpcode::ImageSampleRaw) {
|
|
const auto *sampler = inst.Arg(1).ResolveInstruction();
|
|
Check(memory.sampler == 0 && sampler != nullptr &&
|
|
sampler->Flags<uint32_t>() == 0,
|
|
"real sampler operand was not patched densely");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Check(buffer_use == 3 && image_use == 2 && ir.info.buffers[0].read &&
|
|
ir.info.buffers[1].written,
|
|
"real tracked resource access facts were incomplete");
|
|
}
|
|
|
|
void TestDirectTranslationResetsAnalysisState() {
|
|
const uint32_t first_shader[] = {EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1),
|
|
EncodeSopp(0x01)};
|
|
ShaderRecompiler::IR::Program ir;
|
|
BuildTypedPlan(first_shader, static_cast<uint32_t>(std::size(first_shader)),
|
|
ir);
|
|
ShaderComputeInputInfo compute;
|
|
ShaderRecompiler::IR::CollectShaderInfo(ir, {.compute = &compute});
|
|
Check(ir.resource_tracking_complete && ir.shader_info_complete &&
|
|
!ir.info.buffers.empty(),
|
|
"analysis-reset fixture did not reach completed state");
|
|
ir.shader_hash = 0xdeadbeef;
|
|
|
|
const uint32_t second_shader[] = {EncodeSopp(0x01)};
|
|
ShaderRecompiler::Decoder::Program decoded;
|
|
ShaderRecompiler::Decoder::DecodeProgram(std::span{second_shader}, decoded);
|
|
ir = ShaderRecompiler::Frontend::TranslateProgram(
|
|
decoded, ShaderRecompiler::CFG::BuildGraph(decoded),
|
|
{.stage = ShaderType::Compute, .input_info = {.compute = &compute}});
|
|
Check(!ir.resource_tracking_complete && !ir.shader_info_complete &&
|
|
!ir.srt_plan_complete &&
|
|
ir.srt_reads.empty() && ir.shader_hash == 0 &&
|
|
ir.info.buffers.empty() && ir.info.images.empty() &&
|
|
ir.info.samplers.empty() && ir.info.sampled_pairs.empty() &&
|
|
ir.info.inputs.empty() && ir.info.outputs.empty(),
|
|
"direct translation reused stale provenance/resource/interface state");
|
|
}
|
|
|
|
void TestNewShaderRecompilerStageInputInfo() {
|
|
using StageInputKind = ShaderRecompiler::IR::StageInputKind;
|
|
|
|
const uint32_t shader[] = {0xbf810000u};
|
|
|
|
ShaderComputeInputInfo cs_info{};
|
|
cs_info.group_id[0] = true;
|
|
cs_info.thread_ids_num = 3;
|
|
cs_info.dispatch_thread_dimensions = true;
|
|
|
|
auto cs_options = MakeCompileOptions(ShaderType::Compute);
|
|
cs_options.input_info.compute = &cs_info;
|
|
|
|
auto cs_result = RecompileForTest(shader, cs_options);
|
|
Check(ProgramHasInput(cs_result.program, StageInputKind::WorkgroupId),
|
|
"compute WorkgroupId input missing from reflection");
|
|
Check(ProgramHasInput(cs_result.program, StageInputKind::LocalInvocationId),
|
|
"compute LocalInvocationId input missing from reflection");
|
|
Check(
|
|
ProgramHasInput(cs_result.program, StageInputKind::LocalInvocationIndex),
|
|
"compute LocalInvocationIndex input missing from reflection");
|
|
Check(ProgramHasInput(cs_result.program, StageInputKind::GlobalInvocationId),
|
|
"compute GlobalInvocationId input missing from reflection");
|
|
Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 26u),
|
|
"SPIR-V lacks WorkgroupId BuiltIn decoration");
|
|
Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 27u),
|
|
"SPIR-V lacks LocalInvocationId BuiltIn decoration");
|
|
Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 28u),
|
|
"SPIR-V lacks GlobalInvocationId BuiltIn decoration");
|
|
Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 29u),
|
|
"SPIR-V lacks LocalInvocationIndex BuiltIn decoration");
|
|
CheckSpirvBinaryValidates(cs_result.spirv);
|
|
|
|
auto vs_options = MakeCompileOptions(ShaderType::Vertex);
|
|
|
|
auto vs_result = RecompileForTest(shader, vs_options);
|
|
Check(ProgramHasInput(vs_result.program, StageInputKind::VertexIndex),
|
|
"vertex VertexIndex input missing from reflection");
|
|
Check(ProgramHasInput(vs_result.program, StageInputKind::InstanceIndex),
|
|
"vertex InstanceIndex input missing from reflection");
|
|
Check(SpirvHasDecorationValue(vs_result.spirv, 11u, 42u),
|
|
"SPIR-V lacks VertexIndex BuiltIn decoration");
|
|
Check(SpirvHasDecorationValue(vs_result.spirv, 11u, 43u),
|
|
"SPIR-V lacks InstanceIndex BuiltIn decoration");
|
|
CheckSpirvBinaryValidates(vs_result.spirv);
|
|
|
|
ShaderPixelInputInfo ps_info{};
|
|
ps_info.input_num = 2;
|
|
ps_info.ps_pos_x = true;
|
|
ps_info.ps_pos_y = true;
|
|
ps_info.ps_pos_z = true;
|
|
SetIdentityInterpolatorSettings(&ps_info);
|
|
|
|
auto ps_options = MakeCompileOptions(ShaderType::Pixel);
|
|
ps_options.input_info.pixel = &ps_info;
|
|
|
|
auto ps_result = RecompileForTest(shader, ps_options);
|
|
Check(ProgramHasInput(ps_result.program, StageInputKind::FragCoord),
|
|
"pixel FragCoord input missing from reflection");
|
|
Check(ProgramInputCount(ps_result.program, StageInputKind::Parameter) == 2,
|
|
"pixel interpolant inputs missing from reflection");
|
|
Check(SpirvHasDecorationValue(ps_result.spirv, 11u, 15u),
|
|
"SPIR-V lacks FragCoord BuiltIn decoration");
|
|
Check(SpirvHasDecorationValue(ps_result.spirv, 30u, 0u),
|
|
"SPIR-V lacks interpolant Location 0 decoration");
|
|
Check(SpirvHasDecorationValue(ps_result.spirv, 30u, 1u),
|
|
"SPIR-V lacks interpolant Location 1 decoration");
|
|
CheckSpirvBinaryValidates(ps_result.spirv);
|
|
|
|
ShaderPixelInputInfo ps_pos_y_info{};
|
|
ps_pos_y_info.input_num = 1;
|
|
ps_pos_y_info.ps_system_input_base = 2;
|
|
ps_pos_y_info.ps_pos_y = true;
|
|
SetIdentityInterpolatorSettings(&ps_pos_y_info);
|
|
ps_options.input_info.pixel = &ps_pos_y_info;
|
|
|
|
auto ps_pos_y_result = RecompileForTest(shader, ps_options);
|
|
Check(ProgramHasInput(ps_pos_y_result.program, StageInputKind::FragCoord),
|
|
"pixel POS_Y-only FragCoord input missing from reflection");
|
|
Check(SpirvHasDecorationValue(ps_pos_y_result.spirv, 11u, 15u),
|
|
"SPIR-V lacks POS_Y-only FragCoord BuiltIn decoration");
|
|
CheckSpirvBinaryValidates(ps_pos_y_result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerPixelPipelineEntry() {
|
|
const uint32_t shader[] = {0xbf810000u};
|
|
|
|
EnsureConfigInitialized();
|
|
|
|
HW::PixelShaderInfo regs{};
|
|
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
|
|
ShaderMappedData mapped{};
|
|
mapped.code_size_bytes = sizeof(shader);
|
|
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
|
|
|
HW::ShaderRegisters sh{};
|
|
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
|
|
ShaderPixelInputInfo input_info{};
|
|
const auto params = PrepareProgram(regs, sh, mappings, input_info);
|
|
Check(params.hash == XXH3_64bits(params.code.data(), params.code.size_bytes()) &&
|
|
params.code.data() == shader,
|
|
"pixel shader program parameters lost source identity");
|
|
|
|
const uint32_t vcc_load_shader[] = {
|
|
EncodeSMovB32(106, 27), EncodeSMovB32(107, 28), EncodeSmem0(0x02, 44, 53),
|
|
(0x7du << 25u) | 160u, EncodeMubuf0(0x0c), EncodeMubuf1(0, 11, 1),
|
|
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0), EncodeSopp(0x01),
|
|
};
|
|
std::array<uint32_t, 44> table{};
|
|
std::array<uint32_t, 1> buffer{};
|
|
const auto table_address = reinterpret_cast<uint64_t>(table.data());
|
|
const auto buffer_address = reinterpret_cast<uint64_t>(buffer.data());
|
|
table[40] = static_cast<uint32_t>(buffer_address);
|
|
table[41] = static_cast<uint32_t>(buffer_address >> 32u);
|
|
table[42] = 1;
|
|
|
|
HW::PixelShaderInfo vcc_regs{};
|
|
vcc_regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(vcc_load_shader);
|
|
vcc_regs.ps_regs.rsrc2.user_sgpr = 29;
|
|
vcc_regs.ps_user_sgpr.value[27] = static_cast<uint32_t>(table_address);
|
|
vcc_regs.ps_user_sgpr.value[28] = static_cast<uint32_t>(table_address >> 32u);
|
|
ShaderMappedData vcc_mapped{};
|
|
vcc_mapped.code_size_bytes = sizeof(vcc_load_shader);
|
|
ShaderMapUserData(vcc_regs.ps_regs.data_addr, vcc_mapped);
|
|
|
|
ShaderPixelInputInfo vcc_input{};
|
|
const auto vcc_params = PrepareProgram(vcc_regs, sh, mappings, vcc_input);
|
|
CompilePixelRuntime(vcc_params, vcc_input);
|
|
}
|
|
|
|
void TestComputeLdsAllocationIdentity() {
|
|
const uint32_t shader[] = {
|
|
EncodeDs0(0x0d, 4288u), // ds_write_b32 v0, v1 offset:4288
|
|
EncodeDs1(0, 1, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
HW::ComputeShaderInfo regs{};
|
|
regs.cs_regs.data_addr = reinterpret_cast<uint64_t>(shader);
|
|
regs.cs_regs.num_thread_x = 64;
|
|
regs.cs_regs.num_thread_y = 1;
|
|
regs.cs_regs.num_thread_z = 1;
|
|
ShaderMappedData mapped{};
|
|
mapped.code_size_bytes = sizeof(shader);
|
|
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
|
|
HW::ShaderRegisters sh{};
|
|
|
|
const auto compile = [&](uint16_t encoded_lds_size,
|
|
uint32_t expected_dwords) {
|
|
regs.cs_regs.lds_size = encoded_lds_size;
|
|
ShaderComputeInputInfo input_info{};
|
|
const auto params = PrepareProgram(regs, sh, input_info);
|
|
Check(input_info.lds_size_dwords == expected_dwords,
|
|
"COMPUTE_PGM_RSRC2 LDS allocation units were not decoded");
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.shader_hash = params.hash;
|
|
options.user_data = std::span(params.user_data).first(params.user_data_count);
|
|
options.input_info.compute = &input_info;
|
|
options.wave_size = input_info.wave_size;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(MeasureSpirv(result.spirv).workgroup_variables == 1u,
|
|
"LDS shader did not declare exactly one Workgroup variable");
|
|
Check(SpirvArrayLengthCount(result.spirv, expected_dwords) == 1u,
|
|
"SPIR-V workgroup array did not use the declared LDS allocation");
|
|
Check(SpirvUnsignedLessThanBoundCount(result.spirv, expected_dwords) == 1u,
|
|
"LDS bounds check did not use the declared allocation");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
return input_info;
|
|
};
|
|
|
|
constexpr uint32_t lds_1152_rsrc2 = 0x00048188u;
|
|
constexpr uint32_t lds_896_rsrc2 = 0x00038188u;
|
|
constexpr auto decode_lds_field = [](uint32_t rsrc2) {
|
|
return static_cast<uint16_t>(
|
|
(rsrc2 >> Pm4::COMPUTE_PGM_RSRC2_LDS_SIZE_SHIFT) &
|
|
Pm4::COMPUTE_PGM_RSRC2_LDS_SIZE_MASK);
|
|
};
|
|
const auto lds_1152 = compile(decode_lds_field(lds_1152_rsrc2), 1152u);
|
|
const auto lds_896 = compile(decode_lds_field(lds_896_rsrc2), 896u);
|
|
Check(MakeStageStaticKey(lds_1152) != MakeStageStaticKey(lds_896),
|
|
"compute pipeline identity omitted the LDS allocation");
|
|
|
|
auto split_wave = lds_896;
|
|
split_wave.host_subgroup_size = 32;
|
|
Check(MakeStageStaticKey(lds_896) != MakeStageStaticKey(split_wave),
|
|
"compute shader identity omitted the host subgroup size");
|
|
|
|
auto tg_size_disabled = lds_896;
|
|
auto tg_size_enabled = lds_896;
|
|
tg_size_enabled.tg_size_en = true;
|
|
Check(MakeStageStaticKey(tg_size_disabled) !=
|
|
MakeStageStaticKey(tg_size_enabled),
|
|
"compute shader identity omitted TG_SIZE semantics");
|
|
|
|
auto dispatch_disabled = lds_896;
|
|
dispatch_disabled.dispatch_threads_num[0] = 64;
|
|
dispatch_disabled.dispatch_threads_num[1] = 32;
|
|
dispatch_disabled.dispatch_threads_num[2] = 1;
|
|
auto dispatch_enabled = dispatch_disabled;
|
|
dispatch_enabled.dispatch_thread_dimensions = true;
|
|
Check(MakeStageStaticKey(dispatch_disabled) !=
|
|
MakeStageStaticKey(dispatch_enabled),
|
|
"dispatch-dimension mode did not select a compute shader variant");
|
|
|
|
auto second_extent = dispatch_enabled;
|
|
second_extent.dispatch_threads_num[0] = 1920;
|
|
second_extent.dispatch_threads_num[1] = 1080;
|
|
second_extent.dispatch_threads_num[2] = 4;
|
|
Check(MakeStageStaticKey(dispatch_enabled) ==
|
|
MakeStageStaticKey(second_extent),
|
|
"runtime dispatch extent created a compute shader variant");
|
|
|
|
mapped.scratch_size_dwords = 7;
|
|
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
|
|
regs.cs_regs.lds_size = decode_lds_field(lds_896_rsrc2);
|
|
ShaderComputeInputInfo scratch_info{};
|
|
const auto scratch_params = PrepareProgram(regs, sh, scratch_info);
|
|
Check(scratch_info.scratch_size_dwords == 7,
|
|
"AGC per-thread scratch size was not propagated");
|
|
auto scratch_options = MakeCompileOptions(ShaderType::Compute);
|
|
scratch_options.shader_hash = scratch_params.hash;
|
|
scratch_options.user_data = std::span(scratch_params.user_data).first(scratch_params.user_data_count);
|
|
scratch_options.input_info.compute = &scratch_info;
|
|
scratch_options.wave_size = scratch_info.wave_size;
|
|
|
|
auto scratch_result =
|
|
RecompileForTest(scratch_params.code, scratch_options);
|
|
Check(scratch_result.program.scratch_dwords == 7,
|
|
"AGC per-thread scratch size did not reach the compiler program");
|
|
Check(MakeStageStaticKey(scratch_info) != MakeStageStaticKey(lds_896),
|
|
"compute pipeline identity omitted the scratch allocation");
|
|
|
|
const uint32_t append_shader[] = {
|
|
EncodeSMovB32(124, 132), // m0 = 4 bytes
|
|
EncodeDs0(0x3e),
|
|
EncodeDs1(1, 0, 0), // ds_append v1
|
|
EncodeSopp(0x01),
|
|
};
|
|
ShaderComputeInputInfo append_info = RegressionComputeInputInfo();
|
|
append_info.lds_size_dwords = 1152u;
|
|
auto append_options = MakeCompileOptions(ShaderType::Compute);
|
|
append_options.input_info.compute = &append_info;
|
|
auto append_result =
|
|
RecompileForTest(append_shader, append_options);
|
|
Check(SpirvUnsignedLessThanBoundCount(append_result.spirv, 1152u) == 1u,
|
|
"typed LDS append omitted the declared allocation bound");
|
|
CheckSpirvBinaryValidates(append_result.spirv);
|
|
}
|
|
|
|
void TestPixelProgramCacheBindingIdentity() {
|
|
const uint32_t shader_01[] = {0xbf810000u};
|
|
const uint32_t shader_10[] = {0xbf810000u};
|
|
HW::ShaderRegisters sh{};
|
|
|
|
ShaderPixelInputInfo no_depth_export{};
|
|
auto depth_export = no_depth_export;
|
|
depth_export.ps_depth_export_enable = true;
|
|
Check(MakeStageStaticKey(no_depth_export) != MakeStageStaticKey(depth_export),
|
|
"pixel shader identity omitted depth-export semantics");
|
|
|
|
auto check_program_identity = [&](std::span<const uint32_t> shader) {
|
|
HW::PixelShaderInfo regs{};
|
|
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader.data());
|
|
ShaderMappedData mapped{};
|
|
mapped.code_size_bytes = static_cast<uint32_t>(shader.size_bytes());
|
|
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
|
|
|
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
|
|
ShaderPixelInputInfo first_info{};
|
|
const auto first_params = PrepareProgram(regs, sh, identity_mappings,
|
|
first_info);
|
|
const auto first_key = MakeStageStaticKey(first_info);
|
|
CompilePixelRuntime(first_params, first_info);
|
|
|
|
ShaderPixelInputInfo second_info{};
|
|
const auto second_params = PrepareProgram(regs, sh, identity_mappings,
|
|
second_info);
|
|
CompilePixelRuntime(second_params, second_info);
|
|
Check(
|
|
first_params.hash == XXH3_64bits(shader.data(), shader.size_bytes()) &&
|
|
first_params.hash == second_params.hash &&
|
|
first_key == MakeStageStaticKey(second_info) &&
|
|
first_info.stage.program != nullptr &&
|
|
second_info.stage.program != nullptr,
|
|
"pixel program matching did not preserve an identical source/static identity");
|
|
return std::pair {first_params.hash, first_key};
|
|
};
|
|
|
|
const auto request_01 = check_program_identity(shader_01);
|
|
const auto request_10 = check_program_identity(shader_10);
|
|
Check(request_01 == request_10,
|
|
"relocated identical pixel programs did not share their source identity");
|
|
|
|
// Identical metadata after the scalar-literal prefix must not mask changes
|
|
// to the executable code when computing program identity.
|
|
std::array<uint32_t, 200> shader{};
|
|
shader[0] = 0xbeeb03ffu;
|
|
shader[1] = 0x5fu;
|
|
shader[2] = EncodeSMovB32(0, 129);
|
|
shader[3] = EncodeSopp(0x01);
|
|
shader[192] = 0x65726162u;
|
|
shader[193] = 0x746f6f66u;
|
|
shader[196] = 0x12345678u;
|
|
shader[197] = 0x9abcdef0u;
|
|
const auto first = check_program_identity(shader);
|
|
shader[2] = EncodeSMovB32(0, 130);
|
|
Check(check_program_identity(shader).first != first.first,
|
|
"re-registering changed code at the same address reused its old identity");
|
|
|
|
}
|
|
|
|
void TestGraphicsPushConstantPlacement() {
|
|
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
|
|
constexpr uint32_t OffsetDecoration = 35;
|
|
const uint32_t shader[] = {
|
|
EncodeVop1(0x01, 0, 0), EncodeVop1(0x01, 1, 1),
|
|
EncodeVop1(0x01, 2, 2), EncodeVop1(0x01, 3, 3),
|
|
EncodeExp0(0x00, 0xf), EncodeExp1(0, 1, 2, 3), 0xbf810000u,
|
|
};
|
|
const std::array<uint32_t, 4> user_data = {
|
|
0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u};
|
|
ShaderPixelInputInfo pixel_info{};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.input_info.pixel = &pixel_info;
|
|
options.user_data = user_data;
|
|
|
|
constexpr uint32_t PushDataStart = 7;
|
|
auto placed =
|
|
RecompileForTest(shader, options, nullptr, nullptr, PushDataStart);
|
|
Check(placed.program.bindings.UsesPushData() &&
|
|
placed.program.bindings.push_data_start_dword == PushDataStart &&
|
|
ShaderRecompiler::IR::FindBinding(placed.program.bindings,
|
|
BindingKind::ShaderData) == nullptr,
|
|
"pixel shader did not use its assigned shared push-data position");
|
|
Check(SpirvHasMemberDecorationValue(placed.spirv, OffsetDecoration, 0),
|
|
"SPIR-V push-data block did not use the canonical zero base");
|
|
CheckSpirvBinaryValidates(placed.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerUnsupportedMemoryDecode() {
|
|
const uint32_t mubuf_unknown[] = {EncodeMubuf0(0x7b), EncodeMubuf1(0, 0, 1),
|
|
0xbf810000u};
|
|
CheckNewDecoderUnsupported(mubuf_unknown,
|
|
static_cast<uint32_t>(std::size(mubuf_unknown)),
|
|
"MUBUF", "opcode=0x7b");
|
|
|
|
const uint32_t flat_unknown[] = {EncodeFlat0(0x7e, 0, 4),
|
|
EncodeFlat1(9, 0x7d, 0, 1), 0xbf810000u};
|
|
CheckNewDecoderUnsupported(flat_unknown,
|
|
static_cast<uint32_t>(std::size(flat_unknown)),
|
|
"FLAT", "opcode=0x7e");
|
|
|
|
const uint32_t mtbuf_unknown[] = {EncodeMtbuf0(0x08, 14, 7, 4),
|
|
EncodeMtbuf1(0x08, 9, 0, 1), 0xbf810000u};
|
|
CheckNewDecoderUnsupported(mtbuf_unknown,
|
|
static_cast<uint32_t>(std::size(mtbuf_unknown)),
|
|
"MTBUF", "opcode=0x08");
|
|
|
|
const uint32_t bpermute_gds[] = {
|
|
EncodeDs0(0xb3) | (1u << 17u), EncodeDs1(1, 3, 17), 0xbf810000u};
|
|
CheckNewDecoderUnsupported(bpermute_gds,
|
|
static_cast<uint32_t>(std::size(bpermute_gds)),
|
|
"DS", "available only for LDS");
|
|
}
|
|
|
|
void TestNewShaderRecompilerFlatUserPointerUsesDma() {
|
|
const uint32_t shader[] = {
|
|
EncodeVop2(0x25, 0, 0, 2), // v_add_nc_u32 v0, s0, v2
|
|
EncodeVop1(0x01, 1, 1), // v_mov_b32 v1, s1
|
|
EncodeFlat0(0x0c, 0, 0),
|
|
EncodeFlat1(3, 0x7d, 0, 0), // flat_load_dword v3, v[0:1]
|
|
EncodeExp0(0x00, 0x1),
|
|
EncodeExp1(3, 0, 0, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const uint32_t user_data[] = {0x34567000u, 0x00000012u};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Pixel);
|
|
options.user_data = user_data;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
Check(result.program.info.uses_dma,
|
|
"FLAT user pointer did not enable DMA");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
}
|
|
|
|
void TestNewShaderRecompilerFlatAddressDomainsUseDma() {
|
|
const uint32_t segmented_shader[] = {
|
|
EncodeVop2(0x25, 0, 0, 2),
|
|
EncodeVop1(0x01, 1, 1),
|
|
EncodeFlat0(0x0c, 2, 0),
|
|
EncodeFlat1(3, 0x7d, 0, 0), // global_load_dword v3, v[0:1]
|
|
EncodeFlat0(0x1c, 1, 0),
|
|
EncodeFlat1(0, 0x7d, 3, 0), // scratch_store_dword v3, v[0:1]
|
|
0xbf810000u,
|
|
};
|
|
const uint32_t user_data[] = {0x34567000u, 0u, 0x1000u};
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.user_data = user_data;
|
|
ShaderComputeInputInfo compute{};
|
|
compute.scratch_size_dwords = 1;
|
|
options.input_info.compute = &compute;
|
|
|
|
auto result = RecompileForTest(segmented_shader, options);
|
|
Check(result.program.info.uses_dma,
|
|
"GLOBAL null-SADDR did not enable DMA");
|
|
Check((result.ir_dump.find("GetScratchResource") != std::string::npos) &&
|
|
result.program.scratch_dwords == 1,
|
|
"SCRATCH incorrectly entered guest address tracking");
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
// A local aperture proof must not admit a median which can still produce a
|
|
// canonical global address. Such stores need the existing ownership proof.
|
|
const uint32_t mixed_domain_store[] = {
|
|
EncodeSop1(0x04, 8, 237), // s_mov_b64 s[8:9], private_base
|
|
EncodeVop3Word0(0x159, 1),
|
|
EncodeVop3Word1(128, 9, 256 + 2), // median(0, private high, dynamic v2)
|
|
EncodeFlat0(0x1c, 0, 0), EncodeFlat1(0, 0x7d, 3, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ExpectFatal([&]() { (void)RecompileForTest(mixed_domain_store, options); },
|
|
"mixed local/global FLAT store bypassed GPU ownership tracking");
|
|
const uint32_t changed_mask_store[] = {
|
|
EncodeSop1(0x04, 8, 237), EncodeSop1(0x04, 10, 235),
|
|
EncodeVop1(0x01, 1, 128), // canonical old address high
|
|
EncodeSop1(0x03, 126, 255), 0x55555555u,
|
|
EncodeVop3Word0(0x159, 1), EncodeVop3Word1(9, 11, 256 + 2),
|
|
EncodeSop1(0x04, 126, 193), // restore all lanes after the median
|
|
EncodeFlat0(0x1c, 0, 0), EncodeFlat1(0, 0x7d, 3, 0),
|
|
EncodeSopp(0x01),
|
|
};
|
|
ExpectFatal([&]() { (void)RecompileForTest(changed_mask_store, options); },
|
|
"FLAT proof discarded a differently predicated address value");
|
|
#endif
|
|
}
|
|
|
|
void TestCompilerStageInputOwnership() {
|
|
using namespace ShaderRecompiler;
|
|
const uint32_t shader[] = {EncodeSopp(0x01)};
|
|
ShaderVertexInputInfo vertex{};
|
|
vertex.scratch_size_dwords = 3;
|
|
vertex.mesh.scratch_size_dwords = 11;
|
|
vertex.mesh.threads_num[0] = 192;
|
|
vertex.mesh.threads_num[1] = vertex.mesh.threads_num[2] = 1;
|
|
vertex.mesh.primitives_per_group = 62;
|
|
vertex.mesh.vertices_per_group = 64;
|
|
ShaderPixelInputInfo pixel{};
|
|
pixel.scratch_size_dwords = 5;
|
|
ShaderComputeInputInfo compute{};
|
|
compute.scratch_size_dwords = 7;
|
|
|
|
for (const auto stage : {ShaderType::Vertex, ShaderType::Pixel,
|
|
ShaderType::Compute, ShaderType::Mesh}) {
|
|
CompileOptions options{};
|
|
options.stage = stage;
|
|
options.dump_ir = false;
|
|
uint32_t expected_scratch = 0;
|
|
switch (stage) {
|
|
case ShaderType::Vertex:
|
|
options.input_info.vertex = &vertex;
|
|
expected_scratch = 3;
|
|
break;
|
|
case ShaderType::Pixel:
|
|
options.input_info.pixel = &pixel;
|
|
expected_scratch = 5;
|
|
break;
|
|
case ShaderType::Compute:
|
|
options.input_info.compute = &compute;
|
|
expected_scratch = 7;
|
|
break;
|
|
case ShaderType::Mesh:
|
|
options.input_info.vertex = &vertex;
|
|
expected_scratch = 11;
|
|
break;
|
|
default:
|
|
std::abort();
|
|
}
|
|
const auto translated = TranslateProgram(shader, options);
|
|
Check(
|
|
translated.program.scratch_dwords == expected_scratch,
|
|
"translation did not use its stage's authoritative scratch allocation");
|
|
}
|
|
|
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
|
options.shader_hash = 0x1234;
|
|
auto translated = TranslateProgram(shader, options);
|
|
options.stage = ShaderType::Unknown;
|
|
options.shader_hash = 0;
|
|
auto compiled = CompileProgram(std::move(translated), options, {});
|
|
Check(compiled.program.stage == ShaderType::Compute &&
|
|
compiled.program.shader_hash == 0x1234,
|
|
"compilation reinterpreted the translated program's identity");
|
|
CheckSpirvBinaryValidates(compiled.spirv);
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
options.stage = ShaderType::Vertex;
|
|
options.input_info.vertex = nullptr;
|
|
ExpectFatal([&] { (void)TranslateProgram(shader, options); },
|
|
"missing stage metadata bypassed translation validation");
|
|
const uint32_t auxiliary_position[] = {
|
|
EncodeExp0(0x0d, 0x1), EncodeExp1(0, 0, 0, 0), EncodeSopp(0x01)};
|
|
for (const auto stage : {ShaderType::Pixel, ShaderType::Compute}) {
|
|
ExpectFatal(
|
|
[&] {
|
|
(void)RecompileForTest(auxiliary_position, MakeCompileOptions(stage));
|
|
},
|
|
"auxiliary position export read another stage's input metadata");
|
|
}
|
|
Decoder::Program decoded;
|
|
Decoder::DecodeProgram(shader, decoded);
|
|
const auto cfg = CFG::BuildGraph(decoded);
|
|
Frontend::EmbeddedFetchPlan fetch;
|
|
Frontend::TranslateOptions invalid_fetch{.stage = ShaderType::Compute,
|
|
.input_info = {.compute = &compute},
|
|
.embedded_fetch = &fetch};
|
|
ExpectFatal(
|
|
[&] { (void)Frontend::TranslateProgram(decoded, cfg, invalid_fetch); },
|
|
"embedded vertex fetch accepted another stage's metadata");
|
|
#endif
|
|
}
|
|
|
|
void TestSpirvEmissionOwnsRequirements() {
|
|
using namespace ShaderRecompiler;
|
|
const uint32_t shader[] = {EncodeSopp(0x01)};
|
|
const auto options = MakeCompileOptions(ShaderType::Compute);
|
|
auto compiled = RecompileForTest(shader, options);
|
|
IR::IREmitter emitter(compiled.program.blocks.front());
|
|
emitter.Emit(IR::ValueOpcode::LaneId, {});
|
|
const auto binary = Spirv::EmitProgram(compiled.program, options.input_info);
|
|
CheckSpirvBinaryValidates(binary);
|
|
Check((DisassembleSpirvBinary(binary).find("BuiltIn SubgroupLocalInvocationId") != std::string::npos),
|
|
"emission reused requirements from an earlier IR version");
|
|
}
|
|
|
|
void TestRepeatedExportsHaveOneInterface() {
|
|
const uint32_t shader[] = {EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3),
|
|
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3),
|
|
EncodeSopp(0x01)};
|
|
const auto result =
|
|
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
|
|
Check(result.program.info.outputs.size() == 1,
|
|
"repeated exports duplicated the collected shader interface");
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
uint32_t outputs = 0;
|
|
for (size_t offset = 5; offset < result.spirv.size();) {
|
|
if ((result.spirv[offset] & spv::OpCodeMask) == spv::OpVariable &&
|
|
result.spirv[offset + 3] == spv::StorageClassOutput) {
|
|
outputs++;
|
|
}
|
|
offset += result.spirv[offset] >> spv::WordCountShift;
|
|
}
|
|
Check(outputs == 2 && SpirvHasDecorationValue(result.spirv, 11u, 3u),
|
|
"repeated exports duplicated Position or its clipping-error plane");
|
|
}
|
|
|
|
void TestNewShaderRecompilerSpirvSizeBaselines() {
|
|
const auto compile = [](const char *name, std::span<const uint32_t> shader,
|
|
const SpirvMetrics &budget,
|
|
ShaderType stage = ShaderType::Compute) {
|
|
auto options = MakeCompileOptions(stage);
|
|
options.dump_ir = true;
|
|
|
|
auto result = RecompileForTest(shader, options);
|
|
CheckSpirvBinaryValidates(result.spirv);
|
|
CheckSpirvBudget(name, result.spirv, budget);
|
|
return result;
|
|
};
|
|
|
|
const uint32_t empty[] = {EncodeSopp(0x01)};
|
|
const auto empty_result =
|
|
compile("empty", empty,
|
|
{.words = 64, .instructions = 20, .labels = 4, .branches = 3});
|
|
const auto empty_metrics = MeasureSpirv(empty_result.spirv);
|
|
Check(empty_metrics.type_voids == 1u && empty_metrics.type_functions == 1u &&
|
|
empty_metrics.type_bools == 0u && empty_metrics.type_ints == 0u &&
|
|
empty_metrics.type_floats == 0u &&
|
|
empty_metrics.type_vectors == 0u &&
|
|
empty_metrics.type_pointers == 0u,
|
|
"empty shader emitted unused core types or pointers");
|
|
|
|
const uint32_t dead_gds[] = {
|
|
EncodeDs0(0x36, 0) | (1u << 17u),
|
|
EncodeDs1(0, 0, 1),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto dead_gds_result =
|
|
compile("dead-gds", dead_gds,
|
|
{.words = 64, .instructions = 20, .labels = 4, .branches = 3});
|
|
Check(ShaderRecompiler::IR::FindBinding(
|
|
dead_gds_result.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::Gds) == nullptr,
|
|
"dead GDS load retained a descriptor binding");
|
|
|
|
const uint32_t structured_phi[] = {
|
|
EncodeSMovB32(0, 128), // s0 = 0
|
|
EncodeSopc(0x0a, 0, 129), // loop: s_cmp_lt_u32 s0, 1
|
|
EncodeSopp(0x04, 2), // break when scc == 0
|
|
EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
|
|
EncodeSopp(0x02, 0xfffcu), // continue/backedge
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto structured_result = compile("structured-phi", structured_phi,
|
|
{.words = 140,
|
|
.instructions = 41,
|
|
.phis = 1,
|
|
.labels = 8,
|
|
.loop_merges = 1,
|
|
.branches = 6,
|
|
.conditional_branches = 1});
|
|
const auto structured_metrics = MeasureSpirv(structured_result.spirv);
|
|
Check((structured_result.ir_dump.find("Phi") != std::string::npos),
|
|
"structured Phi size fixture no longer contains an IR Phi");
|
|
Check(structured_metrics.phis == 1u &&
|
|
structured_metrics.function_variables == 0u &&
|
|
structured_metrics.loads == 0u && structured_metrics.stores == 0u,
|
|
"structured Phi retained a spill variable or edge memory operation");
|
|
CheckSpirvPhiParents(structured_result.spirv);
|
|
const auto structured_repeat =
|
|
compile("structured-phi-repeat", structured_phi,
|
|
{.words = 140,
|
|
.instructions = 41,
|
|
.phis = 1,
|
|
.labels = 8,
|
|
.loop_merges = 1,
|
|
.branches = 6,
|
|
.conditional_branches = 1});
|
|
Check(structured_repeat.spirv == structured_result.spirv,
|
|
"deferred Phi patching is not deterministic");
|
|
|
|
const uint32_t wide_buffer[] = {
|
|
EncodeMubuf0(0x0e, 0),
|
|
EncodeMubuf1(0, 0, 1), // buffer_load_dwordx4 v[0:3]
|
|
EncodeMubuf0(0x1e, 16),
|
|
EncodeMubuf1(0, 0, 1), // buffer_store_dwordx4 v[0:3]
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto wide_result = compile("wide-buffer", wide_buffer,
|
|
{.words = 807,
|
|
.instructions = 211,
|
|
.runtime_arrays = 1,
|
|
.variables = 2,
|
|
.loads = 9,
|
|
.stores = 4,
|
|
.array_lengths = 2,
|
|
.phis = 5,
|
|
.labels = 34,
|
|
.selection_merges = 10,
|
|
.branches = 23,
|
|
.conditional_branches = 10});
|
|
Check((wide_result.decoded_dump.find("BUFFER_LOAD_DWORDX4") != std::string::npos),
|
|
"wide buffer size fixture no longer decodes its x4 load");
|
|
Check((wide_result.decoded_dump.find("BUFFER_STORE_DWORDX4") != std::string::npos),
|
|
"wide buffer size fixture no longer decodes its x4 store");
|
|
uint32_t wide_loads = 0;
|
|
uint32_t wide_stores = 0;
|
|
uint32_t scalar_loads = 0;
|
|
uint32_t scalar_stores = 0;
|
|
for (const auto *block : wide_result.program.blocks) {
|
|
for (const auto &inst : *block) {
|
|
switch (inst.GetOpcode()) {
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32x4:
|
|
wide_loads++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32x4:
|
|
wide_stores++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::LoadBufferU32:
|
|
scalar_loads++;
|
|
break;
|
|
case ShaderRecompiler::IR::ValueOpcode::StoreBufferU32:
|
|
scalar_stores++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Check(wide_loads == 1u && wide_stores == 1u && scalar_loads == 0u &&
|
|
scalar_stores == 0u,
|
|
"wide buffer fixture retained scalar sibling memory operations");
|
|
|
|
const uint32_t wide_lds[] = {
|
|
EncodeDs0(0xff, 0), EncodeDs1(0, 0, 4), // ds_read_b128 v[0:3], v4
|
|
EncodeDs0(0xdf, 16), EncodeDs1(0, 0, 4), // ds_write_b128 v[0:3], v4
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto wide_lds_result = compile("wide-lds", wide_lds,
|
|
{.words = 548,
|
|
.instructions = 156,
|
|
.variables = 1,
|
|
.workgroup_variables = 1,
|
|
.loads = 4,
|
|
.stores = 4,
|
|
.phis = 5,
|
|
.labels = 34,
|
|
.selection_merges = 10,
|
|
.branches = 23,
|
|
.conditional_branches = 10});
|
|
const auto wide_lds_metrics = MeasureSpirv(wide_lds_result.spirv);
|
|
Check(wide_lds_metrics.workgroup_variables == 1u &&
|
|
wide_lds_metrics.runtime_arrays == 0u &&
|
|
wide_lds_metrics.array_lengths == 0u,
|
|
"native wide LDS fixture emitted the wrong storage topology");
|
|
|
|
const uint32_t wide_gds[] = {
|
|
EncodeDs0(0xff, 0) | (1u << 17u),
|
|
EncodeDs1(0, 0, 4),
|
|
EncodeDs0(0xdf, 16) | (1u << 17u),
|
|
EncodeDs1(0, 0, 4),
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto wide_gds_result = compile("wide-gds", wide_gds,
|
|
{.words = 577,
|
|
.instructions = 162,
|
|
.runtime_arrays = 1,
|
|
.variables = 1,
|
|
.loads = 4,
|
|
.stores = 4,
|
|
.array_lengths = 1,
|
|
.phis = 5,
|
|
.labels = 34,
|
|
.selection_merges = 10,
|
|
.branches = 23,
|
|
.conditional_branches = 10});
|
|
const auto wide_gds_metrics = MeasureSpirv(wide_gds_result.spirv);
|
|
Check(wide_gds_metrics.runtime_arrays == 1u &&
|
|
wide_gds_metrics.workgroup_variables == 0u &&
|
|
wide_gds_metrics.array_lengths == 1u,
|
|
"native wide GDS fixture did not share one entry-dominating length");
|
|
Check(ShaderRecompiler::IR::FindBinding(
|
|
wide_gds_result.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr,
|
|
"live native wide GDS access did not allocate its descriptor binding");
|
|
const auto count_shared = [](const auto &result,
|
|
ShaderRecompiler::IR::ValueOpcode opcode) {
|
|
uint32_t count = 0;
|
|
for (const auto *block : result.program.blocks) {
|
|
count += std::ranges::count(*block, opcode,
|
|
&ShaderRecompiler::IR::Inst::GetOpcode);
|
|
}
|
|
return count;
|
|
};
|
|
for (const auto *result : {&wide_lds_result, &wide_gds_result}) {
|
|
Check(count_shared(*result,
|
|
ShaderRecompiler::IR::ValueOpcode::LoadSharedU32x4) ==
|
|
1u &&
|
|
count_shared(
|
|
*result,
|
|
ShaderRecompiler::IR::ValueOpcode::WriteSharedU32x4) == 1u &&
|
|
count_shared(*result,
|
|
ShaderRecompiler::IR::ValueOpcode::LoadSharedU32) ==
|
|
0u &&
|
|
count_shared(*result,
|
|
ShaderRecompiler::IR::ValueOpcode::WriteSharedU32) ==
|
|
0u,
|
|
"native wide shared fixture retained scalar sibling operations");
|
|
}
|
|
|
|
const uint32_t wqm[] = {
|
|
EncodeVopc(0xc1, 5 + 256, 8), // v_cmp_lt_u32 vcc, v5, v8
|
|
EncodeSop1(0x0a, 2, 106), // s_wqm_b64 s[2:3], vcc
|
|
EncodeVop1(0x01, 0, 2), // v_mov_b32 v0, s2
|
|
EncodeExp0(0x0c, 0x1),
|
|
EncodeExp1(0, 0, 0, 0), // POS0.x
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto wqm_result = compile("wqm", wqm,
|
|
{.words = 407,
|
|
.instructions = 99,
|
|
.variables = 4,
|
|
.loads = 3,
|
|
.stores = 2,
|
|
.labels = 6,
|
|
.selection_merges = 1,
|
|
.branches = 4,
|
|
.conditional_branches = 1,
|
|
.ballots = 1},
|
|
ShaderType::Vertex);
|
|
Check((wqm_result.ir_dump.find("WqmU64") != std::string::npos),
|
|
"WQM size fixture no longer reaches scalar mask expansion");
|
|
|
|
const uint32_t dispatcher[] = {
|
|
EncodeSopp(0x05, 2), // entry -> B, fallthrough A
|
|
EncodeSopp(0x02, 0), // A -> C
|
|
EncodeSopp(0x05, 0xfffeu), // C -> A, fallthrough B
|
|
EncodeSopp(0x02, 0xfffeu), // B -> C
|
|
EncodeSopp(0x01),
|
|
};
|
|
const auto dispatcher_result = compile("dispatcher", dispatcher,
|
|
{.words = 242,
|
|
.instructions = 67,
|
|
.variables = 3,
|
|
.function_variables = 3,
|
|
.loads = 3,
|
|
.stores = 6,
|
|
.phis = 2,
|
|
.labels = 13,
|
|
.loop_merges = 1,
|
|
.selection_merges = 1,
|
|
.branches = 10,
|
|
.conditional_branches = 1,
|
|
.switches = 1});
|
|
Check(dispatcher_result.program.dispatcher_fallback &&
|
|
(dispatcher_result.ir_dump.find("Phi") != std::string::npos) &&
|
|
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 245u) == 2u &&
|
|
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 251u) == 1u,
|
|
"dispatcher size fixture lost its two control Phis or switch");
|
|
CheckSpirvPhiParents(dispatcher_result.spirv);
|
|
}
|
|
|
|
#include "ShaderRayTracingTests.inc"
|
|
|
|
} // namespace
|
|
} // namespace Libs::Graphics
|
|
|
|
int main() {
|
|
using namespace Libs::Graphics;
|
|
|
|
EnsureConfigInitialized();
|
|
TestRayTracingInstructions();
|
|
TestResourceDescriptorClassification();
|
|
TestShaderBufferResourceSize();
|
|
TestNativeShaderResourceDependencies();
|
|
TestNormalizedImageContracts();
|
|
TestSpirvRequirementsAnalysis();
|
|
TestTypedSpirvSerialization();
|
|
TestDeferredSpirvPhiPatching();
|
|
TestCompilerStageInputOwnership();
|
|
TestSpirvEmissionOwnsRequirements();
|
|
TestRepeatedExportsHaveOneInterface();
|
|
TestNewShaderRecompilerSpirvSizeBaselines();
|
|
TestDemandDrivenSpirvDeclarations();
|
|
TestNewShaderRecompilerSMovB32();
|
|
TestShaderStageBarriers();
|
|
TestVertexBufferGrouping();
|
|
TestNggVertexEntryState();
|
|
TestNewShaderRecompilerClipDisabledPosition();
|
|
TestNewShaderRecompilerAuxPositionExports();
|
|
TestNewShaderRecompilerNativeWideScalarMemoryIr();
|
|
TestNewShaderRecompilerNativeWideBufferIr();
|
|
TestNewShaderRecompilerScalarB64LaneTranslation();
|
|
TestNewShaderRecompilerMubufFormatTranslation();
|
|
TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly();
|
|
TestNewShaderRecompilerTypedBufferTranslation();
|
|
TestNewShaderRecompilerDsReadWrite2Translation();
|
|
TestNewShaderRecompilerDsWideAndAtomicTranslation();
|
|
TestNewShaderRecompilerCapturedVop1SdwaByteConvert();
|
|
TestNewShaderRecompilerCapturedFractF64();
|
|
TestNewShaderRecompilerF64AluEncodings();
|
|
TestNewShaderRecompilerVop1SdwaBfrev();
|
|
TestNewShaderRecompilerVop1SdwaNotDestination();
|
|
TestNewShaderRecompilerScalarMemoryBindingDomains();
|
|
// Opcode semantics and optimized SPIR-V are exercised by
|
|
// ShaderRecompilerComputeTests; keep the distinct decoder contract checks
|
|
// here.
|
|
TestScalarAshrI64Decoder();
|
|
TestImageAtomicWidthDecoder();
|
|
TestDsFloatMinMaxDecoder();
|
|
TestNewShaderDecoderArchitecture();
|
|
TestImageAddressOperands();
|
|
TestSopkCompareImmediateExtension();
|
|
TestNativeStoreCompletion();
|
|
TestNewShaderRecompilerCapturedVopcSdwaCmpxClass();
|
|
TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16();
|
|
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
|
|
TestNewShaderRecompilerRejectsDppOn64BitCompares();
|
|
TestFloatComparisonInputModes();
|
|
TestPsInputCountRegisterDecode();
|
|
TestPixelAncillaryLayerInput();
|
|
TestNewShaderRecompilerUnbasedFlatUsesBda();
|
|
TestNewShaderRecompilerFlatUserPointerUsesDma();
|
|
TestNewShaderRecompilerFlatAddressDomainsUseDma();
|
|
TestNewShaderRecompilerCfgStraightLine();
|
|
TestNewShaderRecompilerCfgIfElse();
|
|
TestNewShaderRecompilerCfgConsecutiveNativePhis();
|
|
TestNewShaderRecompilerStructuredU64Phi();
|
|
TestNewShaderRecompilerCfgTerminalExitMergePS();
|
|
TestNewShaderRecompilerCfgPostEndTargetMergePS();
|
|
TestNewShaderRecompilerCfgLoopBreakContinue();
|
|
TestNewShaderRecompilerCfgLoopHeaderDynamicScalarBufferLoadStructured();
|
|
TestNewShaderRecompilerCfgLoopHeaderBufferLoadStructured();
|
|
TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeStructured();
|
|
TestNewShaderRecompilerCfgLoopHeaderDsReadStructured();
|
|
TestNewShaderRecompilerCfgLoopHeaderDsRead2B64Structured();
|
|
TestNewShaderRecompilerCfgSharedOuterAndLoopMerge();
|
|
TestNewShaderRecompilerCfgLoopExitSharedWithSelection();
|
|
TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection();
|
|
TestNewShaderRecompilerCfgNestedLoopNonlocalExitStructured();
|
|
TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection();
|
|
TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit();
|
|
TestNewShaderRecompilerCfgMixedContinueNonmergeExitStructured();
|
|
TestNewShaderRecompilerCfgConditionalLatchNoSelection();
|
|
TestNewShaderRecompilerCfgDirectConditionalLatchNoSelection();
|
|
TestNewShaderRecompilerCfgLoopEarlyContinuesStructured();
|
|
TestNewShaderRecompilerCfgLoopGatewaySelection();
|
|
TestNewShaderRecompilerCfgConditionalLoopHeaderSelection();
|
|
TestNewShaderRecompilerCfgMultipleLoopLatches();
|
|
TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit();
|
|
TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders();
|
|
TestNewShaderRecompilerCfgExecSccSharedArm();
|
|
TestSharedReturnPreservesDescriptorDominance();
|
|
TestSharedExitPreservesNativeDescriptorSources();
|
|
TestNestedSelectionPreservesDescriptorSources();
|
|
TestNativeScalarReadDescriptorPlanning();
|
|
TestScalarSelectedVccBufferAddress();
|
|
TestNativeGuardedSamplerSource();
|
|
for (const bool scalar_key : {false, true}) TestBoundedMaterialBufferStores(scalar_key);
|
|
TestNativeDescriptorProvenanceKeepsGpuSelection();
|
|
TestCfgSiblingSharedExit();
|
|
TestNewShaderRecompilerCfgNestedTailEarlyExit();
|
|
TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections();
|
|
TestNewShaderRecompilerCfgAlternatingSharedReturns();
|
|
TestNewShaderRecompilerCfgLoopSharedRegion();
|
|
TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop();
|
|
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
|
TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal();
|
|
TestNewShaderRecompilerCfgEarlyReturnSharedLoopContinuation();
|
|
TestNewShaderRecompilerCfgSharedTerminalEarlyExit();
|
|
TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry();
|
|
TestNewShaderRecompilerCfgFailedStructurizationPreservesGraph();
|
|
TestNewShaderRecompilerCfgIrreducibleDispatcher();
|
|
TestNewShaderRecompilerDispatcherSpillsU32x3();
|
|
TestNewShaderRecompilerPlanningOnlyLoads();
|
|
TestNewShaderRecompilerU64PairTranslation();
|
|
TestComputeDispatchWaveSize();
|
|
TestNewShaderRecompilerBufferLoadsGuardedByExec();
|
|
TestNewShaderRecompilerBufferAtomicsGuardedByBounds();
|
|
TestCapturedBufferAtomicsX2();
|
|
TestDisabledDebugBranches();
|
|
TestNewShaderRecompilerPixelImageSampleLodSelection();
|
|
TestNewShaderRecompilerBranchConditionForms();
|
|
TestNewShaderRecompilerSetpcBranch();
|
|
TestFusedShaderHandoffPreservesRegisters();
|
|
TestMeshExportStorage();
|
|
TestMergedShaderUserDataSnapshot();
|
|
TestMeshInputAssembly();
|
|
TestEmbeddedFetchPreservesSharedScalarLoad();
|
|
TestEmbeddedVertexFormatSwizzle();
|
|
TestNewShaderRecompilerSetpcJumpTable();
|
|
TestNewShaderRecompilerPrunesUnreachableSetpcMetadata();
|
|
TestNewShaderRecompilerSetpcDwordJumpTable();
|
|
TestTypedEntryStateIsMinimal();
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
TestFinalSsaRejectsRegisterStatePseudos();
|
|
#endif
|
|
TestValuePhiValidation();
|
|
TestWave32MaskProjection();
|
|
TestU64ShiftConstantPropagation();
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
TestNativeWideValueValidation();
|
|
#endif
|
|
TestNewShaderRecompilerZeroInitialRegisterState();
|
|
TestNewShaderRecompilerVertexSystemInputsWithoutMirrors();
|
|
TestNewShaderRecompilerVertexExportUsesInvocationExecMask();
|
|
TestNewShaderRecompilerPerInvocationMasksWithoutMirrors();
|
|
TestNewShaderRecompilerPerInvocationU64Complement();
|
|
TestNewShaderRecompilerExpPixelOutputs();
|
|
TestRenderTargetReverseExportMapping();
|
|
TestBlendMappingClassification();
|
|
TestLogicalAlphaBlendExport();
|
|
TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled();
|
|
TestTypedDescriptorRealWideMoveTranslation();
|
|
TestComputeImageFill();
|
|
TestGpuProducedWritableDescriptor();
|
|
TestTypedDescriptorRealCarryAndScalarLoads();
|
|
TestSrtWalkerRealSmemTranslation();
|
|
TestSrtWalkerVccBaseTranslation();
|
|
TestSrtWalkerRealSBufferTranslation();
|
|
TestScalarMemorySourcesCapturedBeforeWrites();
|
|
TestScalarMemoryLoadCrossesIntoVcc();
|
|
TestScalarMemoryUnusedTailDce();
|
|
TestResourceTrackingRealDensePatching();
|
|
TestDirectTranslationResetsAnalysisState();
|
|
TestNewShaderRecompilerNativeBindingPlan();
|
|
TestNewShaderRecompilerStageInputInfo();
|
|
TestCustomVintrpMovTranslation();
|
|
TestPerspectiveCentroidInputs();
|
|
TestGraphicsCreateInterpolantMapping();
|
|
TestNewShaderRecompilerPixelPipelineEntry();
|
|
TestComputeLdsAllocationIdentity();
|
|
TestPixelProgramCacheBindingIdentity();
|
|
TestGraphicsPushConstantPlacement();
|
|
TestNewShaderRecompilerUnsupportedMemoryDecode();
|
|
|
|
TestNewShaderRecompilerVop3LaneReadDestinationEncoding();
|
|
|
|
return 0;
|
|
}
|