mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 05:46:56 +01:00
shader_recompiler: add SSA
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERCFG_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERCFG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::CFG {
|
||||
|
||||
enum class BranchCondition {
|
||||
Always,
|
||||
SccZero,
|
||||
SccNonZero,
|
||||
VccZero,
|
||||
VccNonZero,
|
||||
ExecZero,
|
||||
ExecNonZero,
|
||||
GotoVariable,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class TerminatorKind { Branch, ConditionalBranch, IndirectBranch, Return, Unsupported };
|
||||
|
||||
enum class FailureKind {
|
||||
None,
|
||||
InvalidInput,
|
||||
UnsupportedInstruction,
|
||||
InvalidBranchTarget,
|
||||
MissingFallthrough,
|
||||
InvalidLabel,
|
||||
IrreducibleControlFlow,
|
||||
StructuredControlFlow
|
||||
};
|
||||
|
||||
struct Terminator {
|
||||
TerminatorKind kind = TerminatorKind::Return;
|
||||
BranchCondition condition = BranchCondition::Always;
|
||||
uint32_t true_block = UINT32_MAX;
|
||||
uint32_t false_block = UINT32_MAX;
|
||||
uint32_t merge_block = UINT32_MAX;
|
||||
uint32_t continue_block = UINT32_MAX;
|
||||
uint32_t indirect_pc_sgpr = UINT32_MAX;
|
||||
uint32_t indirect_selector_code = UINT32_MAX;
|
||||
std::vector<uint32_t> indirect_target_pcs;
|
||||
std::vector<uint32_t> indirect_targets;
|
||||
std::vector<uint32_t> indirect_selector_values;
|
||||
std::vector<uint32_t> indirect_selector_targets;
|
||||
uint32_t goto_variable = UINT32_MAX;
|
||||
int32_t goto_value = -1;
|
||||
bool loop_header = false;
|
||||
};
|
||||
|
||||
struct BasicBlock {
|
||||
uint32_t id = 0;
|
||||
uint32_t start_pc = 0;
|
||||
uint32_t end_pc = 0;
|
||||
uint32_t inst_begin = 0;
|
||||
uint32_t inst_end = 0;
|
||||
std::vector<uint32_t> predecessors;
|
||||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> dominators;
|
||||
std::vector<uint32_t> post_dominators;
|
||||
Terminator terminator;
|
||||
};
|
||||
|
||||
struct BackEdge {
|
||||
uint32_t from = UINT32_MAX;
|
||||
uint32_t to = UINT32_MAX;
|
||||
bool natural = false;
|
||||
};
|
||||
|
||||
struct NaturalLoop {
|
||||
uint32_t header = UINT32_MAX;
|
||||
uint32_t latch = UINT32_MAX;
|
||||
uint32_t merge = UINT32_MAX;
|
||||
uint32_t continue_block = UINT32_MAX;
|
||||
std::vector<uint32_t> body_blocks;
|
||||
std::vector<uint32_t> exit_blocks;
|
||||
};
|
||||
|
||||
struct StronglyConnectedComponent {
|
||||
std::vector<uint32_t> blocks;
|
||||
std::vector<uint32_t> entry_blocks;
|
||||
bool irreducible = false;
|
||||
};
|
||||
|
||||
struct Graph {
|
||||
std::vector<BasicBlock> blocks;
|
||||
std::vector<BackEdge> back_edges;
|
||||
std::vector<NaturalLoop> natural_loops;
|
||||
std::vector<StronglyConnectedComponent> components;
|
||||
std::vector<uint32_t> code_table_load_pcs;
|
||||
uint32_t entry_block = UINT32_MAX;
|
||||
bool irreducible = false;
|
||||
bool unsupported = false;
|
||||
FailureKind failure_kind = FailureKind::None;
|
||||
uint32_t failure_block = UINT32_MAX;
|
||||
std::string unsupported_reason;
|
||||
|
||||
const BasicBlock* FindBlock(uint32_t id) const;
|
||||
BasicBlock* FindBlock(uint32_t id);
|
||||
const BasicBlock* FindBlockByPc(uint32_t pc) const;
|
||||
BasicBlock* FindBlockByPc(uint32_t pc);
|
||||
bool Dominates(uint32_t dominator, uint32_t block) const;
|
||||
bool PostDominates(uint32_t post_dominator, uint32_t block) const;
|
||||
uint32_t FindNearestCommonPostDominator(uint32_t block_a, uint32_t block_b) const;
|
||||
};
|
||||
|
||||
bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* error);
|
||||
bool Structurize(Graph& graph, std::string* error);
|
||||
bool IsolateLoopHeader(Graph& graph, uint32_t header_id, std::string* error);
|
||||
std::string BranchConditionToString(BranchCondition condition);
|
||||
std::string FailureKindToString(FailureKind kind);
|
||||
std::string GraphToString(const Graph& graph);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::CFG
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERCFG_H_ */
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ExportOps.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeExp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated EXP instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t target = (word0 >> 4u) & 0x3fu;
|
||||
const uint32_t en = word0 & 0xfu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word0;
|
||||
inst.word_count = 2;
|
||||
inst.family = Family::EXP;
|
||||
inst.opcode_id = target;
|
||||
inst.opcode = Opcode::Exp;
|
||||
inst.exp.target = target;
|
||||
inst.exp.en = en;
|
||||
inst.exp.compr = ((word0 >> 10u) & 1u) != 0;
|
||||
inst.exp.done = ((word0 >> 11u) & 1u) != 0;
|
||||
inst.exp.vm = ((word0 >> 12u) & 1u) != 0;
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
|
||||
if (!DecodeVectorGpr(word1 & 0xffu, inst.src0, error) ||
|
||||
!DecodeVectorGpr((word1 >> 8u) & 0xffu, inst.src1, error) ||
|
||||
!DecodeVectorGpr((word1 >> 16u) & 0xffu, inst.src2, error) ||
|
||||
!DecodeVectorGpr((word1 >> 24u) & 0xffu, inst.src3, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == 0x14u && inst.exp.done && en == 0x1u) {
|
||||
inst.src_count = 1u;
|
||||
} else {
|
||||
inst.src_count = inst.exp.compr ? 2u : 4u;
|
||||
}
|
||||
if (en == 0u) {
|
||||
inst.src_count = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeExp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_ */
|
||||
@@ -0,0 +1,375 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
struct MimgSampleInfo {
|
||||
uint32_t opcode = 0;
|
||||
const char* name = nullptr;
|
||||
uint32_t flags = 0;
|
||||
uint32_t address_components = 3;
|
||||
};
|
||||
|
||||
struct MimgGatherInfo {
|
||||
uint32_t opcode = 0;
|
||||
const char* name = nullptr;
|
||||
Opcode decoded = Opcode::Unsupported;
|
||||
uint32_t flags = 0;
|
||||
uint32_t address_components = 3;
|
||||
};
|
||||
|
||||
struct MimgAtomicInfo {
|
||||
uint32_t opcode = 0;
|
||||
const char* name = nullptr;
|
||||
Opcode decoded = Opcode::Unsupported;
|
||||
};
|
||||
|
||||
constexpr ImageDimension DecodeImageDimension(uint32_t dim) {
|
||||
switch (dim) {
|
||||
case 0u: return ImageDimension::Dim1D;
|
||||
case 1u: return ImageDimension::Dim2D;
|
||||
case 2u: return ImageDimension::Dim3D;
|
||||
case 3u: return ImageDimension::Dim2DArray;
|
||||
case 4u: return ImageDimension::Dim1DArray;
|
||||
case 5u: return ImageDimension::Dim2DArray;
|
||||
case 6u: return ImageDimension::Dim2DMsaa;
|
||||
case 7u: return ImageDimension::Dim2DMsaaArray;
|
||||
default: return ImageDimension::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageCoordComponents(ImageDimension dimension) {
|
||||
switch (dimension) {
|
||||
case ImageDimension::Dim1D: return 1u;
|
||||
case ImageDimension::Dim1DArray: return 2u;
|
||||
case ImageDimension::Dim2DMsaa:
|
||||
case ImageDimension::Dim3D:
|
||||
case ImageDimension::Dim2DArray: return 3u;
|
||||
case ImageDimension::Dim2DMsaaArray: return 4u;
|
||||
default: return 2u;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageGradientComponents(ImageDimension dimension) {
|
||||
switch (dimension) {
|
||||
case ImageDimension::Dim1D:
|
||||
case ImageDimension::Dim1DArray: return 1u;
|
||||
case ImageDimension::Dim3D: return 3u;
|
||||
default: return 2u;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension dimension) {
|
||||
const auto coord_components = ImageCoordComponents(dimension);
|
||||
uint32_t components = coord_components;
|
||||
if ((flags & ImageSampleFlagOffset) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagCompare) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagBias) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagLod) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagDerivative) != 0) {
|
||||
components += ImageGradientComponents(dimension) * 2u;
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
constexpr MimgSampleInfo SampleInfo(uint32_t opcode, const char* name, uint32_t flags) {
|
||||
return {opcode, name, flags, ImageSampleAddressComponents(flags, ImageDimension::Dim2D)};
|
||||
}
|
||||
|
||||
constexpr MimgSampleInfo MIMG_SAMPLE_OPS[] = {
|
||||
SampleInfo(0x20u, "image_sample", 0),
|
||||
SampleInfo(0x21u, "image_sample_cl", ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x22u, "image_sample_d", ImageSampleFlagDerivative),
|
||||
SampleInfo(0x23u, "image_sample_d_cl", ImageSampleFlagDerivative | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x24u, "image_sample_l", ImageSampleFlagLod),
|
||||
SampleInfo(0x25u, "image_sample_b", ImageSampleFlagBias),
|
||||
SampleInfo(0x26u, "image_sample_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x27u, "image_sample_lz", ImageSampleFlagLevelZero),
|
||||
SampleInfo(0x28u, "image_sample_c", ImageSampleFlagCompare),
|
||||
SampleInfo(0x29u, "image_sample_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x2au, "image_sample_c_d", ImageSampleFlagCompare | ImageSampleFlagDerivative),
|
||||
SampleInfo(0x2bu, "image_sample_c_d_cl",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x2cu, "image_sample_c_l", ImageSampleFlagCompare | ImageSampleFlagLod),
|
||||
SampleInfo(0x2du, "image_sample_c_b", ImageSampleFlagCompare | ImageSampleFlagBias),
|
||||
SampleInfo(0x2eu, "image_sample_c_b_cl",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x2fu, "image_sample_c_lz", ImageSampleFlagCompare | ImageSampleFlagLevelZero),
|
||||
SampleInfo(0x30u, "image_sample_o", ImageSampleFlagOffset),
|
||||
SampleInfo(0x31u, "image_sample_cl_o", ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x32u, "image_sample_d_o", ImageSampleFlagDerivative | ImageSampleFlagOffset),
|
||||
SampleInfo(0x33u, "image_sample_d_cl_o",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x34u, "image_sample_l_o", ImageSampleFlagLod | ImageSampleFlagOffset),
|
||||
SampleInfo(0x35u, "image_sample_b_o", ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0x36u, "image_sample_b_cl_o",
|
||||
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x37u, "image_sample_lz_o", ImageSampleFlagLevelZero | ImageSampleFlagOffset),
|
||||
SampleInfo(0x38u, "image_sample_c_o", ImageSampleFlagCompare | ImageSampleFlagOffset),
|
||||
SampleInfo(0x39u, "image_sample_c_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3au, "image_sample_c_d_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3bu, "image_sample_c_d_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x3cu, "image_sample_c_l_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLod | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3du, "image_sample_c_b_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3eu, "image_sample_c_b_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x3fu, "image_sample_c_lz_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset),
|
||||
SampleInfo(0x68u, "image_sample_cd", ImageSampleFlagDerivative | ImageSampleFlagCd),
|
||||
SampleInfo(0x69u, "image_sample_cd_cl",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x6au, "image_sample_c_cd",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd),
|
||||
SampleInfo(0x6bu, "image_sample_c_cd_cl",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
|
||||
ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x6cu, "image_sample_cd_o",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagOffset),
|
||||
SampleInfo(0x6du, "image_sample_cd_cl_o",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x6eu, "image_sample_c_cd_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x6fu, "image_sample_c_cd_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
|
||||
ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xa0u, "image_sample_a", 0),
|
||||
SampleInfo(0xa1u, "image_sample_cl_a", ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xa5u, "image_sample_b_a", ImageSampleFlagBias),
|
||||
SampleInfo(0xa6u, "image_sample_b_cl_a", ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xa8u, "image_sample_c_a", ImageSampleFlagCompare),
|
||||
SampleInfo(0xa9u, "image_sample_c_cl_a", ImageSampleFlagCompare | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xadu, "image_sample_c_b_a", ImageSampleFlagCompare | ImageSampleFlagBias),
|
||||
SampleInfo(0xaeu, "image_sample_c_b_cl_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xb0u, "image_sample_o_a", ImageSampleFlagOffset),
|
||||
SampleInfo(0xb1u, "image_sample_cl_o_a", ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb5u, "image_sample_b_o_a", ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb6u, "image_sample_b_cl_o_a",
|
||||
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb8u, "image_sample_c_o_a", ImageSampleFlagCompare | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb9u, "image_sample_c_cl_o_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xbdu, "image_sample_c_b_o_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0xbeu, "image_sample_c_b_cl_o_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
};
|
||||
|
||||
constexpr MimgGatherInfo MIMG_GATHER_OPS[] = {
|
||||
{0x47u, "image_gather4_lz", Opcode::ImageGather4Lz, ImageSampleFlagLevelZero, 2u},
|
||||
{0x48u, "image_gather4_c", Opcode::ImageGather4C, ImageSampleFlagCompare, 3u},
|
||||
{0x4fu, "image_gather4_c_lz", Opcode::ImageGather4CLz,
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero, 3u},
|
||||
{0x57u, "image_gather4_lz_o", Opcode::ImageGather4LzO,
|
||||
ImageSampleFlagLevelZero | ImageSampleFlagOffset, 3u},
|
||||
{0x58u, "image_gather4_c_o", Opcode::ImageGather4CO,
|
||||
ImageSampleFlagCompare | ImageSampleFlagOffset, 4u},
|
||||
{0x5fu, "image_gather4_c_lz_o", Opcode::ImageGather4CLzO,
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset, 4u},
|
||||
{0x61u, "image_gather4h", Opcode::ImageGather4H, ImageSampleFlagGatherHorizontal, 2u},
|
||||
};
|
||||
|
||||
constexpr MimgAtomicInfo MIMG_ATOMIC_OPS[] = {
|
||||
{0x11u, "image_atomic_add", Opcode::ImageAtomicAdd},
|
||||
{0x15u, "image_atomic_umin", Opcode::ImageAtomicUMin},
|
||||
{0x17u, "image_atomic_umax", Opcode::ImageAtomicUMax},
|
||||
{0x18u, "image_atomic_and", Opcode::ImageAtomicAnd},
|
||||
{0x19u, "image_atomic_or", Opcode::ImageAtomicOr},
|
||||
{0x1au, "image_atomic_xor", Opcode::ImageAtomicXor},
|
||||
};
|
||||
|
||||
const MimgSampleInfo* LookupSample(uint32_t opcode) {
|
||||
for (const auto& info: MIMG_SAMPLE_OPS) {
|
||||
if (info.opcode == opcode) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MimgGatherInfo* LookupGather(uint32_t opcode) {
|
||||
for (const auto& info: MIMG_GATHER_OPS) {
|
||||
if (info.opcode == opcode) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MimgAtomicInfo* LookupAtomic(uint32_t opcode) {
|
||||
for (const auto& info: MIMG_ATOMIC_OPS) {
|
||||
if (info.opcode == opcode) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgGatherInfo* gather,
|
||||
const MimgAtomicInfo* atomic) {
|
||||
if (sample != nullptr) {
|
||||
return Opcode::ImageSample;
|
||||
}
|
||||
if (gather != nullptr) {
|
||||
return gather->decoded;
|
||||
}
|
||||
if (atomic != nullptr) {
|
||||
return atomic->decoded;
|
||||
}
|
||||
|
||||
switch (opcode) {
|
||||
case 0x00u: return Opcode::ImageLoad;
|
||||
case 0x01u: return Opcode::ImageLoadMip;
|
||||
case 0x08u: return Opcode::ImageStore;
|
||||
case 0x09u: return Opcode::ImageStoreMip;
|
||||
case 0x0eu: return Opcode::ImageGetResinfo;
|
||||
case 0x60u: return Opcode::ImageGetLod;
|
||||
default: return Opcode::Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t DecodeMimgSampleFlags(const MimgSampleInfo* sample, const MimgGatherInfo* gather) {
|
||||
if (sample != nullptr) {
|
||||
return sample->flags;
|
||||
}
|
||||
if (gather != nullptr) {
|
||||
return gather->flags;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
|
||||
const MimgSampleInfo* sample, const MimgGatherInfo* gather,
|
||||
const MimgAtomicInfo* atomic) {
|
||||
if (sample != nullptr) {
|
||||
return ImageSampleAddressComponents(sample->flags, dimension);
|
||||
}
|
||||
if (gather != nullptr) {
|
||||
return ImageSampleAddressComponents(gather->flags, dimension);
|
||||
}
|
||||
if (atomic != nullptr) {
|
||||
return ImageCoordComponents(dimension);
|
||||
}
|
||||
|
||||
switch (opcode) {
|
||||
case 0x01u:
|
||||
case 0x09u: return ImageCoordComponents(dimension) + 1u;
|
||||
case 0x00u:
|
||||
case 0x08u:
|
||||
case 0x60u: return ImageCoordComponents(dimension);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CountDmaskComponents(uint32_t dmask) {
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < 4u; i++) {
|
||||
count += (dmask >> i) & 1u;
|
||||
}
|
||||
return count != 0 ? count : 1u;
|
||||
}
|
||||
|
||||
bool IsSingleDmaskBit(uint32_t dmask) {
|
||||
return dmask != 0u && (dmask & (dmask - 1u)) == 0u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MIMG instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = ((word0 >> 18u) & 0x7fu) | ((word0 & 1u) << 7u);
|
||||
const uint32_t nsa_dwords = (word0 >> 1u) & 0x3u;
|
||||
const auto dimension = DecodeImageDimension((word0 >> 3u) & 0x7u);
|
||||
const uint32_t word_count = 2u + nsa_dwords;
|
||||
if (word_index + word_count > code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MIMG NSA instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t ssamp = (word1 >> 21u) & 0x1fu;
|
||||
const bool a16 = ((word1 >> 30u) & 0x1u) != 0u;
|
||||
const auto* sample = LookupSample(opcode);
|
||||
const auto* gather = LookupGather(opcode);
|
||||
const auto* atomic = LookupAtomic(opcode);
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word0;
|
||||
inst.word_count = word_count;
|
||||
inst.family = Family::MIMG;
|
||||
inst.opcode_id = opcode;
|
||||
inst.opcode = DecodeMimgOpcode(opcode, sample, gather, atomic);
|
||||
inst.dmask = (word0 >> 8u) & 0xfu;
|
||||
inst.data_dwords = gather != nullptr ? 4u : CountDmaskComponents(inst.dmask);
|
||||
inst.glc = ((word0 >> 13u) & 1u) != 0;
|
||||
inst.slc = ((word0 >> 25u) & 1u) != 0;
|
||||
inst.image_sample_flags = DecodeMimgSampleFlags(sample, gather);
|
||||
if (a16) {
|
||||
inst.image_sample_flags |= ImageSampleFlagA16;
|
||||
}
|
||||
inst.image_dimension = dimension;
|
||||
inst.image_nsa_dwords = nsa_dwords;
|
||||
for (uint32_t i = 0; i < nsa_dwords * 4u && i < MaxImageNsaAddressComponents; i++) {
|
||||
inst.image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu;
|
||||
}
|
||||
inst.image_address_components =
|
||||
DecodeMimgAddressComponents(opcode, dimension, sample, gather, atomic);
|
||||
SetRawWords(inst, code, word_index, word_count);
|
||||
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode is not implemented");
|
||||
}
|
||||
if (gather != nullptr && !IsSingleDmaskBit(inst.dmask)) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode,
|
||||
"MIMG image gather requires exactly one dmask bit");
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst, nullptr);
|
||||
DecodeVectorGpr(vaddr, inst.src0, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(ssamp * 4u, pc, inst.src2, nullptr);
|
||||
inst.src_count = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* MimgSampleOpcodeName(uint32_t opcode) {
|
||||
const auto* sample = LookupSample(opcode);
|
||||
return sample != nullptr ? sample->name : nullptr;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
|
||||
const char* MimgSampleOpcodeName(uint32_t opcode);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_ */
|
||||
@@ -0,0 +1,477 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/MemoryOps.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
struct MemoryOpcodeInfo {
|
||||
uint32_t opcode = 0;
|
||||
Opcode decoded = Opcode::Unsupported;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo SMEM_OPS[] = {
|
||||
{0x00u, Opcode::SLoadDword, 1, 32}, {0x01u, Opcode::SLoadDwordx2, 2, 32},
|
||||
{0x02u, Opcode::SLoadDwordx4, 4, 32}, {0x03u, Opcode::SLoadDwordx8, 8, 32},
|
||||
{0x04u, Opcode::SLoadDwordx16, 16, 32}, {0x08u, Opcode::SBufferLoadDword, 1, 32},
|
||||
{0x09u, Opcode::SBufferLoadDwordx2, 2, 32}, {0x0au, Opcode::SBufferLoadDwordx4, 4, 32},
|
||||
{0x0bu, Opcode::SBufferLoadDwordx8, 8, 32}, {0x0cu, Opcode::SBufferLoadDwordx16, 16, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MUBUF_OPS[] = {
|
||||
{0x00u, Opcode::BufferLoadFormatX, 1, 32, false, false, true},
|
||||
{0x01u, Opcode::BufferLoadFormatXy, 2, 32, false, false, true},
|
||||
{0x02u, Opcode::BufferLoadFormatXyz, 3, 32, false, false, true},
|
||||
{0x03u, Opcode::BufferLoadFormatXyzw, 4, 32, false, false, true},
|
||||
{0x04u, Opcode::BufferStoreFormatX, 1, 32, false, false, true},
|
||||
{0x05u, Opcode::BufferStoreFormatXy, 2, 32, false, false, true},
|
||||
{0x06u, Opcode::BufferStoreFormatXyz, 3, 32, false, false, true},
|
||||
{0x07u, Opcode::BufferStoreFormatXyzw, 4, 32, false, false, true},
|
||||
{0x08u, Opcode::BufferLoadUbyte, 1, 8},
|
||||
{0x09u, Opcode::BufferLoadSbyte, 1, 8, true},
|
||||
{0x0au, Opcode::BufferLoadUshort, 1, 16},
|
||||
{0x0bu, Opcode::BufferLoadSshort, 1, 16, true},
|
||||
{0x0cu, Opcode::BufferLoadDword, 1, 32},
|
||||
{0x0du, Opcode::BufferLoadDwordx2, 2, 32},
|
||||
{0x0eu, Opcode::BufferLoadDwordx4, 4, 32},
|
||||
{0x0fu, Opcode::BufferLoadDwordx3, 3, 32},
|
||||
{0x18u, Opcode::BufferStoreByte, 1, 8},
|
||||
{0x1au, Opcode::BufferStoreShort, 1, 16},
|
||||
{0x1cu, Opcode::BufferStoreDword, 1, 32},
|
||||
{0x1du, Opcode::BufferStoreDwordx2, 2, 32},
|
||||
{0x1eu, Opcode::BufferStoreDwordx4, 4, 32},
|
||||
{0x1fu, Opcode::BufferStoreDwordx3, 3, 32},
|
||||
{0x30u, Opcode::BufferAtomicSwap, 1, 32},
|
||||
{0x32u, Opcode::BufferAtomicAdd, 1, 32},
|
||||
{0x33u, Opcode::BufferAtomicSub, 1, 32},
|
||||
{0x35u, Opcode::BufferAtomicSMin, 1, 32},
|
||||
{0x36u, Opcode::BufferAtomicUMin, 1, 32},
|
||||
{0x37u, Opcode::BufferAtomicSMax, 1, 32},
|
||||
{0x38u, Opcode::BufferAtomicUMax, 1, 32},
|
||||
{0x39u, Opcode::BufferAtomicAnd, 1, 32},
|
||||
{0x3au, Opcode::BufferAtomicOr, 1, 32},
|
||||
{0x3bu, Opcode::BufferAtomicXor, 1, 32},
|
||||
{0x3fu, Opcode::BufferAtomicFMin, 1, 32},
|
||||
{0x40u, Opcode::BufferAtomicFMax, 1, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPS[] = {
|
||||
{0x00u, Opcode::TBufferLoadFormatX, 1, 32, false, true, true},
|
||||
{0x01u, Opcode::TBufferLoadFormatXy, 2, 32, false, true, true},
|
||||
{0x02u, Opcode::TBufferLoadFormatXyz, 3, 32, false, true, true},
|
||||
{0x03u, Opcode::TBufferLoadFormatXyzw, 4, 32, false, true, true},
|
||||
{0x04u, Opcode::TBufferStoreFormatX, 1, 32, false, true, true},
|
||||
{0x05u, Opcode::TBufferStoreFormatXy, 2, 32, false, true, true},
|
||||
{0x06u, Opcode::TBufferStoreFormatXyz, 3, 32, false, true, true},
|
||||
{0x07u, Opcode::TBufferStoreFormatXyzw, 4, 32, false, true, true},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo FLAT_OPS[] = {
|
||||
{0x08u, Opcode::FlatLoadUbyte, 1, 8}, {0x09u, Opcode::FlatLoadSbyte, 1, 8, true},
|
||||
{0x0au, Opcode::FlatLoadUshort, 1, 16}, {0x0bu, Opcode::FlatLoadSshort, 1, 16, true},
|
||||
{0x0cu, Opcode::FlatLoadDword, 1, 32}, {0x0du, Opcode::FlatLoadDwordx2, 2, 32},
|
||||
{0x0eu, Opcode::FlatLoadDwordx4, 4, 32}, {0x0fu, Opcode::FlatLoadDwordx3, 3, 32},
|
||||
{0x18u, Opcode::FlatStoreByte, 1, 8}, {0x1au, Opcode::FlatStoreShort, 1, 16},
|
||||
{0x1cu, Opcode::FlatStoreDword, 1, 32}, {0x1du, Opcode::FlatStoreDwordx2, 2, 32},
|
||||
{0x1eu, Opcode::FlatStoreDwordx4, 4, 32}, {0x1fu, Opcode::FlatStoreDwordx3, 3, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo DS_OPS[] = {
|
||||
{0x00u, Opcode::DsAddU32, 1, 32}, {0x01u, Opcode::DsSubU32, 1, 32},
|
||||
{0x05u, Opcode::DsMinI32, 1, 32}, {0x06u, Opcode::DsMaxI32, 1, 32},
|
||||
{0x07u, Opcode::DsMinU32, 1, 32}, {0x08u, Opcode::DsMaxU32, 1, 32},
|
||||
{0x09u, Opcode::DsAndB32, 1, 32}, {0x0au, Opcode::DsOrB32, 1, 32},
|
||||
{0x0bu, Opcode::DsXorB32, 1, 32}, {0x0du, Opcode::DsWriteB32, 1, 32},
|
||||
{0x0eu, Opcode::DsWrite2B32, 2, 32}, {0x0fu, Opcode::DsWrite2St64B32, 2, 32},
|
||||
{0x12u, Opcode::DsMinF32, 1, 32}, {0x13u, Opcode::DsMaxF32, 1, 32},
|
||||
{0x1eu, Opcode::DsWriteByte, 1, 8}, {0x1fu, Opcode::DsWriteShort, 1, 16},
|
||||
{0x20u, Opcode::DsAddRtnU32, 1, 32}, {0x21u, Opcode::DsSubRtnU32, 1, 32},
|
||||
{0x25u, Opcode::DsMinRtnI32, 1, 32}, {0x26u, Opcode::DsMaxRtnI32, 1, 32},
|
||||
{0x27u, Opcode::DsMinRtnU32, 1, 32}, {0x28u, Opcode::DsMaxRtnU32, 1, 32},
|
||||
{0x29u, Opcode::DsAndRtnB32, 1, 32}, {0x2au, Opcode::DsOrRtnB32, 1, 32},
|
||||
{0x2bu, Opcode::DsXorRtnB32, 1, 32}, {0x2du, Opcode::DsWrxchgRtnB32, 1, 32},
|
||||
{0x35u, Opcode::DsSwizzleB32, 1, 32}, {0x36u, Opcode::DsReadB32, 1, 32},
|
||||
{0x37u, Opcode::DsRead2B32, 2, 32}, {0x38u, Opcode::DsRead2B32, 2, 32},
|
||||
{0x39u, Opcode::DsReadSbyte, 1, 8, true}, {0x3au, Opcode::DsReadUbyte, 1, 8},
|
||||
{0x3bu, Opcode::DsReadSshort, 1, 16, true}, {0x3cu, Opcode::DsReadUshort, 1, 16},
|
||||
{0x3du, Opcode::DsConsume, 1, 32}, {0x3eu, Opcode::DsAppend, 1, 32},
|
||||
{0x4du, Opcode::DsWriteB64, 2, 32}, {0x4eu, Opcode::DsWrite2B64, 4, 32},
|
||||
{0x4fu, Opcode::DsWrite2St64B64, 4, 32}, {0x76u, Opcode::DsReadB64, 2, 32},
|
||||
{0x77u, Opcode::DsRead2B64, 4, 32}, {0x78u, Opcode::DsRead2St64B64, 4, 32},
|
||||
{0xb0u, Opcode::DsWriteAddtidB32, 1, 32}, {0xb1u, Opcode::DsReadAddtidB32, 1, 32},
|
||||
{0xdeu, Opcode::DsWriteB96, 3, 32}, {0xdfu, Opcode::DsWriteB128, 4, 32},
|
||||
{0xfeu, Opcode::DsReadB96, 3, 32}, {0xffu, Opcode::DsReadB128, 4, 32},
|
||||
};
|
||||
|
||||
const MemoryOpcodeInfo* LookupMemoryOpcode(const MemoryOpcodeInfo* ops, uint32_t count,
|
||||
uint32_t opcode) {
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (ops[i].opcode == opcode) {
|
||||
return &ops[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t SignExtendU32(uint32_t value, uint32_t bits) {
|
||||
if (bits == 0u || bits >= 32u) {
|
||||
return value;
|
||||
}
|
||||
const uint32_t sign = 1u << (bits - 1u);
|
||||
return (value ^ sign) - sign;
|
||||
}
|
||||
|
||||
void MarkMemoryUnsupported(Instruction& inst, Family family, uint32_t opcode, const char* reason) {
|
||||
inst.family = family;
|
||||
inst.opcode_id = opcode;
|
||||
inst.opcode = Opcode::Unsupported;
|
||||
SetUnsupported(inst, family, opcode, reason);
|
||||
}
|
||||
|
||||
void ApplyMemoryInfo(Instruction& inst, const MemoryOpcodeInfo* info) {
|
||||
if (info == nullptr) {
|
||||
inst.opcode = Opcode::Unsupported;
|
||||
return;
|
||||
}
|
||||
inst.opcode = info->decoded;
|
||||
inst.data_dwords = info->data_dwords;
|
||||
inst.data_bits = info->data_bits;
|
||||
inst.data_signed = info->data_signed;
|
||||
inst.typed = info->typed;
|
||||
inst.formatted = info->formatted;
|
||||
}
|
||||
|
||||
bool IsDsWriteOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DsWriteByte:
|
||||
case Opcode::DsWriteShort:
|
||||
case Opcode::DsWrite2B32:
|
||||
case Opcode::DsWrite2St64B32:
|
||||
case Opcode::DsWrite2B64:
|
||||
case Opcode::DsWrite2St64B64:
|
||||
case Opcode::DsWriteB32:
|
||||
case Opcode::DsWriteB64:
|
||||
case Opcode::DsWriteB96:
|
||||
case Opcode::DsWriteB128: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDsAtomicOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DsAddU32:
|
||||
case Opcode::DsAddRtnU32:
|
||||
case Opcode::DsSubU32:
|
||||
case Opcode::DsSubRtnU32:
|
||||
case Opcode::DsMinI32:
|
||||
case Opcode::DsMinRtnI32:
|
||||
case Opcode::DsMaxI32:
|
||||
case Opcode::DsMaxRtnI32:
|
||||
case Opcode::DsMinU32:
|
||||
case Opcode::DsMinRtnU32:
|
||||
case Opcode::DsMaxU32:
|
||||
case Opcode::DsMaxRtnU32:
|
||||
case Opcode::DsAndB32:
|
||||
case Opcode::DsAndRtnB32:
|
||||
case Opcode::DsOrB32:
|
||||
case Opcode::DsOrRtnB32:
|
||||
case Opcode::DsXorB32:
|
||||
case Opcode::DsXorRtnB32:
|
||||
case Opcode::DsWrxchgRtnB32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t DsSourceCount(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DsWrite2B32:
|
||||
case Opcode::DsWrite2St64B32:
|
||||
case Opcode::DsWrite2B64:
|
||||
case Opcode::DsWrite2St64B64:
|
||||
case Opcode::DsMinF32:
|
||||
case Opcode::DsMaxF32: return 3u;
|
||||
case Opcode::DsReadAddtidB32:
|
||||
case Opcode::DsConsume:
|
||||
case Opcode::DsAppend: return 0u;
|
||||
default: return IsDsWriteOpcode(opcode) || IsDsAtomicOpcode(opcode) ? 2u : 1u;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsFlatStoreOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::FlatStoreByte:
|
||||
case Opcode::FlatStoreShort:
|
||||
case Opcode::FlatStoreDword:
|
||||
case Opcode::FlatStoreDwordx2:
|
||||
case Opcode::FlatStoreDwordx3:
|
||||
case Opcode::FlatStoreDwordx4: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated SMEM instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = (word0 >> 18u) & 0xffu;
|
||||
const uint32_t sdst = (word0 >> 6u) & 0x7fu;
|
||||
const uint32_t sbase = word0 & 0x3fu;
|
||||
const uint32_t soffset = (word1 >> 25u) & 0x7fu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word0;
|
||||
inst.word_count = 2;
|
||||
inst.offset = SignExtendU32(word1 & 0x1fffffu, 21u);
|
||||
inst.glc = ((word0 >> 16u) & 1u) != 0;
|
||||
inst.family = Family::SMEM;
|
||||
inst.opcode_id = opcode;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(SMEM_OPS, static_cast<uint32_t>(std::size(SMEM_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
|
||||
}
|
||||
|
||||
DecodeScalarDestination(sdst, pc, inst.dst, nullptr);
|
||||
// SMEM encodes SBASE in SGPR pairs. Scalar-buffer loads still use the same
|
||||
// pair index; their descriptor operand consumes four SGPRs from that base.
|
||||
DecodeScalarSource(sbase * 2u, pc, inst.src0, nullptr);
|
||||
DecodeScalarSource(soffset, pc, inst.src1, nullptr);
|
||||
inst.src_count = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MUBUF instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = ((word0 >> 18u) & 0x7fu) | (((word0 >> 25u) & 1u) << 7u);
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t soffset = (word1 >> 24u) & 0xffu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word0;
|
||||
inst.word_count = 2;
|
||||
inst.offset = word0 & 0xfffu;
|
||||
inst.idxen = ((word0 >> 13u) & 1u) != 0;
|
||||
inst.offen = ((word0 >> 12u) & 1u) != 0;
|
||||
inst.glc = ((word0 >> 14u) & 1u) != 0;
|
||||
inst.slc = ((word1 >> 22u) & 1u) != 0;
|
||||
inst.family = Family::MUBUF;
|
||||
inst.opcode_id = opcode;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(MUBUF_OPS, static_cast<uint32_t>(std::size(MUBUF_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::MUBUF, opcode, "MUBUF opcode is not implemented");
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst, nullptr);
|
||||
DecodeVectorGpr(vaddr, inst.src0, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(soffset, pc, inst.src2, nullptr);
|
||||
inst.src_count = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MTBUF instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = ((word0 >> 16u) & 0x7u) | (((word1 >> 21u) & 1u) << 3u);
|
||||
const uint32_t dfmt = (word0 >> 19u) & 0xfu;
|
||||
const uint32_t nfmt = (word0 >> 23u) & 0x7u;
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t soffset = (word1 >> 24u) & 0xffu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word0;
|
||||
inst.word_count = 2;
|
||||
inst.offset = word0 & 0xfffu;
|
||||
inst.idxen = ((word0 >> 13u) & 1u) != 0;
|
||||
inst.offen = ((word0 >> 12u) & 1u) != 0;
|
||||
inst.glc = ((word0 >> 14u) & 1u) != 0;
|
||||
inst.slc = ((word1 >> 22u) & 1u) != 0;
|
||||
inst.family = Family::MTBUF;
|
||||
inst.opcode_id = opcode;
|
||||
inst.data_format = dfmt;
|
||||
inst.number_format = nfmt;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(MTBUF_OPS, static_cast<uint32_t>(std::size(MTBUF_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::MTBUF, opcode, "MTBUF opcode is not implemented");
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst, nullptr);
|
||||
DecodeVectorGpr(vaddr, inst.src0, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(soffset, pc, inst.src2, nullptr);
|
||||
inst.src_count = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated FLAT instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t offset = word0 & 0xfffu;
|
||||
const uint32_t dlc = (word0 >> 12u) & 1u;
|
||||
const uint32_t lds = (word0 >> 13u) & 1u;
|
||||
const uint32_t seg = (word0 >> 14u) & 0x3u;
|
||||
const uint32_t opcode = (word0 >> 18u) & 0x7fu;
|
||||
const uint32_t vdst = (word1 >> 24u) & 0xffu;
|
||||
const uint32_t saddr = (word1 >> 16u) & 0x7fu;
|
||||
const uint32_t data = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t addr = word1 & 0xffu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word0;
|
||||
inst.word_count = 2;
|
||||
inst.offset = seg == 0u ? (offset & 0x7ffu) : SignExtendU32(offset, 12u);
|
||||
inst.glc = ((word0 >> 16u) & 1u) != 0;
|
||||
inst.slc = ((word0 >> 17u) & 1u) != 0;
|
||||
inst.family = Family::FLAT;
|
||||
inst.opcode_id = opcode;
|
||||
inst.memory_segment = seg;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(FLAT_OPS, static_cast<uint32_t>(std::size(FLAT_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
|
||||
if (dlc != 0 || lds != 0 || inst.glc || inst.slc || seg == 3u) {
|
||||
SetUnsupported(inst, Family::FLAT, opcode, "FLAT modifiers or segment are not implemented");
|
||||
return true;
|
||||
}
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::FLAT, opcode, "FLAT opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
DecodeVectorGpr(IsFlatStoreOpcode(inst.opcode) ? data : vdst, inst.dst, nullptr);
|
||||
DecodeVectorGpr(addr, inst.src0, nullptr);
|
||||
inst.src_count = 1;
|
||||
if (seg == 0u || saddr == 0x7du || saddr == 0x7fu) {
|
||||
DecodeVectorGpr(addr + 1u, inst.src1, nullptr);
|
||||
inst.src_count = 2;
|
||||
} else {
|
||||
DecodeScalarSource(saddr, pc, inst.src1, nullptr);
|
||||
inst.src_count = 2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated DS instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = (word0 >> 18u) & 0xffu;
|
||||
const uint32_t offset0 = word0 & 0xffu;
|
||||
const uint32_t offset1 = (word0 >> 8u) & 0xffu;
|
||||
const uint32_t vdst = (word1 >> 24u) & 0xffu;
|
||||
const uint32_t data1 = (word1 >> 16u) & 0xffu;
|
||||
const uint32_t data0 = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t addr = word1 & 0xffu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word0;
|
||||
inst.word_count = 2;
|
||||
inst.offset = offset0 | (offset1 << 8u);
|
||||
inst.gds = ((word0 >> 17u) & 1u) != 0u;
|
||||
inst.family = Family::DS;
|
||||
inst.opcode_id = opcode;
|
||||
const auto* info = LookupMemoryOpcode(DS_OPS, static_cast<uint32_t>(std::size(DS_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::DS, opcode, "DS opcode is not implemented");
|
||||
}
|
||||
if (inst.opcode == Opcode::DsSwizzleB32 && inst.offset >= 0xe000u) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
|
||||
}
|
||||
if (inst.gds &&
|
||||
(inst.opcode == Opcode::DsSwizzleB32 || inst.opcode == Opcode::DsWriteAddtidB32 ||
|
||||
inst.opcode == Opcode::DsReadAddtidB32)) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle/addtid is available only for LDS");
|
||||
}
|
||||
if (inst.opcode == Opcode::DsWriteAddtidB32 && data1 != 0u) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
"DS write addtid data1 operand is not implemented");
|
||||
}
|
||||
if (inst.opcode == Opcode::DsReadAddtidB32 && (data0 != 0u || data1 != 0u)) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
"DS read addtid data operands are not implemented");
|
||||
}
|
||||
if (inst.opcode == Opcode::DsWrite2B32 || opcode == 0x37u) {
|
||||
inst.offset = offset0 * 4u;
|
||||
inst.secondary_offset = offset1 * 4u;
|
||||
} else if (inst.opcode == Opcode::DsWrite2St64B32 || opcode == 0x38u) {
|
||||
inst.offset = offset0 * 256u;
|
||||
inst.secondary_offset = offset1 * 256u;
|
||||
} else if (inst.opcode == Opcode::DsWrite2B64 || inst.opcode == Opcode::DsRead2B64) {
|
||||
inst.offset = offset0 * 8u;
|
||||
inst.secondary_offset = offset1 * 8u;
|
||||
} else if (inst.opcode == Opcode::DsWrite2St64B64 || inst.opcode == Opcode::DsRead2St64B64) {
|
||||
inst.offset = offset0 * 512u;
|
||||
inst.secondary_offset = offset1 * 512u;
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdst, inst.dst, nullptr);
|
||||
DecodeVectorGpr(addr, inst.src0, nullptr);
|
||||
DecodeVectorGpr(data0, inst.src1, nullptr);
|
||||
DecodeVectorGpr(data1, inst.src2, nullptr);
|
||||
inst.src_count = DsSourceCount(inst.opcode);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, std::string* error);
|
||||
bool DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, std::string* error);
|
||||
bool DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_ */
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail {
|
||||
|
||||
struct OpcodeMap {
|
||||
uint32_t encoding = 0;
|
||||
Opcode decoded = Opcode::Unknown;
|
||||
};
|
||||
|
||||
template <typename Entry, size_t N>
|
||||
constexpr const Entry* FindOpcode(const Entry (&table)[N], uint32_t encoding) {
|
||||
for (const auto& entry: table) {
|
||||
if (entry.encoding == encoding) {
|
||||
return &entry;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename Entry, size_t N>
|
||||
constexpr Opcode LookupOpcode(const Entry (&table)[N], uint32_t encoding) {
|
||||
const auto* entry = FindOpcode(table, encoding);
|
||||
return entry != nullptr ? entry->decoded : Opcode::Unsupported;
|
||||
}
|
||||
|
||||
template <typename Entry, size_t N>
|
||||
constexpr bool HasUniqueEncodings(const Entry (&table)[N]) {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
for (size_t j = i + 1; j < N; j++) {
|
||||
if (table[i].encoding == table[j].encoding) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail
|
||||
|
||||
#endif // EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
|
||||
@@ -0,0 +1,277 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ScalarAluOps.h"
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/decode/OpcodeTable.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
using Detail::OpcodeMap;
|
||||
|
||||
constexpr OpcodeMap SOP2_OPS[] = {
|
||||
{0x00u, Opcode::SAddU32}, {0x01u, Opcode::SSubU32}, {0x02u, Opcode::SAddI32},
|
||||
{0x03u, Opcode::SSubI32}, {0x04u, Opcode::SAddcU32}, {0x05u, Opcode::SSubbU32},
|
||||
{0x06u, Opcode::SMinI32}, {0x07u, Opcode::SMinU32}, {0x08u, Opcode::SMaxI32},
|
||||
{0x09u, Opcode::SMaxU32}, {0x0au, Opcode::SCselectB32}, {0x0bu, Opcode::SCselectB64},
|
||||
{0x0eu, Opcode::SAndB32}, {0x0fu, Opcode::SAndB64}, {0x10u, Opcode::SOrB32},
|
||||
{0x11u, Opcode::SOrB64}, {0x12u, Opcode::SXorB32}, {0x13u, Opcode::SXorB64},
|
||||
{0x14u, Opcode::SAndn2B32}, {0x15u, Opcode::SAndn2B64}, {0x16u, Opcode::SOrn2B32},
|
||||
{0x17u, Opcode::SOrn2B64}, {0x18u, Opcode::SNandB32}, {0x19u, Opcode::SNandB64},
|
||||
{0x1au, Opcode::SNorB32}, {0x1bu, Opcode::SNorB64}, {0x1cu, Opcode::SXnorB32},
|
||||
{0x1du, Opcode::SXnorB64}, {0x1eu, Opcode::SLshlB32}, {0x1fu, Opcode::SLshlB64},
|
||||
{0x20u, Opcode::SLshrB32}, {0x21u, Opcode::SLshrB64}, {0x22u, Opcode::SAshrI32},
|
||||
{0x24u, Opcode::SBfmB32}, {0x25u, Opcode::SBfmB64}, {0x26u, Opcode::SMulI32},
|
||||
{0x27u, Opcode::SBfeU32}, {0x29u, Opcode::SBfeU64}, {0x2eu, Opcode::SLshl1AddU32},
|
||||
{0x2fu, Opcode::SLshl2AddU32}, {0x30u, Opcode::SLshl3AddU32}, {0x31u, Opcode::SLshl4AddU32},
|
||||
{0x32u, Opcode::SPackLlB32B16}, {0x33u, Opcode::SPackLhB32B16}, {0x34u, Opcode::SPackHhB32B16},
|
||||
{0x35u, Opcode::SMulHiU32},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOP1_OPS[] = {
|
||||
{0x03u, Opcode::SMovB32},
|
||||
{0x04u, Opcode::SMovB64},
|
||||
{0x07u, Opcode::SNotB32},
|
||||
{0x08u, Opcode::SNotB64},
|
||||
{0x0au, Opcode::SWqmB64},
|
||||
{0x0bu, Opcode::SBrevB32},
|
||||
{0x0fu, Opcode::SBcnt1I32B32},
|
||||
{0x10u, Opcode::SBcnt1I32B64},
|
||||
{0x13u, Opcode::SFf1I32B32},
|
||||
{0x16u, Opcode::SFlbitI32B64},
|
||||
{0x1bu, Opcode::SBitset0B32},
|
||||
{0x1du, Opcode::SBitset1B32},
|
||||
{0x1fu, Opcode::SGetpcB64},
|
||||
{0x20u, Opcode::SSetpcB64},
|
||||
{0x24u, Opcode::SAndSaveexecB64},
|
||||
{0x28u, Opcode::SOrn2SaveexecB64},
|
||||
{0x34u, Opcode::SAbsI32},
|
||||
{0x37u, Opcode::SAndn1SaveexecB64},
|
||||
{0x3bu, Opcode::SBitreplicateB64B32},
|
||||
{0x3cu, Opcode::SAndSaveexecB32},
|
||||
{0x44u, Opcode::SAndn1SaveexecB32},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOPC_OPS[] = {
|
||||
{0x00u, Opcode::SCmpEqI32}, {0x01u, Opcode::SCmpLgI32}, {0x02u, Opcode::SCmpGtI32},
|
||||
{0x03u, Opcode::SCmpGeI32}, {0x04u, Opcode::SCmpLtI32}, {0x05u, Opcode::SCmpLeI32},
|
||||
{0x06u, Opcode::SCmpEqU32}, {0x07u, Opcode::SCmpLgU32}, {0x08u, Opcode::SCmpGtU32},
|
||||
{0x09u, Opcode::SCmpGeU32}, {0x0au, Opcode::SCmpLtU32}, {0x0bu, Opcode::SCmpLeU32},
|
||||
{0x0cu, Opcode::SBitcmp0B32}, {0x0du, Opcode::SBitcmp1B32}, {0x12u, Opcode::SCmpEqU64},
|
||||
{0x13u, Opcode::SCmpLgU64},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOPK_OPS[] = {
|
||||
{0x00u, Opcode::SMovkI32}, {0x03u, Opcode::SCmpEqI32}, {0x04u, Opcode::SCmpLgI32},
|
||||
{0x05u, Opcode::SCmpGtI32}, {0x06u, Opcode::SCmpGeI32}, {0x07u, Opcode::SCmpLtI32},
|
||||
{0x08u, Opcode::SCmpLeI32}, {0x09u, Opcode::SCmpEqU32}, {0x0au, Opcode::SCmpLgU32},
|
||||
{0x0bu, Opcode::SCmpGtU32}, {0x0cu, Opcode::SCmpGeU32}, {0x0du, Opcode::SCmpLtU32},
|
||||
{0x0eu, Opcode::SCmpLeU32}, {0x0fu, Opcode::SAddI32}, {0x10u, Opcode::SMulkI32},
|
||||
{0x13u, Opcode::SSetregB32}, {0x17u, Opcode::SWaitcnt}, {0x18u, Opcode::SWaitcnt},
|
||||
{0x19u, Opcode::SWaitcnt}, {0x1au, Opcode::SWaitcnt},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOPP_OPS[] = {
|
||||
{0x00u, Opcode::SNop}, {0x01u, Opcode::SEndpgm}, {0x02u, Opcode::SBranch},
|
||||
{0x04u, Opcode::SCbranchScc0}, {0x05u, Opcode::SCbranchScc1}, {0x06u, Opcode::SCbranchVccz},
|
||||
{0x07u, Opcode::SCbranchVccnz}, {0x08u, Opcode::SCbranchExecz}, {0x09u, Opcode::SCbranchExecnz},
|
||||
{0x0au, Opcode::SBarrier}, {0x0cu, Opcode::SWaitcnt}, {0x0eu, Opcode::SSleep},
|
||||
{0x10u, Opcode::SSendmsg}, {0x16u, Opcode::STtraceData}, {0x20u, Opcode::SInstPrefetch},
|
||||
};
|
||||
static_assert(Detail::HasUniqueEncodings(SOP1_OPS));
|
||||
static_assert(Detail::HasUniqueEncodings(SOP2_OPS));
|
||||
static_assert(Detail::HasUniqueEncodings(SOPK_OPS));
|
||||
static_assert(Detail::HasUniqueEncodings(SOPC_OPS));
|
||||
static_assert(Detail::HasUniqueEncodings(SOPP_OPS));
|
||||
|
||||
bool DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) {
|
||||
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) ||
|
||||
!DecodeScalarSource(ssrc1, pc, inst.src1, error)) {
|
||||
return false;
|
||||
}
|
||||
inst.src_count = 2;
|
||||
return ReadLiteralOperands(code, word_index, inst, error);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word;
|
||||
inst.family = Family::SOP1;
|
||||
inst.opcode_id = opcode;
|
||||
inst.opcode = Detail::LookupOpcode(SOP1_OPS, opcode);
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOP1, opcode, "SOP1 opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (inst.opcode) {
|
||||
case Opcode::SGetpcB64:
|
||||
inst.src_count = 0;
|
||||
return DecodeScalarDestination(sdst, pc, inst.dst, error);
|
||||
case Opcode::SSetpcB64:
|
||||
inst.src_count = 1;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error)) {
|
||||
return false;
|
||||
}
|
||||
return ReadLiteralOperands(code, word_index, inst, error);
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) ||
|
||||
!DecodeScalarDestination(sdst, pc, inst.dst, error)) {
|
||||
return false;
|
||||
}
|
||||
inst.src_count = 1;
|
||||
return ReadLiteralOperands(code, word_index, inst, error);
|
||||
}
|
||||
|
||||
bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 23u) & 0x7fu;
|
||||
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word;
|
||||
inst.family = Family::SOP2;
|
||||
inst.opcode_id = opcode;
|
||||
inst.opcode = Detail::LookupOpcode(SOP2_OPS, opcode);
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOP2, opcode, "SOP2 opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DecodeScalarDestination(sdst, pc, inst.dst, error)) {
|
||||
return false;
|
||||
}
|
||||
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
|
||||
}
|
||||
|
||||
bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 23u) & 0x1fu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
const auto imm = static_cast<int16_t>(word & 0xffffu);
|
||||
|
||||
(void)error;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word;
|
||||
inst.family = Family::SOPK;
|
||||
inst.opcode_id = opcode;
|
||||
inst.opcode = Detail::LookupOpcode(SOPK_OPS, opcode);
|
||||
inst.src0.kind = OperandKind::IntegerInlineConstant;
|
||||
inst.src0.signed_val = imm;
|
||||
inst.src0.value = static_cast<uint32_t>(imm);
|
||||
inst.src_count = 1;
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOPK, opcode, "SOPK opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (inst.opcode) {
|
||||
case Opcode::SMovkI32: return DecodeScalarDestination(sdst, pc, inst.dst, error);
|
||||
case Opcode::SWaitcnt: {
|
||||
const uint32_t waitcnt = word & 0xffffu;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
inst.src0.signed_val = static_cast<int32_t>(waitcnt);
|
||||
inst.src0.value = waitcnt;
|
||||
inst.src_count = 1;
|
||||
return true;
|
||||
}
|
||||
case Opcode::SSetregB32:
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
inst.src1.kind = OperandKind::LiteralConstant;
|
||||
inst.src1.value = word & 0xffffu;
|
||||
inst.src1.signed_val = static_cast<int32_t>(imm);
|
||||
inst.src_count = 2;
|
||||
return DecodeScalarSource(sdst, pc, inst.src0, error);
|
||||
default: break;
|
||||
}
|
||||
|
||||
inst.src1 = inst.src0;
|
||||
if (!DecodeScalarSource(sdst, pc, inst.src0, error)) {
|
||||
return false;
|
||||
}
|
||||
if (inst.opcode == Opcode::SAddI32 || inst.opcode == Opcode::SMulkI32) {
|
||||
inst.src_count = 2;
|
||||
return DecodeScalarDestination(sdst, pc, inst.dst, error);
|
||||
}
|
||||
|
||||
inst.dst.kind = OperandKind::Scc;
|
||||
inst.src_count = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
const uint32_t opcode = (word >> 16u) & 0x7fu;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word;
|
||||
inst.family = Family::SOPC;
|
||||
inst.opcode_id = opcode;
|
||||
inst.opcode = Detail::LookupOpcode(SOPC_OPS, opcode);
|
||||
inst.dst.kind = OperandKind::Scc;
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOPC, opcode, "SOPC opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
|
||||
}
|
||||
|
||||
bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 16u) & 0x7fu;
|
||||
const uint32_t simm = word & 0xffffu;
|
||||
|
||||
(void)error;
|
||||
|
||||
inst.pc = pc;
|
||||
inst.word = word;
|
||||
inst.family = Family::SOPP;
|
||||
inst.opcode_id = opcode;
|
||||
inst.opcode = Detail::LookupOpcode(SOPP_OPS, opcode);
|
||||
inst.src0.kind = OperandKind::LiteralConstant;
|
||||
inst.src0.value = simm;
|
||||
inst.src0.signed_val = static_cast<int16_t>(simm);
|
||||
inst.src_count = (inst.opcode == Opcode::SNop || inst.opcode == Opcode::SWaitcnt ||
|
||||
inst.opcode == Opcode::SSleep || inst.opcode == Opcode::SSendmsg ||
|
||||
inst.opcode == Opcode::STtraceData || inst.opcode == Opcode::SInstPrefetch)
|
||||
? 1
|
||||
: 0;
|
||||
inst.branch_offset = static_cast<int32_t>(static_cast<int16_t>(simm)) * 4;
|
||||
inst.branch_target = pc + 4u + static_cast<uint32_t>(inst.branch_offset);
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst.opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOPP, opcode, "SOPP control-flow opcode is not implemented");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,689 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
enum class Family {
|
||||
Unknown,
|
||||
SOP1,
|
||||
SOP2,
|
||||
SOPK,
|
||||
SOPC,
|
||||
SOPP,
|
||||
VOP1,
|
||||
VOP2,
|
||||
VOP3,
|
||||
VOP3P,
|
||||
VOPC,
|
||||
VINTRP,
|
||||
SMEM,
|
||||
MUBUF,
|
||||
MTBUF,
|
||||
FLAT,
|
||||
DS,
|
||||
MIMG,
|
||||
EXP
|
||||
};
|
||||
|
||||
enum class Opcode {
|
||||
Unknown,
|
||||
Unsupported,
|
||||
|
||||
SMovB32,
|
||||
SMovB64,
|
||||
SMovkI32,
|
||||
SAbsI32,
|
||||
SBrevB32,
|
||||
SBcnt1I32B32,
|
||||
SBcnt1I32B64,
|
||||
SFf1I32B32,
|
||||
SFlbitI32B64,
|
||||
SBitreplicateB64B32,
|
||||
SGetpcB64,
|
||||
SSetpcB64,
|
||||
SAndSaveexecB32,
|
||||
SAndn1SaveexecB32,
|
||||
SAndSaveexecB64,
|
||||
SOrn2SaveexecB64,
|
||||
SAndn1SaveexecB64,
|
||||
SNotB32,
|
||||
SNotB64,
|
||||
SWqmB64,
|
||||
SAddU32,
|
||||
SAddcU32,
|
||||
SSubbU32,
|
||||
SSubU32,
|
||||
SAddI32,
|
||||
SSubI32,
|
||||
SBitcmp0B32,
|
||||
SBitcmp1B32,
|
||||
SBitset0B32,
|
||||
SBitset1B32,
|
||||
SMinI32,
|
||||
SMaxI32,
|
||||
SMinU32,
|
||||
SMaxU32,
|
||||
SAndB32,
|
||||
SAndB64,
|
||||
SAndn2B32,
|
||||
SAndn2B64,
|
||||
SOrB32,
|
||||
SOrB64,
|
||||
SOrn2B32,
|
||||
SOrn2B64,
|
||||
SXorB32,
|
||||
SXorB64,
|
||||
SNandB32,
|
||||
SNandB64,
|
||||
SNorB32,
|
||||
SNorB64,
|
||||
SXnorB32,
|
||||
SXnorB64,
|
||||
SLshlB32,
|
||||
SLshlB64,
|
||||
SLshl1AddU32,
|
||||
SLshl2AddU32,
|
||||
SLshl3AddU32,
|
||||
SLshl4AddU32,
|
||||
SLshrB32,
|
||||
SLshrB64,
|
||||
SAshrI32,
|
||||
SMulI32,
|
||||
SMulHiU32,
|
||||
SMulkI32,
|
||||
SBfeU32,
|
||||
SBfeU64,
|
||||
SBfmB32,
|
||||
SBfmB64,
|
||||
SCselectB32,
|
||||
SCselectB64,
|
||||
SPackLlB32B16,
|
||||
SPackLhB32B16,
|
||||
SPackHhB32B16,
|
||||
SCmpEqI32,
|
||||
SCmpLgI32,
|
||||
SCmpGtI32,
|
||||
SCmpGeI32,
|
||||
SCmpLtI32,
|
||||
SCmpLeI32,
|
||||
SCmpEqU32,
|
||||
SCmpLgU32,
|
||||
SCmpGtU32,
|
||||
SCmpGeU32,
|
||||
SCmpLtU32,
|
||||
SCmpLeU32,
|
||||
SCmpEqU64,
|
||||
SCmpLgU64,
|
||||
|
||||
VNop,
|
||||
VMovB32,
|
||||
VMovreldB32,
|
||||
VMovrelsB32,
|
||||
VReadfirstlaneB32,
|
||||
VReadlaneB32,
|
||||
VWritelaneB32,
|
||||
VPermlane16B32,
|
||||
VPermlanex16B32,
|
||||
VCubeidF32,
|
||||
VCubescF32,
|
||||
VCubetcF32,
|
||||
VCubemaF32,
|
||||
VCndmaskB32,
|
||||
VDot2cF32F16,
|
||||
VCvtF32I32,
|
||||
VCvtF32U32,
|
||||
VCvtU32F32,
|
||||
VCvtI32F32,
|
||||
VCvtF16F32,
|
||||
VCvtF32F16,
|
||||
VCvtF16U16,
|
||||
VCvtU16F16,
|
||||
VCvtF16I16,
|
||||
VCvtI16F16,
|
||||
VCvtRpiI32F32,
|
||||
VCvtFlrI32F32,
|
||||
VCvtOffF32I4,
|
||||
VCvtF32Ubyte0,
|
||||
VCvtF32Ubyte1,
|
||||
VCvtF32Ubyte2,
|
||||
VCvtF32Ubyte3,
|
||||
VRcpF32,
|
||||
VRcpIflagF32,
|
||||
VFractF32,
|
||||
VTruncF32,
|
||||
VCeilF32,
|
||||
VRndneF32,
|
||||
VFloorF32,
|
||||
VExpF32,
|
||||
VLogF32,
|
||||
VRsqF32,
|
||||
VSqrtF32,
|
||||
VRcpF16,
|
||||
VSqrtF16,
|
||||
VRsqF16,
|
||||
VLogF16,
|
||||
VExpF16,
|
||||
VFloorF16,
|
||||
VCeilF16,
|
||||
VTruncF16,
|
||||
VRndneF16,
|
||||
VSinF32,
|
||||
VCosF32,
|
||||
VNotB32,
|
||||
VBfrevB32,
|
||||
VFfbhU32,
|
||||
VFfblB32,
|
||||
VAddF32,
|
||||
VSubF32,
|
||||
VSubrevF32,
|
||||
VMulF32,
|
||||
VMacF32,
|
||||
VMadmkF32,
|
||||
VMadakF32,
|
||||
VMinF32,
|
||||
VMaxF32,
|
||||
VAddF16,
|
||||
VSubF16,
|
||||
VSubrevF16,
|
||||
VMulF16,
|
||||
VFmacF16,
|
||||
VFmamkF16,
|
||||
VFmaakF16,
|
||||
VMinI32,
|
||||
VMaxI32,
|
||||
VMulI32I24,
|
||||
VMulU32U24,
|
||||
VMulLoU32,
|
||||
VMulHiU32,
|
||||
VMulLoI32,
|
||||
VMulHiI32,
|
||||
VCvtPkrtzF16F32,
|
||||
VCvtPkU8F32,
|
||||
VMadF32,
|
||||
VMadI32I24,
|
||||
VMadU32U24,
|
||||
VMadU64U32,
|
||||
VFmaF32,
|
||||
VFmaF16,
|
||||
VPackB32F16,
|
||||
VBfeU32,
|
||||
VBfeI32,
|
||||
VBfiB32,
|
||||
VAlignbitB32,
|
||||
VMin3F32,
|
||||
VMin3I32,
|
||||
VMin3U32,
|
||||
VMin3F16,
|
||||
VMax3F32,
|
||||
VMax3I32,
|
||||
VMax3U32,
|
||||
VMax3F16,
|
||||
VMed3F32,
|
||||
VMed3I32,
|
||||
VMed3U32,
|
||||
VMed3F16,
|
||||
VSadU32,
|
||||
VAdd3U32,
|
||||
VLshlAddU32,
|
||||
VAddLshlU32,
|
||||
VXadU32,
|
||||
VLshlOrB32,
|
||||
VAndOrB32,
|
||||
VOr3B32,
|
||||
VXor3B32,
|
||||
VAddI32,
|
||||
VSubI32,
|
||||
VSubrevI32,
|
||||
VBfmB32,
|
||||
VLdexpF32,
|
||||
VCvtPknormI16F32,
|
||||
VCvtPknormU16F32,
|
||||
VCvtPkU16U32,
|
||||
VPkMadI16,
|
||||
VPkMulLoU16,
|
||||
VPkAddI16,
|
||||
VPkSubI16,
|
||||
VPkLshlrevB16,
|
||||
VPkLshrrevB16,
|
||||
VPkAshrrevI16,
|
||||
VPkMaxI16,
|
||||
VPkMinI16,
|
||||
VPkMadU16,
|
||||
VPkAddF16,
|
||||
VPkMulF16,
|
||||
VPkMinF16,
|
||||
VPkMaxF16,
|
||||
VPkFmaF16,
|
||||
VPkFmacF16,
|
||||
VPkAddU16,
|
||||
VPkSubU16,
|
||||
VPkMaxU16,
|
||||
VPkMinU16,
|
||||
VMadMixloF16,
|
||||
VMadMixhiF16,
|
||||
VAddNcU32,
|
||||
VAddcU32,
|
||||
VSubNcU32,
|
||||
VSubrevNcU32,
|
||||
VAddNcU16,
|
||||
VSubNcU16,
|
||||
VMaxU16,
|
||||
VMaxI16,
|
||||
VMinU16,
|
||||
VMinI16,
|
||||
VAddNcI16,
|
||||
VSubNcI16,
|
||||
VAndB32,
|
||||
VOrB32,
|
||||
VXorB32,
|
||||
VXnorB32,
|
||||
VLshlB32,
|
||||
VLshlrevB32,
|
||||
VLshrB32,
|
||||
VLshrrevB32,
|
||||
VAshrI32,
|
||||
VAshrrevI32,
|
||||
VLshlrevB16,
|
||||
VLshrrevB16,
|
||||
VAshrrevI16,
|
||||
VBcntU32B32,
|
||||
VMbcntLoU32B32,
|
||||
VMbcntHiU32B32,
|
||||
VMinU32,
|
||||
VMaxU32,
|
||||
VMaxF16,
|
||||
VMinF16,
|
||||
VCmpFF32,
|
||||
VCmpLtF32,
|
||||
VCmpEqF32,
|
||||
VCmpLeF32,
|
||||
VCmpGtF32,
|
||||
VCmpLgF32,
|
||||
VCmpGeF32,
|
||||
VCmpOF32,
|
||||
VCmpUF32,
|
||||
VCmpNgeF32,
|
||||
VCmpNlgF32,
|
||||
VCmpNgtF32,
|
||||
VCmpNleF32,
|
||||
VCmpNeqF32,
|
||||
VCmpNltF32,
|
||||
VCmpTruF32,
|
||||
VCmpxLtF32,
|
||||
VCmpxEqF32,
|
||||
VCmpxLeF32,
|
||||
VCmpxGtF32,
|
||||
VCmpxLgF32,
|
||||
VCmpxGeF32,
|
||||
VCmpxNgeF32,
|
||||
VCmpxNlgF32,
|
||||
VCmpxNgtF32,
|
||||
VCmpxNleF32,
|
||||
VCmpxNeqF32,
|
||||
VCmpxNltF32,
|
||||
VCmpFI32,
|
||||
VCmpLtI32,
|
||||
VCmpEqI32,
|
||||
VCmpLeI32,
|
||||
VCmpGtI32,
|
||||
VCmpNeI32,
|
||||
VCmpGeI32,
|
||||
VCmpTI32,
|
||||
VCmpClassF32,
|
||||
VCmpLtI16,
|
||||
VCmpEqI16,
|
||||
VCmpLeI16,
|
||||
VCmpGtI16,
|
||||
VCmpNeI16,
|
||||
VCmpGeI16,
|
||||
VCmpLtF16,
|
||||
VCmpEqF16,
|
||||
VCmpLeF16,
|
||||
VCmpGtF16,
|
||||
VCmpLgF16,
|
||||
VCmpGeF16,
|
||||
VCmpNeqF16,
|
||||
VCmpxLtF16,
|
||||
VCmpxEqF16,
|
||||
VCmpxLeF16,
|
||||
VCmpxGtF16,
|
||||
VCmpxGeF16,
|
||||
VCmpxNeqF16,
|
||||
VCmpxNltF16,
|
||||
VCmpxLtI32,
|
||||
VCmpxEqI32,
|
||||
VCmpxLeI32,
|
||||
VCmpxGtI32,
|
||||
VCmpxNeI32,
|
||||
VCmpxGeI32,
|
||||
VCmpLtU16,
|
||||
VCmpEqU16,
|
||||
VCmpLeU16,
|
||||
VCmpGtU16,
|
||||
VCmpNeU16,
|
||||
VCmpGeU16,
|
||||
VCmpFU32,
|
||||
VCmpLtU32,
|
||||
VCmpEqU32,
|
||||
VCmpLeU32,
|
||||
VCmpGtU32,
|
||||
VCmpNeU32,
|
||||
VCmpGeU32,
|
||||
VCmpTU32,
|
||||
VCmpEqI64,
|
||||
VCmpNeU64,
|
||||
VCmpxLtU32,
|
||||
VCmpxEqU32,
|
||||
VCmpxLeU32,
|
||||
VCmpxGtU32,
|
||||
VCmpxNeU32,
|
||||
VCmpxGeU32,
|
||||
|
||||
SLoadDword,
|
||||
SLoadDwordx2,
|
||||
SLoadDwordx4,
|
||||
SLoadDwordx8,
|
||||
SLoadDwordx16,
|
||||
SBufferLoadDword,
|
||||
SBufferLoadDwordx2,
|
||||
SBufferLoadDwordx4,
|
||||
SBufferLoadDwordx8,
|
||||
SBufferLoadDwordx16,
|
||||
BufferLoadFormatX,
|
||||
BufferLoadFormatXy,
|
||||
BufferLoadFormatXyz,
|
||||
BufferLoadFormatXyzw,
|
||||
BufferStoreFormatX,
|
||||
BufferStoreFormatXy,
|
||||
BufferStoreFormatXyz,
|
||||
BufferStoreFormatXyzw,
|
||||
BufferLoadUbyte,
|
||||
BufferLoadSbyte,
|
||||
BufferLoadUshort,
|
||||
BufferLoadSshort,
|
||||
BufferLoadDword,
|
||||
BufferLoadDwordx2,
|
||||
BufferLoadDwordx3,
|
||||
BufferLoadDwordx4,
|
||||
BufferStoreByte,
|
||||
BufferStoreShort,
|
||||
BufferStoreDword,
|
||||
BufferStoreDwordx2,
|
||||
BufferStoreDwordx3,
|
||||
BufferStoreDwordx4,
|
||||
TBufferLoadFormatX,
|
||||
TBufferLoadFormatXy,
|
||||
TBufferLoadFormatXyz,
|
||||
TBufferLoadFormatXyzw,
|
||||
TBufferStoreFormatX,
|
||||
TBufferStoreFormatXy,
|
||||
TBufferStoreFormatXyz,
|
||||
TBufferStoreFormatXyzw,
|
||||
BufferAtomicSwap,
|
||||
BufferAtomicAdd,
|
||||
BufferAtomicSub,
|
||||
BufferAtomicSMin,
|
||||
BufferAtomicUMin,
|
||||
BufferAtomicSMax,
|
||||
BufferAtomicUMax,
|
||||
BufferAtomicAnd,
|
||||
BufferAtomicOr,
|
||||
BufferAtomicXor,
|
||||
BufferAtomicFMin,
|
||||
BufferAtomicFMax,
|
||||
FlatLoadUbyte,
|
||||
FlatLoadSbyte,
|
||||
FlatLoadUshort,
|
||||
FlatLoadSshort,
|
||||
FlatLoadDword,
|
||||
FlatLoadDwordx2,
|
||||
FlatLoadDwordx3,
|
||||
FlatLoadDwordx4,
|
||||
FlatStoreByte,
|
||||
FlatStoreShort,
|
||||
FlatStoreDword,
|
||||
FlatStoreDwordx2,
|
||||
FlatStoreDwordx3,
|
||||
FlatStoreDwordx4,
|
||||
DsAddU32,
|
||||
DsAddRtnU32,
|
||||
DsSubU32,
|
||||
DsSubRtnU32,
|
||||
DsMinI32,
|
||||
DsMinRtnI32,
|
||||
DsMaxI32,
|
||||
DsMaxRtnI32,
|
||||
DsMinU32,
|
||||
DsMinRtnU32,
|
||||
DsMaxU32,
|
||||
DsMaxRtnU32,
|
||||
DsAndB32,
|
||||
DsAndRtnB32,
|
||||
DsOrB32,
|
||||
DsOrRtnB32,
|
||||
DsXorB32,
|
||||
DsXorRtnB32,
|
||||
DsWrxchgRtnB32,
|
||||
DsMinF32,
|
||||
DsMaxF32,
|
||||
DsSwizzleB32,
|
||||
DsConsume,
|
||||
DsAppend,
|
||||
DsReadSbyte,
|
||||
DsReadUbyte,
|
||||
DsReadSshort,
|
||||
DsReadUshort,
|
||||
DsRead2B32,
|
||||
DsReadB32,
|
||||
DsReadB64,
|
||||
DsRead2B64,
|
||||
DsRead2St64B64,
|
||||
DsReadB96,
|
||||
DsReadB128,
|
||||
DsWriteByte,
|
||||
DsWriteShort,
|
||||
DsWrite2B32,
|
||||
DsWrite2St64B32,
|
||||
DsWrite2B64,
|
||||
DsWrite2St64B64,
|
||||
DsWriteB32,
|
||||
DsWriteB64,
|
||||
DsWriteB96,
|
||||
DsWriteB128,
|
||||
DsWriteAddtidB32,
|
||||
DsReadAddtidB32,
|
||||
ImageGetResinfo,
|
||||
ImageGetLod,
|
||||
ImageLoad,
|
||||
ImageLoadMip,
|
||||
ImageStore,
|
||||
ImageStoreMip,
|
||||
ImageAtomicAdd,
|
||||
ImageAtomicUMin,
|
||||
ImageAtomicUMax,
|
||||
ImageAtomicAnd,
|
||||
ImageAtomicOr,
|
||||
ImageAtomicXor,
|
||||
ImageSample,
|
||||
ImageGather4Lz,
|
||||
ImageGather4C,
|
||||
ImageGather4CLz,
|
||||
ImageGather4LzO,
|
||||
ImageGather4CO,
|
||||
ImageGather4CLzO,
|
||||
ImageGather4H,
|
||||
VInterpP1F32,
|
||||
VInterpP2F32,
|
||||
VInterpMovF32,
|
||||
|
||||
SNop,
|
||||
SWaitcnt,
|
||||
SBarrier,
|
||||
SBranch,
|
||||
SCbranchScc0,
|
||||
SCbranchScc1,
|
||||
SCbranchVccz,
|
||||
SCbranchVccnz,
|
||||
SCbranchExecz,
|
||||
SCbranchExecnz,
|
||||
SSendmsg,
|
||||
SSetregB32,
|
||||
SSleep,
|
||||
STtraceData,
|
||||
SInstPrefetch,
|
||||
SEndpgm,
|
||||
Exp
|
||||
};
|
||||
|
||||
enum class OperandKind {
|
||||
Unknown,
|
||||
LiteralConstant,
|
||||
IntegerInlineConstant,
|
||||
FloatInlineConstant,
|
||||
Sgpr,
|
||||
VccLo,
|
||||
VccHi,
|
||||
VccZ,
|
||||
ExecLo,
|
||||
ExecHi,
|
||||
ExecZ,
|
||||
Scc,
|
||||
M0,
|
||||
PopsExitingWaveId,
|
||||
Null,
|
||||
Vgpr,
|
||||
};
|
||||
|
||||
enum ImageSampleFlag : uint32_t {
|
||||
ImageSampleFlagLod = 1u << 0u,
|
||||
ImageSampleFlagBias = 1u << 1u,
|
||||
ImageSampleFlagDerivative = 1u << 2u,
|
||||
ImageSampleFlagCompare = 1u << 3u,
|
||||
ImageSampleFlagOffset = 1u << 4u,
|
||||
ImageSampleFlagLevelZero = 1u << 5u,
|
||||
ImageSampleFlagLodClamp = 1u << 6u,
|
||||
ImageSampleFlagA16 = 1u << 7u,
|
||||
ImageSampleFlagCd = 1u << 8u,
|
||||
ImageSampleFlagGatherHorizontal = 1u << 9u,
|
||||
};
|
||||
|
||||
enum class ImageDimension : uint32_t {
|
||||
Unknown,
|
||||
Dim1D,
|
||||
Dim1DArray,
|
||||
Dim2D,
|
||||
Dim3D,
|
||||
Dim2DArray,
|
||||
Dim2DMsaa,
|
||||
Dim2DMsaaArray,
|
||||
};
|
||||
|
||||
constexpr uint32_t MaxInstructionRawWords = 5u;
|
||||
constexpr uint32_t MaxImageNsaAddressComponents = 12u;
|
||||
|
||||
struct Operand {
|
||||
OperandKind kind = OperandKind::Unknown;
|
||||
uint32_t value = 0;
|
||||
int32_t signed_val = 0;
|
||||
float float_val = 0.0f;
|
||||
uint32_t reg = 0;
|
||||
uint32_t sdwa_sel = 6;
|
||||
// Native 16-bit destinations use the same selector fields internally but preserve the
|
||||
// untouched half. Real SDWA encodings overwrite this with their decoded DST_U value.
|
||||
uint32_t sdwa_dst_unused = 2;
|
||||
uint32_t omod = 0;
|
||||
uint32_t dpp_ctrl = 0;
|
||||
uint32_t dpp_row_mask = 0xf;
|
||||
uint32_t dpp_bank_mask = 0xf;
|
||||
bool sdwa_sext = false;
|
||||
bool dpp_fetch_inactive = false;
|
||||
bool dpp_bound_ctrl = false;
|
||||
bool op_sel = false;
|
||||
bool op_sel_hi = false;
|
||||
bool negate = false;
|
||||
bool negate_hi = false;
|
||||
bool absolute = false;
|
||||
bool clamp = false;
|
||||
bool dpp = false;
|
||||
};
|
||||
|
||||
struct Instruction {
|
||||
uint32_t pc = 0;
|
||||
uint32_t word = 0;
|
||||
uint32_t word_count = 1;
|
||||
uint32_t raw[MaxInstructionRawWords] = {};
|
||||
uint32_t raw_count = 1;
|
||||
Family family = Family::Unknown;
|
||||
uint32_t opcode_id = 0;
|
||||
Opcode opcode = Opcode::Unknown;
|
||||
Operand dst;
|
||||
Operand dst2;
|
||||
Operand src0;
|
||||
Operand src1;
|
||||
Operand src2;
|
||||
Operand src3;
|
||||
uint32_t src_count = 0;
|
||||
uint32_t offset = 0;
|
||||
uint32_t secondary_offset = 0;
|
||||
uint32_t dmask = 0;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
uint32_t data_format = 0;
|
||||
uint32_t number_format = 0;
|
||||
uint32_t image_sample_flags = 0;
|
||||
ImageDimension image_dimension = ImageDimension::Unknown;
|
||||
uint32_t image_address_components = 0;
|
||||
uint32_t image_nsa_dwords = 0;
|
||||
uint32_t image_nsa_addr[MaxImageNsaAddressComponents] = {};
|
||||
uint32_t memory_segment = 0;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
bool gds = false;
|
||||
bool glc = false;
|
||||
bool slc = false;
|
||||
bool idxen = false;
|
||||
bool offen = false;
|
||||
int32_t branch_offset = 0;
|
||||
uint32_t branch_target = 0;
|
||||
struct {
|
||||
uint32_t target = 0;
|
||||
uint32_t en = 0;
|
||||
bool done = false;
|
||||
bool compr = false;
|
||||
bool vm = false;
|
||||
} exp;
|
||||
std::string unsupported_reason;
|
||||
};
|
||||
|
||||
struct Program {
|
||||
std::span<const uint32_t> code;
|
||||
std::vector<Instruction> instructions;
|
||||
};
|
||||
|
||||
bool DecodeProgram(std::span<const uint32_t> code, Program& program, std::string* error);
|
||||
|
||||
bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand, std::string* error);
|
||||
bool DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand, std::string* error);
|
||||
bool DecodeVectorGpr(uint32_t reg, Operand& operand, std::string* error);
|
||||
bool ReadLiteralOperands(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
void SetRawWords(Instruction& inst, std::span<const uint32_t> code, uint32_t word_index,
|
||||
uint32_t word_count);
|
||||
void SetUnsupported(Instruction& inst, Family family, uint32_t opcode_id, const char* reason);
|
||||
std::string FamilyToString(Family family);
|
||||
std::string OpcodeToString(Opcode opcode);
|
||||
std::string OperandToString(const Operand& operand);
|
||||
const char* ImageDimensionToString(ImageDimension dimension);
|
||||
std::string InstructionToString(const Instruction& inst);
|
||||
std::string ProgramToString(const Program& program);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVop3p(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, std::string* error);
|
||||
bool DecodeVintrp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_ */
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
IR::ExportFlags Translator::AddExportInfo(const IR::Instruction& inst) {
|
||||
const auto index = static_cast<uint32_t>(value_program.export_info.size());
|
||||
value_program.export_info.push_back(inst.export_info);
|
||||
return {.index = index, .pc = inst.pc};
|
||||
}
|
||||
|
||||
bool Translator::TranslateAttributeOperation(const IR::Instruction& inst) {
|
||||
if (inst.op == IR::Opcode::LoadInputF32) {
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(IR::ValueOpcode::GetAttribute,
|
||||
{IR::Value(inst.input_info.attr), IR::Value(inst.input_info.chan)}));
|
||||
return true;
|
||||
}
|
||||
if (inst.op == IR::Opcode::Export) {
|
||||
std::array<IR::Value, 4> components {IR::Value(0u), IR::Value(0u), IR::Value(0u),
|
||||
IR::Value(0u)};
|
||||
for (uint32_t index = 0; index < std::min(inst.src_count, 4u); index++) {
|
||||
components[index] = ReadRawU32(PlainOperand(inst.src[index]));
|
||||
}
|
||||
const auto data = ir.Emit(IR::ValueOpcode::CompositeConstructU32x4,
|
||||
{components[0], components[1], components[2], components[3]});
|
||||
ir.Emit(IR::ValueOpcode::SetAttribute, {data, ir.GetExec()}, AddExportInfo(inst));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
bool Translator::TranslateIntegerCompare(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode {};
|
||||
IR::Type type = IR::Type::U32;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::CompareFalse:
|
||||
WriteCompareResult(inst.dst, IR::U1(IR::Value(false)));
|
||||
return true;
|
||||
case IR::Opcode::CompareTrue:
|
||||
WriteCompareResult(inst.dst, IR::U1(IR::Value(true)));
|
||||
return true;
|
||||
case IR::Opcode::CompareEqU32:
|
||||
case IR::Opcode::CompareMaskEqU32:
|
||||
case IR::Opcode::CompareEqI32:
|
||||
case IR::Opcode::CompareMaskEqI32: opcode = IR::ValueOpcode::IEqual32; break;
|
||||
case IR::Opcode::CompareNeU32:
|
||||
case IR::Opcode::CompareMaskNeU32:
|
||||
case IR::Opcode::CompareNeI32:
|
||||
case IR::Opcode::CompareMaskNeI32: opcode = IR::ValueOpcode::INotEqual32; break;
|
||||
case IR::Opcode::CompareGtU32:
|
||||
case IR::Opcode::CompareMaskGtU32: opcode = IR::ValueOpcode::UGreaterThan32; break;
|
||||
case IR::Opcode::CompareGeU32:
|
||||
case IR::Opcode::CompareMaskGeU32: opcode = IR::ValueOpcode::UGreaterThanEqual32; break;
|
||||
case IR::Opcode::CompareLtU32:
|
||||
case IR::Opcode::CompareMaskLtU32: opcode = IR::ValueOpcode::ULessThan32; break;
|
||||
case IR::Opcode::CompareLeU32:
|
||||
case IR::Opcode::CompareMaskLeU32: opcode = IR::ValueOpcode::ULessThanEqual32; break;
|
||||
case IR::Opcode::CompareGtI32:
|
||||
case IR::Opcode::CompareMaskGtI32: opcode = IR::ValueOpcode::SGreaterThan32; break;
|
||||
case IR::Opcode::CompareGeI32:
|
||||
case IR::Opcode::CompareMaskGeI32: opcode = IR::ValueOpcode::SGreaterThanEqual32; break;
|
||||
case IR::Opcode::CompareLtI32:
|
||||
case IR::Opcode::CompareMaskLtI32: opcode = IR::ValueOpcode::SLessThan32; break;
|
||||
case IR::Opcode::CompareLeI32:
|
||||
case IR::Opcode::CompareMaskLeI32: opcode = IR::ValueOpcode::SLessThanEqual32; break;
|
||||
case IR::Opcode::CompareEqU64:
|
||||
opcode = IR::ValueOpcode::IEqual64;
|
||||
type = IR::Type::U64;
|
||||
break;
|
||||
case IR::Opcode::CompareNeU64:
|
||||
opcode = IR::ValueOpcode::INotEqual64;
|
||||
type = IR::Type::U64;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
const auto lhs = ReadOperand(inst.src[0], type);
|
||||
const auto rhs = ReadOperand(inst.src[1], type);
|
||||
WriteCompareResult(inst.dst, IR::U1(ir.Emit(opcode, {lhs, rhs})));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateInteger16Compare(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode {};
|
||||
bool signed_value = false;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::CompareEqU16:
|
||||
case IR::Opcode::CompareEqI16: opcode = IR::ValueOpcode::IEqual32; break;
|
||||
case IR::Opcode::CompareNeU16:
|
||||
case IR::Opcode::CompareNeI16: opcode = IR::ValueOpcode::INotEqual32; break;
|
||||
case IR::Opcode::CompareGtU16: opcode = IR::ValueOpcode::UGreaterThan32; break;
|
||||
case IR::Opcode::CompareGeU16: opcode = IR::ValueOpcode::UGreaterThanEqual32; break;
|
||||
case IR::Opcode::CompareLtU16: opcode = IR::ValueOpcode::ULessThan32; break;
|
||||
case IR::Opcode::CompareLeU16: opcode = IR::ValueOpcode::ULessThanEqual32; break;
|
||||
case IR::Opcode::CompareGtI16:
|
||||
opcode = IR::ValueOpcode::SGreaterThan32;
|
||||
signed_value = true;
|
||||
break;
|
||||
case IR::Opcode::CompareGeI16:
|
||||
opcode = IR::ValueOpcode::SGreaterThanEqual32;
|
||||
signed_value = true;
|
||||
break;
|
||||
case IR::Opcode::CompareLtI16:
|
||||
opcode = IR::ValueOpcode::SLessThan32;
|
||||
signed_value = true;
|
||||
break;
|
||||
case IR::Opcode::CompareLeI16:
|
||||
opcode = IR::ValueOpcode::SLessThanEqual32;
|
||||
signed_value = true;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
if (inst.op == IR::Opcode::CompareEqI16 || inst.op == IR::Opcode::CompareNeI16) {
|
||||
signed_value = true;
|
||||
}
|
||||
WriteCompareResult(inst.dst,
|
||||
IR::U1(ir.Emit(opcode, {ReadU16AsU32(inst.src[0], signed_value),
|
||||
ReadU16AsU32(inst.src[1], signed_value)})));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateFloatCompare(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode {};
|
||||
bool half = false;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::CompareEqF32:
|
||||
case IR::Opcode::CompareMaskEqF32: opcode = IR::ValueOpcode::FPOrdEqual32; break;
|
||||
case IR::Opcode::CompareNeF32:
|
||||
case IR::Opcode::CompareMaskNeF32: opcode = IR::ValueOpcode::FPOrdNotEqual32; break;
|
||||
case IR::Opcode::CompareGtF32:
|
||||
case IR::Opcode::CompareMaskGtF32: opcode = IR::ValueOpcode::FPOrdGreaterThan32; break;
|
||||
case IR::Opcode::CompareGeF32:
|
||||
case IR::Opcode::CompareMaskGeF32: opcode = IR::ValueOpcode::FPOrdGreaterThanEqual32; break;
|
||||
case IR::Opcode::CompareLtF32:
|
||||
case IR::Opcode::CompareMaskLtF32: opcode = IR::ValueOpcode::FPOrdLessThan32; break;
|
||||
case IR::Opcode::CompareLeF32:
|
||||
case IR::Opcode::CompareMaskLeF32: opcode = IR::ValueOpcode::FPOrdLessThanEqual32; break;
|
||||
case IR::Opcode::CompareUnordEqF32:
|
||||
case IR::Opcode::CompareMaskUnordEqF32: opcode = IR::ValueOpcode::FPUnordEqual32; break;
|
||||
case IR::Opcode::CompareUnordNeF32:
|
||||
case IR::Opcode::CompareMaskUnordNeF32: opcode = IR::ValueOpcode::FPUnordNotEqual32; break;
|
||||
case IR::Opcode::CompareUnordGtF32:
|
||||
case IR::Opcode::CompareMaskUnordGtF32:
|
||||
opcode = IR::ValueOpcode::FPUnordGreaterThan32;
|
||||
break;
|
||||
case IR::Opcode::CompareUnordGeF32:
|
||||
case IR::Opcode::CompareMaskUnordGeF32:
|
||||
opcode = IR::ValueOpcode::FPUnordGreaterThanEqual32;
|
||||
break;
|
||||
case IR::Opcode::CompareUnordLtF32:
|
||||
case IR::Opcode::CompareMaskUnordLtF32: opcode = IR::ValueOpcode::FPUnordLessThan32; break;
|
||||
case IR::Opcode::CompareUnordLeF32:
|
||||
case IR::Opcode::CompareMaskUnordLeF32:
|
||||
opcode = IR::ValueOpcode::FPUnordLessThanEqual32;
|
||||
break;
|
||||
case IR::Opcode::CompareEqF16:
|
||||
case IR::Opcode::CompareMaskEqF16:
|
||||
opcode = IR::ValueOpcode::FPOrdEqual32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareNeF16:
|
||||
case IR::Opcode::CompareMaskNeF16:
|
||||
opcode = IR::ValueOpcode::FPOrdNotEqual32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareGtF16:
|
||||
case IR::Opcode::CompareMaskGtF16:
|
||||
opcode = IR::ValueOpcode::FPOrdGreaterThan32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareGeF16:
|
||||
case IR::Opcode::CompareMaskGeF16:
|
||||
opcode = IR::ValueOpcode::FPOrdGreaterThanEqual32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareLtF16:
|
||||
case IR::Opcode::CompareMaskLtF16:
|
||||
opcode = IR::ValueOpcode::FPOrdLessThan32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareLeF16:
|
||||
case IR::Opcode::CompareMaskLeF16:
|
||||
opcode = IR::ValueOpcode::FPOrdLessThanEqual32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareUnordNeF16:
|
||||
case IR::Opcode::CompareMaskUnordNeF16:
|
||||
opcode = IR::ValueOpcode::FPUnordNotEqual32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareMaskUnordGeF16:
|
||||
opcode = IR::ValueOpcode::FPUnordGreaterThanEqual32;
|
||||
half = true;
|
||||
break;
|
||||
case IR::Opcode::CompareOrderedF32:
|
||||
case IR::Opcode::CompareUnorderedF32: {
|
||||
const auto lhs = IR::F32(ReadOperand(inst.src[0], IR::Type::F32));
|
||||
const auto rhs = IR::F32(ReadOperand(inst.src[1], IR::Type::F32));
|
||||
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {lhs})),
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {rhs})));
|
||||
const auto result =
|
||||
inst.op == IR::Opcode::CompareUnorderedF32 ? unordered : ir.LogicalNot(unordered);
|
||||
WriteCompareResult(inst.dst, result);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::CompareClassF32: {
|
||||
const auto value = ReadOperand(inst.src[0], IR::Type::F32);
|
||||
const auto mask = ReadOperand(inst.src[1], IR::Type::U32);
|
||||
WriteCompareResult(inst.dst,
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::FPCmpClass32, {value, mask})));
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
const auto lhs =
|
||||
half ? IR::Value(ReadF16AsF32(inst.src[0])) : ReadOperand(inst.src[0], IR::Type::F32);
|
||||
const auto rhs =
|
||||
half ? IR::Value(ReadF16AsF32(inst.src[1])) : ReadOperand(inst.src[1], IR::Type::F32);
|
||||
WriteCompareResult(inst.dst, IR::U1(ir.Emit(opcode, {lhs, rhs})));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,260 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
bool Translator::TranslateStateOperation(const IR::Instruction& inst) {
|
||||
const auto zero = IR::U32(IR::Value(0u));
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::SaveexecB32:
|
||||
case IR::Opcode::SaveexecB64: {
|
||||
const auto old_low = ir.GetExecLo();
|
||||
const auto old_high = ir.GetExecHi();
|
||||
const auto src_low = ReadRawU32(inst.src[0]);
|
||||
const auto src_high = inst.op == IR::Opcode::SaveexecB64
|
||||
? ReadRawU32(OffsetOperand(inst.src[0], 1))
|
||||
: IR::U32(IR::Value(0u));
|
||||
const auto apply = [&](IR::U32 old_value, IR::U32 src_value) {
|
||||
switch (inst.saveexec_mode) {
|
||||
case IR::SaveexecMode::And: return ir.BitwiseAnd(old_value, src_value);
|
||||
case IR::SaveexecMode::Andn1:
|
||||
return ir.BitwiseAnd(old_value, ir.BitwiseNot(src_value));
|
||||
case IR::SaveexecMode::Orn2:
|
||||
return ir.BitwiseOr(ir.BitwiseNot(old_value), src_value);
|
||||
}
|
||||
return old_value;
|
||||
};
|
||||
const auto result_low = apply(old_low, src_low);
|
||||
const auto result_high =
|
||||
inst.op == IR::Opcode::SaveexecB64 ? apply(old_high, src_high) : old_high;
|
||||
if (inst.op == IR::Opcode::SaveexecB64) {
|
||||
WriteOperand(inst.dst, ir.PackUint2x32(old_low, old_high));
|
||||
} else {
|
||||
WriteOperand(inst.dst, old_low);
|
||||
}
|
||||
ir.SetExecLo(result_low);
|
||||
ir.SetExecHi(result_high);
|
||||
ir.SetExec(ThreadBit(result_low, result_high));
|
||||
const auto nonzero = inst.op == IR::Opcode::SaveexecB64
|
||||
? ir.BitwiseOr(result_low, result_high)
|
||||
: result_low;
|
||||
ir.SetScc(ir.INotEqual(nonzero, zero));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ScalarAddCarryU32:
|
||||
case IR::Opcode::IAddCarryU32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
const auto carry_in = ConditionBit(inst.src[2]);
|
||||
const auto add0 = ir.Emit(IR::ValueOpcode::IAddCarry32, {lhs, rhs});
|
||||
const auto partial = ir.CompositeExtract(add0, 0);
|
||||
const auto carry0 = ir.CompositeExtract(add0, 1);
|
||||
const auto add1 = ir.Emit(IR::ValueOpcode::IAddCarry32, {partial, carry_in});
|
||||
const auto result = ir.CompositeExtract(add1, 0);
|
||||
const auto carry1 = ir.CompositeExtract(add1, 1);
|
||||
const auto carry = ir.INotEqual(ir.BitwiseOr(carry0, carry1), IR::U32(IR::Value(0u)));
|
||||
WriteOperand(inst.dst, result);
|
||||
if (inst.op == IR::Opcode::ScalarAddCarryU32) {
|
||||
ir.SetScc(carry);
|
||||
} else {
|
||||
WriteMask(inst.dst2, carry);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ScalarSubBorrowU32:
|
||||
case IR::Opcode::ISubBorrowU32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
const auto result = ir.ISub(lhs, rhs);
|
||||
const auto borrow = ir.UGreaterThan(rhs, lhs);
|
||||
WriteOperand(inst.dst, result);
|
||||
if (inst.op == IR::Opcode::ScalarSubBorrowU32) {
|
||||
ir.SetScc(borrow);
|
||||
} else {
|
||||
WriteMask(inst.dst2, borrow);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ScalarSubBorrowCarryU32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
const auto borrow_in = ConditionBit(inst.src[2]);
|
||||
const auto partial = ir.ISub(lhs, rhs);
|
||||
const auto result = ir.ISub(partial, borrow_in);
|
||||
const auto borrow0 = ir.UGreaterThan(rhs, lhs);
|
||||
const auto borrow1 = ir.UGreaterThan(borrow_in, partial);
|
||||
WriteOperand(inst.dst, result);
|
||||
ir.SetScc(ir.LogicalOr(borrow0, borrow1));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ScalarSignedAddOverflowI32:
|
||||
case IR::Opcode::ScalarSignedSubOverflowI32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
const bool subtract = inst.op == IR::Opcode::ScalarSignedSubOverflowI32;
|
||||
const auto result = subtract ? ir.ISub(lhs, rhs) : ir.IAdd(lhs, rhs);
|
||||
const auto shift = IR::U32(IR::Value(31u));
|
||||
const auto lhs_sign = ir.ShiftRightLogical(lhs, shift);
|
||||
const auto rhs_sign = ir.ShiftRightLogical(rhs, shift);
|
||||
const auto out_sign = ir.ShiftRightLogical(result, shift);
|
||||
const auto inputs =
|
||||
subtract ? ir.INotEqual(lhs_sign, rhs_sign) : ir.IEqual(lhs_sign, rhs_sign);
|
||||
const auto changed = ir.INotEqual(lhs_sign, out_sign);
|
||||
WriteOperand(inst.dst, result);
|
||||
ir.SetScc(ir.LogicalAnd(inputs, changed));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ScalarShiftLeftAddCarryU32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto shift = ReadU32(inst.src[1]);
|
||||
const auto rhs = ReadU32(inst.src[2]);
|
||||
const auto shifted = ir.ShiftLeftLogical(lhs, shift);
|
||||
const auto result = ir.IAdd(shifted, rhs);
|
||||
const auto add_carry = ir.ULessThan(result, shifted);
|
||||
const auto inverse_shift = ir.ISub(IR::U32(IR::Value(32u)), shift);
|
||||
const auto shifted_out = ir.ShiftRightLogical(lhs, inverse_shift);
|
||||
const auto shift_carry = ir.INotEqual(shifted_out, zero);
|
||||
WriteOperand(inst.dst, result);
|
||||
ir.SetScc(ir.LogicalOr(add_carry, shift_carry));
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::TranslateControlOperation(const IR::Instruction& inst) {
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::ControlNop: ir.Emit(IR::ValueOpcode::ControlNop); return true;
|
||||
case IR::Opcode::Waitcnt: ir.Emit(IR::ValueOpcode::Waitcnt); return true;
|
||||
case IR::Opcode::Barrier: ir.Emit(IR::ValueOpcode::Barrier); return true;
|
||||
case IR::Opcode::Sendmsg: ir.Emit(IR::ValueOpcode::Sendmsg); return true;
|
||||
case IR::Opcode::TtraceData: ir.Emit(IR::ValueOpcode::TtraceData); return true;
|
||||
case IR::Opcode::InstPrefetch: ir.Emit(IR::ValueOpcode::InstPrefetch); return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::TranslateMove(const IR::Instruction& inst) {
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::MoveU32:
|
||||
if (inst.src[0].kind == IR::OperandKind::Register &&
|
||||
(inst.src[0].reg.file == IR::RegisterFile::Exec ||
|
||||
inst.src[0].reg.file == IR::RegisterFile::Vcc) &&
|
||||
inst.dst.kind == IR::OperandKind::Register &&
|
||||
(inst.dst.reg.file == IR::RegisterFile::Exec ||
|
||||
inst.dst.reg.file == IR::RegisterFile::Vcc)) {
|
||||
WriteMask(inst.dst, ReadMask(inst.src[0]));
|
||||
} else {
|
||||
WriteOperand(inst.dst, ReadOperand(inst.src[0], IR::Type::U32));
|
||||
}
|
||||
return true;
|
||||
case IR::Opcode::MoveF32Bits:
|
||||
WriteOperand(inst.dst, ReadOperand(inst.src[0], IR::Type::F32));
|
||||
return true;
|
||||
case IR::Opcode::MoveU64: {
|
||||
const bool scalar_pair = inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.src[0].kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file == IR::RegisterFile::Scalar &&
|
||||
inst.src[0].reg.file == IR::RegisterFile::Scalar;
|
||||
WriteOperand(inst.dst, ReadOperand(inst.src[0], IR::Type::U64));
|
||||
if (scalar_pair) {
|
||||
ir.SetThreadBitScalarReg(
|
||||
static_cast<IR::ScalarReg>(inst.dst.reg.index),
|
||||
ir.GetThreadBitScalarReg(static_cast<IR::ScalarReg>(inst.src[0].reg.index)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::WqmB64: {
|
||||
if (!current_per_invocation_masks && inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file == IR::RegisterFile::Scalar) {
|
||||
const auto result =
|
||||
ExpandWholeQuadMask(IR::U64(ReadOperand(inst.src[0], IR::Type::U64)));
|
||||
WriteOperand(inst.dst, result);
|
||||
ir.SetScc(IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
|
||||
return true;
|
||||
}
|
||||
const auto result = IR::U1(ir.Emit(IR::ValueOpcode::WqmMask, {ReadMask(inst.src[0])}));
|
||||
WriteMask64(inst.dst, result);
|
||||
ir.SetScc(result);
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::TranslateLaneOperation(const IR::Instruction& inst) {
|
||||
const auto lane_mask = IR::U32(IR::Value(current_wave_size == 32u ? 31u : 63u));
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::MoveRelSourceU32: {
|
||||
EXIT_IF(inst.src[0].kind != IR::OperandKind::Register ||
|
||||
inst.src[0].reg.file != IR::RegisterFile::Vector);
|
||||
const auto base = inst.src[0].reg.index;
|
||||
const auto m0 = ir.BitwiseAnd(ReadU32(inst.src[1]), IR::U32(IR::Value(0xffu)));
|
||||
auto selected = ir.GetVectorReg(static_cast<IR::VectorReg>(base));
|
||||
for (uint32_t index = base + 1u; index < current_vector_limit; index++) {
|
||||
const auto match = ir.IEqual(m0, IR::U32(IR::Value(index - base)));
|
||||
selected =
|
||||
ir.Select(match, ir.GetVectorReg(static_cast<IR::VectorReg>(index)), selected);
|
||||
}
|
||||
WriteOperand(inst.dst, selected);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::MoveRelDestU32: {
|
||||
EXIT_IF(inst.dst.kind != IR::OperandKind::Register ||
|
||||
inst.dst.reg.file != IR::RegisterFile::Vector);
|
||||
const auto base = inst.dst.reg.index;
|
||||
const auto value = ReadU32(inst.src[0]);
|
||||
const auto m0 = ir.BitwiseAnd(ReadU32(inst.src[1]), IR::U32(IR::Value(0xffu)));
|
||||
for (uint32_t index = base; index < current_vector_limit; index++) {
|
||||
const auto reg = static_cast<IR::VectorReg>(index);
|
||||
const auto match = ir.IEqual(m0, IR::U32(IR::Value(index - base)));
|
||||
const auto write = ir.LogicalAnd(ir.GetExec(), match);
|
||||
ir.SetVectorReg(reg, ir.Select(write, value, ir.GetVectorReg(reg)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ReadFirstLaneU32: {
|
||||
const auto result =
|
||||
ir.Emit(IR::ValueOpcode::ReadFirstLane, {ReadU32(inst.src[0]), ir.GetExec()});
|
||||
WriteOperand(inst.dst, result);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ReadLaneU32: {
|
||||
const auto lane = ir.BitwiseAnd(ReadU32(inst.src[1]), lane_mask);
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(IR::ValueOpcode::ReadLane, {ReadU32(inst.src[0]), lane}));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::WriteLaneU32: {
|
||||
EXIT_IF(inst.dst.kind != IR::OperandKind::Register ||
|
||||
inst.dst.reg.file != IR::RegisterFile::Vector);
|
||||
const auto reg = static_cast<IR::VectorReg>(inst.dst.reg.index);
|
||||
const auto lane = ir.BitwiseAnd(ReadU32(inst.src[1]), lane_mask);
|
||||
const auto result = IR::U32(ir.Emit(
|
||||
IR::ValueOpcode::WriteLane, {ir.GetVectorReg(reg), ReadU32(inst.src[0]), lane}));
|
||||
// V_WRITELANE_B32 ignores EXEC; bypass the ordinary VGPR destination path.
|
||||
ir.SetVectorReg(reg, result);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::Permlane16B32:
|
||||
case IR::Opcode::Permlanex16B32: {
|
||||
const IR::PermlaneFlags flags {
|
||||
.x16 = inst.op == IR::Opcode::Permlanex16B32,
|
||||
.fetch_inactive = inst.dst.op_sel,
|
||||
.bound_control = inst.dst.op_sel_hi,
|
||||
};
|
||||
const auto result = ir.Emit(
|
||||
IR::ValueOpcode::Permlane16U32,
|
||||
{ReadU32(inst.src[0]), ReadU32(inst.src[1]), ReadU32(inst.src[2]), ir.GetExec()},
|
||||
flags);
|
||||
auto dst = inst.dst;
|
||||
dst.op_sel = false;
|
||||
dst.op_sel_hi = false;
|
||||
WriteOperand(dst, result);
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
IR::F32 Translator::SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value) {
|
||||
return IR::F32(ir.Emit(IR::ValueOpcode::SelectF32, {condition, true_value, false_value}));
|
||||
}
|
||||
|
||||
IR::U32 Translator::ConvertF32ToU32Saturated(IR::F32 value, float upper_bound, float safe_upper,
|
||||
uint32_t upper_result) {
|
||||
const auto zero = IR::F32(IR::Value::F32(0.0f));
|
||||
const auto nan = IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {value}));
|
||||
const auto low = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThanEqual32, {value, zero}));
|
||||
const auto high = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::FPOrdGreaterThanEqual32, {value, IR::Value::F32(upper_bound)}));
|
||||
const auto truncated = IR::F32(ir.Emit(IR::ValueOpcode::FPTrunc32, {value}));
|
||||
const auto safe_low = SelectF32(ir.LogicalOr(nan, low), zero, truncated);
|
||||
const auto safe = SelectF32(high, IR::F32(IR::Value::F32(safe_upper)), safe_low);
|
||||
const auto converted = IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32F32, {safe}));
|
||||
return ir.Select(high, IR::U32(IR::Value(upper_result)), converted);
|
||||
}
|
||||
|
||||
IR::U32 Translator::ConvertF32ToI32Saturated(IR::F32 value, float lower_bound, float upper_bound,
|
||||
float safe_upper, uint32_t lower_result,
|
||||
uint32_t upper_result) {
|
||||
const auto nan = IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {value}));
|
||||
const auto low = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::FPOrdLessThanEqual32, {value, IR::Value::F32(lower_bound)}));
|
||||
const auto high = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::FPOrdGreaterThanEqual32, {value, IR::Value::F32(upper_bound)}));
|
||||
const auto truncated = IR::F32(ir.Emit(IR::ValueOpcode::FPTrunc32, {value}));
|
||||
const auto safe_low = SelectF32(low, IR::F32(IR::Value::F32(lower_bound)), truncated);
|
||||
const auto safe_high = SelectF32(high, IR::F32(IR::Value::F32(safe_upper)), safe_low);
|
||||
const auto safe = SelectF32(nan, IR::F32(IR::Value::F32(0.0f)), safe_high);
|
||||
const auto converted = IR::U32(ir.Emit(IR::ValueOpcode::ConvertS32F32, {safe}));
|
||||
const auto clamped_high = ir.Select(high, IR::U32(IR::Value(upper_result)), converted);
|
||||
const auto clamped = ir.Select(low, IR::U32(IR::Value(lower_result)), clamped_high);
|
||||
return ir.Select(nan, IR::U32(IR::Value(0u)), clamped);
|
||||
}
|
||||
|
||||
bool Translator::TranslateConversion(const IR::Instruction& inst) {
|
||||
const auto f32 = [&](uint32_t index) {
|
||||
return IR::F32(ReadOperand(inst.src[index], IR::Type::F32));
|
||||
};
|
||||
const auto u32 = [&](uint32_t index) { return ReadU32(inst.src[index]); };
|
||||
const auto convert_u32 = [&](IR::F32 value) {
|
||||
return ConvertF32ToU32Saturated(value, 4294967296.0f, 4294967040.0f, 0xffffffffu);
|
||||
};
|
||||
const auto convert_i32 = [&](IR::F32 value) {
|
||||
return ConvertF32ToI32Saturated(value, -2147483648.0f, 2147483648.0f, 2147483520.0f,
|
||||
0x80000000u, 0x7fffffffu);
|
||||
};
|
||||
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::ConvertByteU32ToF32: {
|
||||
const auto index =
|
||||
inst.src[1].kind == IR::OperandKind::ImmediateU32 ? inst.src[1].imm & 3u : 0u;
|
||||
const auto byte =
|
||||
ir.BitwiseAnd(ir.ShiftRightLogical(u32(0), IR::U32(IR::Value(index * 8u))),
|
||||
IR::U32(IR::Value(0xffu)));
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::ConvertF32U32, {byte}));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ConvertU32ToF32:
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::ConvertF32U32, {u32(0)}));
|
||||
return true;
|
||||
case IR::Opcode::ConvertI32ToF32:
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::ConvertF32S32, {u32(0)}));
|
||||
return true;
|
||||
case IR::Opcode::ConvertF32ToU32: WriteOperand(inst.dst, convert_u32(f32(0))); return true;
|
||||
case IR::Opcode::ConvertF32ToI32: WriteOperand(inst.dst, convert_i32(f32(0))); return true;
|
||||
case IR::Opcode::ConvertF32ToF16: WriteF16(inst.dst, f32(0)); return true;
|
||||
case IR::Opcode::ConvertF16ToF32:
|
||||
WriteOperand(inst.dst, ReadF16AsF32(inst.src[0]));
|
||||
return true;
|
||||
case IR::Opcode::ConvertU16ToF16: {
|
||||
const auto value = IR::F32(
|
||||
ir.Emit(IR::ValueOpcode::ConvertF32U32, {ReadU16AsU32(inst.src[0], false)}));
|
||||
WriteF16(inst.dst, value);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ConvertI16ToF16: {
|
||||
const auto value =
|
||||
IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32S32, {ReadU16AsU32(inst.src[0], true)}));
|
||||
WriteF16(inst.dst, value);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ConvertF16ToU16: {
|
||||
const auto converted =
|
||||
ConvertF32ToU32Saturated(ReadF16AsF32(inst.src[0]), 65536.0f, 65535.0f, 0xffffu);
|
||||
WriteU16(inst.dst, converted);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ConvertF16ToI16: {
|
||||
const auto converted = ConvertF32ToI32Saturated(
|
||||
ReadF16AsF32(inst.src[0]), -32768.0f, 32768.0f, 32767.0f, 0xffff8000u, 0x7fffu);
|
||||
WriteU16(inst.dst, ir.BitwiseAnd(converted, IR::U32(IR::Value(0xffffu))));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ConvertRoundPlusInfF32ToI32: {
|
||||
const auto biased =
|
||||
IR::F32(ir.Emit(IR::ValueOpcode::FPAdd32, {f32(0), IR::Value::F32(0.5f)}));
|
||||
const auto rounded = IR::F32(ir.Emit(IR::ValueOpcode::FPFloor32, {biased}));
|
||||
WriteOperand(inst.dst, convert_i32(rounded));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ConvertFloorF32ToI32: {
|
||||
const auto rounded = IR::F32(ir.Emit(IR::ValueOpcode::FPFloor32, {f32(0)}));
|
||||
WriteOperand(inst.dst, convert_i32(rounded));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ConvertI4ToOffsetF32: {
|
||||
const auto nibble = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {u32(0), IR::Value(0u), IR::Value(4u)}));
|
||||
const auto value = IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32S32, {nibble}));
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(IR::ValueOpcode::FPMul32, {value, IR::Value::F32(1.0f / 16.0f)}));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::PackF32ToF16Rtz:
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::PackFloat2x16Rtz, {f32(0), f32(1)}));
|
||||
return true;
|
||||
case IR::Opcode::PackSnorm2x16F32:
|
||||
case IR::Opcode::PackUnorm2x16F32: {
|
||||
const auto pair = ir.Emit(IR::ValueOpcode::CompositeConstructF32x2, {f32(0), f32(1)});
|
||||
const auto opcode = inst.op == IR::Opcode::PackSnorm2x16F32
|
||||
? IR::ValueOpcode::PackSnorm2x16
|
||||
: IR::ValueOpcode::PackUnorm2x16;
|
||||
WriteOperand(inst.dst, ir.Emit(opcode, {pair}));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::PackU8F32: {
|
||||
const auto byte = ConvertF32ToU32Saturated(f32(0), 255.0f, 255.0f, 255u);
|
||||
const auto index = ir.BitwiseAnd(u32(1), IR::U32(IR::Value(3u)));
|
||||
const auto shift = ir.ShiftLeftLogical(index, IR::U32(IR::Value(3u)));
|
||||
const auto mask = ir.ShiftLeftLogical(IR::U32(IR::Value(0xffu)), shift);
|
||||
const auto base = ir.BitwiseAnd(u32(2), ir.BitwiseNot(mask));
|
||||
WriteOperand(inst.dst, ir.BitwiseOr(base, ir.ShiftLeftLogical(byte, shift)));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::PackB32F16: {
|
||||
const auto low = ReadF16LaneBits(inst.src[0], false);
|
||||
const auto high =
|
||||
ir.ShiftLeftLogical(ReadF16LaneBits(inst.src[1], false), IR::U32(IR::Value(16u)));
|
||||
WriteOperand(inst.dst, ir.BitwiseOr(low, high));
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
IR::U32 Translator::PackU16Lanes(IR::U32 low, IR::U32 high) {
|
||||
const auto mask = IR::U32(IR::Value(0xffffu));
|
||||
return ir.BitwiseOr(ir.BitwiseAnd(low, mask),
|
||||
ir.ShiftLeftLogical(ir.BitwiseAnd(high, mask), IR::U32(IR::Value(16u))));
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
bool Translator::TranslateU64MaskOperation(const IR::Instruction& inst) {
|
||||
if (current_per_invocation_masks) {
|
||||
return TranslatePerInvocationU64Mask(inst);
|
||||
}
|
||||
return inst.op == IR::Opcode::BitwiseAndU64 || inst.op == IR::Opcode::BitwiseOrU64
|
||||
? TranslateSimpleInteger(inst)
|
||||
: TranslateComposedInteger(inst);
|
||||
}
|
||||
|
||||
bool Translator::TranslateInstruction(const IR::Instruction& inst,
|
||||
const BufferAddressValues* address_snapshot) {
|
||||
switch (IR::GetOpcodeInfo(inst.op).lowering_class) {
|
||||
case IR::LoweringClass::Control: return TranslateControlOperation(inst);
|
||||
case IR::LoweringClass::Move: return TranslateMove(inst);
|
||||
case IR::LoweringClass::Lane: return TranslateLaneOperation(inst);
|
||||
case IR::LoweringClass::State: return TranslateStateOperation(inst);
|
||||
case IR::LoweringClass::Memory: return TranslateMemoryOperation(inst, address_snapshot);
|
||||
case IR::LoweringClass::Attribute: return TranslateAttributeOperation(inst);
|
||||
case IR::LoweringClass::IntegerCompare: return TranslateIntegerCompare(inst);
|
||||
case IR::LoweringClass::Integer16Compare: return TranslateInteger16Compare(inst);
|
||||
case IR::LoweringClass::FloatCompare: return TranslateFloatCompare(inst);
|
||||
case IR::LoweringClass::Conversion: return TranslateConversion(inst);
|
||||
case IR::LoweringClass::Integer16: return TranslateInteger16Operation(inst);
|
||||
case IR::LoweringClass::PackedInteger16: return TranslatePackedInteger16(inst);
|
||||
case IR::LoweringClass::PackedFloat16: return TranslatePackedFloat16(inst);
|
||||
case IR::LoweringClass::Float16: return TranslateFloat16Operation(inst);
|
||||
case IR::LoweringClass::Float: return TranslateFloatOperation(inst);
|
||||
case IR::LoweringClass::U64Mask: return TranslateU64MaskOperation(inst);
|
||||
case IR::LoweringClass::SimpleInteger: return TranslateSimpleInteger(inst);
|
||||
case IR::LoweringClass::ComposedInteger: return TranslateComposedInteger(inst);
|
||||
case IR::LoweringClass::ExtendedInteger: return TranslateExtendedInteger(inst);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
bool Translator::TranslatePackedFloat16(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::PackedAddF16: opcode = IR::ValueOpcode::FPAdd32; break;
|
||||
case IR::Opcode::PackedMulF16: opcode = IR::ValueOpcode::FPMul32; break;
|
||||
case IR::Opcode::PackedMinF16: opcode = IR::ValueOpcode::FPMin32; break;
|
||||
case IR::Opcode::PackedMaxF16: opcode = IR::ValueOpcode::FPMax32; break;
|
||||
case IR::Opcode::PackedFmaF16: opcode = IR::ValueOpcode::FPFma32; break;
|
||||
default: return false;
|
||||
}
|
||||
const auto translate_lane = [&](bool high) {
|
||||
const auto lhs = ReadF16LaneAsF32(inst.src[0], high);
|
||||
const auto rhs = ReadF16LaneAsF32(inst.src[1], high);
|
||||
IR::F32 result;
|
||||
if (inst.src_count == 3u) {
|
||||
result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.src[2], high)}));
|
||||
} else {
|
||||
result = IR::F32(ir.Emit(opcode, {lhs, rhs}));
|
||||
}
|
||||
return ApplyF32ResultModifiers(inst.dst, result);
|
||||
};
|
||||
auto raw = inst.dst;
|
||||
raw.omod = 0u;
|
||||
raw.clamp = false;
|
||||
auto result = PackHalf2x16(translate_lane(false), translate_lane(true));
|
||||
if (inst.op == IR::Opcode::PackedMinF16 || inst.op == IR::Opcode::PackedMaxF16) {
|
||||
const auto quiet_snan = [&](const IR::Operand& operand, bool high) {
|
||||
const auto bits = ReadU16LaneAsU32(operand, high, false);
|
||||
const auto exponent = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x7c00u)));
|
||||
const auto payload = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x01ffu)));
|
||||
const auto snan = ir.LogicalAnd(ir.IEqual(exponent, IR::U32(IR::Value(0x7c00u))),
|
||||
ir.INotEqual(payload, IR::U32(IR::Value(0u))));
|
||||
return std::pair {snan, ir.BitwiseOr(bits, IR::U32(IR::Value(0x0200u)))};
|
||||
};
|
||||
const auto override_lane = [&](bool high) {
|
||||
const auto [lhs_snan, lhs_quiet] = quiet_snan(inst.src[0], high);
|
||||
const auto [rhs_snan, rhs_quiet] = quiet_snan(inst.src[1], high);
|
||||
const auto normal = high ? ir.ShiftRightLogical(result, IR::U32(IR::Value(16u)))
|
||||
: ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu)));
|
||||
return ir.Select(lhs_snan, lhs_quiet, ir.Select(rhs_snan, rhs_quiet, normal));
|
||||
};
|
||||
result = PackU16Lanes(override_lane(false), override_lane(true));
|
||||
}
|
||||
WriteOperand(raw, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateFloat16Operation(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::AddF16: opcode = IR::ValueOpcode::FPAdd32; break;
|
||||
case IR::Opcode::SubF16: opcode = IR::ValueOpcode::FPSub32; break;
|
||||
case IR::Opcode::MulF16: opcode = IR::ValueOpcode::FPMul32; break;
|
||||
case IR::Opcode::MinF16: opcode = IR::ValueOpcode::FPMin32; break;
|
||||
case IR::Opcode::MaxF16: opcode = IR::ValueOpcode::FPMax32; break;
|
||||
case IR::Opcode::FmaF16:
|
||||
case IR::Opcode::MadMixF16: opcode = IR::ValueOpcode::FPFma32; break;
|
||||
case IR::Opcode::RcpF16: opcode = IR::ValueOpcode::FPRecip32; break;
|
||||
case IR::Opcode::SqrtF16: opcode = IR::ValueOpcode::FPSqrt; break;
|
||||
case IR::Opcode::InverseSqrtF16: opcode = IR::ValueOpcode::FPRecipSqrt32; break;
|
||||
case IR::Opcode::Log2F16: opcode = IR::ValueOpcode::FPLog2; break;
|
||||
case IR::Opcode::Exp2F16: opcode = IR::ValueOpcode::FPExp2; break;
|
||||
case IR::Opcode::FloorF16: opcode = IR::ValueOpcode::FPFloor32; break;
|
||||
case IR::Opcode::CeilF16: opcode = IR::ValueOpcode::FPCeil32; break;
|
||||
case IR::Opcode::TruncF16: opcode = IR::ValueOpcode::FPTrunc32; break;
|
||||
case IR::Opcode::RoundEvenF16: opcode = IR::ValueOpcode::FPRoundEven32; break;
|
||||
case IR::Opcode::Min3F16: opcode = IR::ValueOpcode::FPMinTri32; break;
|
||||
case IR::Opcode::Max3F16: opcode = IR::ValueOpcode::FPMaxTri32; break;
|
||||
case IR::Opcode::Med3F16: opcode = IR::ValueOpcode::FPMedTri32; break;
|
||||
default: return false;
|
||||
}
|
||||
std::array<IR::Value, 3> args;
|
||||
for (uint32_t index = 0; index < inst.src_count; index++) {
|
||||
args[index] = inst.op == IR::Opcode::MadMixF16 ? IR::Value(ReadMixF32(inst.src[index]))
|
||||
: IR::Value(ReadF16AsF32(inst.src[index]));
|
||||
}
|
||||
IR::F32 result;
|
||||
switch (inst.src_count) {
|
||||
case 1: result = IR::F32(ir.Emit(opcode, {args[0]})); break;
|
||||
case 2: result = IR::F32(ir.Emit(opcode, {args[0], args[1]})); break;
|
||||
case 3: result = IR::F32(ir.Emit(opcode, {args[0], args[1], args[2]})); break;
|
||||
default: EXIT("invalid half-float source count: %u", inst.src_count);
|
||||
}
|
||||
WriteF16(inst.dst, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateFloatOperation(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode {};
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::RcpF32: opcode = IR::ValueOpcode::FPRecip32; break;
|
||||
case IR::Opcode::RcpIflagF32: opcode = IR::ValueOpcode::FPRecipIFlag32; break;
|
||||
case IR::Opcode::FractF32: opcode = IR::ValueOpcode::FPFract32; break;
|
||||
case IR::Opcode::TruncF32: opcode = IR::ValueOpcode::FPTrunc32; break;
|
||||
case IR::Opcode::CeilF32: opcode = IR::ValueOpcode::FPCeil32; break;
|
||||
case IR::Opcode::RoundEvenF32: opcode = IR::ValueOpcode::FPRoundEven32; break;
|
||||
case IR::Opcode::FloorF32: opcode = IR::ValueOpcode::FPFloor32; break;
|
||||
case IR::Opcode::Exp2F32: opcode = IR::ValueOpcode::FPExp2; break;
|
||||
case IR::Opcode::Log2F32: opcode = IR::ValueOpcode::FPLog2; break;
|
||||
case IR::Opcode::InverseSqrtF32: opcode = IR::ValueOpcode::FPRecipSqrt32; break;
|
||||
case IR::Opcode::SqrtF32: opcode = IR::ValueOpcode::FPSqrt; break;
|
||||
case IR::Opcode::SinF32: opcode = IR::ValueOpcode::FPSin; break;
|
||||
case IR::Opcode::CosF32: opcode = IR::ValueOpcode::FPCos; break;
|
||||
case IR::Opcode::FAddF32: opcode = IR::ValueOpcode::FPAdd32; break;
|
||||
case IR::Opcode::FSubF32: opcode = IR::ValueOpcode::FPSub32; break;
|
||||
case IR::Opcode::FMulF32: opcode = IR::ValueOpcode::FPMul32; break;
|
||||
case IR::Opcode::FMinF32: opcode = IR::ValueOpcode::FPMin32; break;
|
||||
case IR::Opcode::FMaxF32: opcode = IR::ValueOpcode::FPMax32; break;
|
||||
case IR::Opcode::FMadF32: opcode = IR::ValueOpcode::FPFma32; break;
|
||||
case IR::Opcode::FMin3F32: opcode = IR::ValueOpcode::FPMinTri32; break;
|
||||
case IR::Opcode::FMax3F32: opcode = IR::ValueOpcode::FPMaxTri32; break;
|
||||
case IR::Opcode::FMed3F32: opcode = IR::ValueOpcode::FPMedTri32; break;
|
||||
case IR::Opcode::LdexpF32: opcode = IR::ValueOpcode::FPLdexp; break;
|
||||
default: break;
|
||||
}
|
||||
if (opcode != IR::ValueOpcode {}) {
|
||||
std::array<IR::Value, 3> args;
|
||||
for (uint32_t index = 0; index < inst.src_count; index++) {
|
||||
const auto arg_type = IR::ArgTypeOf(opcode, index);
|
||||
args[index] =
|
||||
arg_type == IR::Type::F32
|
||||
? (inst.op == IR::Opcode::FMadF32 ? IR::Value(ReadMixF32(inst.src[index]))
|
||||
: ReadOperand(inst.src[index], IR::Type::F32))
|
||||
: ReadOperand(inst.src[index], arg_type);
|
||||
}
|
||||
IR::Value result;
|
||||
switch (inst.src_count) {
|
||||
case 1: result = ir.Emit(opcode, {args[0]}); break;
|
||||
case 2: result = ir.Emit(opcode, {args[0], args[1]}); break;
|
||||
case 3: result = ir.Emit(opcode, {args[0], args[1], args[2]}); break;
|
||||
default: EXIT("invalid floating-point source count: %u", inst.src_count);
|
||||
}
|
||||
WriteOperand(inst.dst, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto select_f32 = [&](IR::U1 condition, IR::F32 true_value, IR::F32 false_value) {
|
||||
return IR::F32(ir.Emit(IR::ValueOpcode::SelectF32, {condition, true_value, false_value}));
|
||||
};
|
||||
if (inst.op == IR::Opcode::Dot2AccF32F16) {
|
||||
auto a = inst.src[0];
|
||||
a.op_sel = false;
|
||||
a.op_sel_hi = true;
|
||||
auto b = inst.src[1];
|
||||
b.op_sel = false;
|
||||
b.op_sel_hi = true;
|
||||
const auto a_lo = ReadF16LaneAsF32(a, false);
|
||||
const auto a_hi = ReadF16LaneAsF32(a, true);
|
||||
const auto b_lo = ReadF16LaneAsF32(b, false);
|
||||
const auto b_hi = ReadF16LaneAsF32(b, true);
|
||||
const auto acc = ReadMixF32(inst.src[2]);
|
||||
const auto lo = IR::F32(ir.Emit(IR::ValueOpcode::FPFma32, {a_lo, b_lo, acc}));
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::FPFma32, {a_hi, b_hi, lo}));
|
||||
return true;
|
||||
}
|
||||
if (inst.op != IR::Opcode::CubeIdF32 && inst.op != IR::Opcode::CubeScF32 &&
|
||||
inst.op != IR::Opcode::CubeTcF32 && inst.op != IR::Opcode::CubeMaF32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto x = ReadMixF32(inst.src[0]);
|
||||
const auto y = ReadMixF32(inst.src[1]);
|
||||
const auto z = ReadMixF32(inst.src[2]);
|
||||
const auto nx = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {x}));
|
||||
const auto ny = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {y}));
|
||||
const auto nz = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {z}));
|
||||
const auto ax = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {x}));
|
||||
const auto ay = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {y}));
|
||||
const auto az = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {z}));
|
||||
const auto z_face =
|
||||
ir.LogicalAnd(IR::U1(ir.Emit(IR::ValueOpcode::FPOrdGreaterThanEqual32, {az, ax})),
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::FPOrdGreaterThanEqual32, {az, ay})));
|
||||
const auto y_face = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdGreaterThanEqual32, {ay, ax}));
|
||||
const auto x_neg = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThan32, {x, IR::Value::F32(0.0f)}));
|
||||
const auto y_neg = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThan32, {y, IR::Value::F32(0.0f)}));
|
||||
const auto z_neg = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThan32, {z, IR::Value::F32(0.0f)}));
|
||||
const auto select_face = [&](IR::F32 x_value, IR::F32 y_value, IR::F32 z_value) {
|
||||
return select_f32(z_face, z_value, select_f32(y_face, y_value, x_value));
|
||||
};
|
||||
IR::F32 result;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::CubeIdF32:
|
||||
result = select_face(
|
||||
select_f32(x_neg, IR::F32(IR::Value::F32(1.0f)), IR::F32(IR::Value::F32(0.0f))),
|
||||
select_f32(y_neg, IR::F32(IR::Value::F32(3.0f)), IR::F32(IR::Value::F32(2.0f))),
|
||||
select_f32(z_neg, IR::F32(IR::Value::F32(5.0f)), IR::F32(IR::Value::F32(4.0f))));
|
||||
break;
|
||||
case IR::Opcode::CubeScF32:
|
||||
result = select_face(select_f32(x_neg, z, nz), x, select_f32(z_neg, nx, x));
|
||||
break;
|
||||
case IR::Opcode::CubeTcF32: result = select_face(ny, select_f32(y_neg, nz, z), ny); break;
|
||||
case IR::Opcode::CubeMaF32: {
|
||||
const auto two = IR::F32(IR::Value::F32(2.0f));
|
||||
result = select_face(IR::F32(ir.Emit(IR::ValueOpcode::FPMul32, {x, two})),
|
||||
IR::F32(ir.Emit(IR::ValueOpcode::FPMul32, {y, two})),
|
||||
IR::F32(ir.Emit(IR::ValueOpcode::FPMul32, {z, two})));
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
WriteOperand(inst.dst, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,630 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
bool Translator::TranslateInteger16Operation(const IR::Instruction& inst) {
|
||||
const auto mask16 = [&](IR::U32 value) {
|
||||
return ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu)));
|
||||
};
|
||||
IR::Value result;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::ShiftLeftLogicalU16:
|
||||
case IR::Opcode::ShiftRightLogicalU16:
|
||||
case IR::Opcode::ShiftRightArithmeticI16: {
|
||||
const bool arithmetic = inst.op == IR::Opcode::ShiftRightArithmeticI16;
|
||||
const auto value = ReadU16AsU32(inst.src[0], arithmetic);
|
||||
const auto count =
|
||||
ir.BitwiseAnd(ReadU16AsU32(inst.src[1], false), IR::U32(IR::Value(15u)));
|
||||
if (inst.op == IR::Opcode::ShiftLeftLogicalU16) {
|
||||
result = ir.ShiftLeftLogical(value, count);
|
||||
} else if (arithmetic) {
|
||||
result = ir.ShiftRightArithmetic(value, count);
|
||||
} else {
|
||||
result = ir.ShiftRightLogical(value, count);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::IAddU16:
|
||||
result = ir.IAdd(ReadU16AsU32(inst.src[0], false), ReadU16AsU32(inst.src[1], false));
|
||||
break;
|
||||
case IR::Opcode::ISubI16:
|
||||
result = ir.ISub(ReadU16AsU32(inst.src[0], false), ReadU16AsU32(inst.src[1], false));
|
||||
break;
|
||||
case IR::Opcode::IMinI16:
|
||||
case IR::Opcode::IMaxI16:
|
||||
case IR::Opcode::UMinU16:
|
||||
case IR::Opcode::UMaxU16: {
|
||||
const bool sign = inst.op == IR::Opcode::IMinI16 || inst.op == IR::Opcode::IMaxI16;
|
||||
IR::ValueOpcode opcode;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::IMinI16: opcode = IR::ValueOpcode::SMin32; break;
|
||||
case IR::Opcode::IMaxI16: opcode = IR::ValueOpcode::SMax32; break;
|
||||
case IR::Opcode::UMinU16: opcode = IR::ValueOpcode::UMin32; break;
|
||||
default: opcode = IR::ValueOpcode::UMax32; break;
|
||||
}
|
||||
result =
|
||||
ir.Emit(opcode, {ReadU16AsU32(inst.src[0], sign), ReadU16AsU32(inst.src[1], sign)});
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
WriteU16(inst.dst, mask16(IR::U32(result)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslatePackedInteger16(const IR::Instruction& inst) {
|
||||
const auto lane = [&](const IR::Operand& operand, bool high, bool sign) {
|
||||
return ReadU16LaneAsU32(operand, high, sign);
|
||||
};
|
||||
const auto mask_count = [&](IR::U32 value) {
|
||||
return ir.BitwiseAnd(value, IR::U32(IR::Value(15u)));
|
||||
};
|
||||
const auto translate_lane = [&](bool high) -> IR::U32 {
|
||||
if (inst.op == IR::Opcode::PackedLshlrevB16 || inst.op == IR::Opcode::PackedLshrrevB16 ||
|
||||
inst.op == IR::Opcode::PackedAshrrevI16) {
|
||||
const auto count = mask_count(lane(inst.src[0], high, false));
|
||||
const bool arithmetic = inst.op == IR::Opcode::PackedAshrrevI16;
|
||||
const auto value = lane(inst.src[1], high, arithmetic);
|
||||
if (inst.op == IR::Opcode::PackedLshlrevB16) {
|
||||
return ir.ShiftLeftLogical(value, count);
|
||||
}
|
||||
return arithmetic ? ir.ShiftRightArithmetic(value, count)
|
||||
: ir.ShiftRightLogical(value, count);
|
||||
}
|
||||
|
||||
const bool signed_minmax =
|
||||
inst.op == IR::Opcode::PackedMaxI16 || inst.op == IR::Opcode::PackedMinI16;
|
||||
const auto lhs = lane(inst.src[0], high, signed_minmax);
|
||||
const auto rhs = lane(inst.src[1], high, signed_minmax);
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::PackedMadI16:
|
||||
case IR::Opcode::PackedMadU16:
|
||||
return ir.IAdd(ir.IMul(lhs, rhs),
|
||||
lane(inst.src[2], high, inst.op == IR::Opcode::PackedMadI16));
|
||||
case IR::Opcode::PackedMulLoU16: return ir.IMul(lhs, rhs);
|
||||
case IR::Opcode::PackedAddI16:
|
||||
case IR::Opcode::PackedAddU16: return ir.IAdd(lhs, rhs);
|
||||
case IR::Opcode::PackedSubI16:
|
||||
case IR::Opcode::PackedSubU16: return ir.ISub(lhs, rhs);
|
||||
case IR::Opcode::PackedMaxI16:
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::SMax32, {lhs, rhs}));
|
||||
case IR::Opcode::PackedMinI16:
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {lhs, rhs}));
|
||||
case IR::Opcode::PackedMaxU16:
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::UMax32, {lhs, rhs}));
|
||||
case IR::Opcode::PackedMinU16:
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {lhs, rhs}));
|
||||
default: EXIT("invalid packed integer opcode");
|
||||
}
|
||||
};
|
||||
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::PackedMadI16:
|
||||
case IR::Opcode::PackedMulLoU16:
|
||||
case IR::Opcode::PackedAddI16:
|
||||
case IR::Opcode::PackedSubI16:
|
||||
case IR::Opcode::PackedLshlrevB16:
|
||||
case IR::Opcode::PackedLshrrevB16:
|
||||
case IR::Opcode::PackedAshrrevI16:
|
||||
case IR::Opcode::PackedMaxI16:
|
||||
case IR::Opcode::PackedMinI16:
|
||||
case IR::Opcode::PackedMadU16:
|
||||
case IR::Opcode::PackedAddU16:
|
||||
case IR::Opcode::PackedSubU16:
|
||||
case IR::Opcode::PackedMaxU16:
|
||||
case IR::Opcode::PackedMinU16:
|
||||
WriteOperand(inst.dst, PackU16Lanes(translate_lane(false), translate_lane(true)));
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::TranslatePerInvocationU64Mask(const IR::Instruction& inst) {
|
||||
if (!current_per_invocation_masks) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto lhs = [&] { return ReadMask(inst.src[0]); };
|
||||
const auto rhs = [&] { return ReadMask(inst.src[1]); };
|
||||
IR::U1 result;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::BitwiseAndU64: result = ir.LogicalAnd(lhs(), rhs()); break;
|
||||
case IR::Opcode::BitwiseOrU64: result = ir.LogicalOr(lhs(), rhs()); break;
|
||||
case IR::Opcode::BitwiseXorU64:
|
||||
result = IR::U1(ir.Emit(IR::ValueOpcode::LogicalXor, {lhs(), rhs()}));
|
||||
break;
|
||||
case IR::Opcode::BitwiseAndNotU64:
|
||||
result = ir.LogicalAnd(lhs(), ir.LogicalNot(rhs()));
|
||||
break;
|
||||
case IR::Opcode::BitwiseOrNotU64: result = ir.LogicalOr(lhs(), ir.LogicalNot(rhs())); break;
|
||||
case IR::Opcode::BitwiseNandU64: result = ir.LogicalNot(ir.LogicalAnd(lhs(), rhs())); break;
|
||||
case IR::Opcode::BitwiseNorU64: result = ir.LogicalNot(ir.LogicalOr(lhs(), rhs())); break;
|
||||
case IR::Opcode::BitwiseXnorU64:
|
||||
result = ir.LogicalNot(IR::U1(ir.Emit(IR::ValueOpcode::LogicalXor, {lhs(), rhs()})));
|
||||
break;
|
||||
case IR::Opcode::BitwiseNotU64: result = ir.LogicalNot(lhs()); break;
|
||||
default: return false;
|
||||
}
|
||||
WriteMask64(inst.dst, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateSimpleInteger(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode {};
|
||||
IR::Type type = IR::Type::U32;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::AbsI32: opcode = IR::ValueOpcode::IAbs32; break;
|
||||
case IR::Opcode::IAddU32: opcode = IR::ValueOpcode::IAdd32; break;
|
||||
case IR::Opcode::ISubU32: opcode = IR::ValueOpcode::ISub32; break;
|
||||
case IR::Opcode::IMulU32: opcode = IR::ValueOpcode::IMul32; break;
|
||||
case IR::Opcode::UMulHighU32: opcode = IR::ValueOpcode::UMulHi; break;
|
||||
case IR::Opcode::SMulHighI32: opcode = IR::ValueOpcode::SMulHi; break;
|
||||
case IR::Opcode::IMinI32: opcode = IR::ValueOpcode::SMin32; break;
|
||||
case IR::Opcode::IMaxI32: opcode = IR::ValueOpcode::SMax32; break;
|
||||
case IR::Opcode::UMinU32: opcode = IR::ValueOpcode::UMin32; break;
|
||||
case IR::Opcode::UMaxU32: opcode = IR::ValueOpcode::UMax32; break;
|
||||
case IR::Opcode::IMin3I32: opcode = IR::ValueOpcode::SMinTri32; break;
|
||||
case IR::Opcode::IMax3I32: opcode = IR::ValueOpcode::SMaxTri32; break;
|
||||
case IR::Opcode::IMed3I32: opcode = IR::ValueOpcode::SMedTri32; break;
|
||||
case IR::Opcode::UMin3U32: opcode = IR::ValueOpcode::UMinTri32; break;
|
||||
case IR::Opcode::UMax3U32: opcode = IR::ValueOpcode::UMaxTri32; break;
|
||||
case IR::Opcode::UMed3U32: opcode = IR::ValueOpcode::UMedTri32; break;
|
||||
case IR::Opcode::BitwiseAndU32: opcode = IR::ValueOpcode::BitwiseAnd32; break;
|
||||
case IR::Opcode::BitwiseOrU32: opcode = IR::ValueOpcode::BitwiseOr32; break;
|
||||
case IR::Opcode::BitwiseXorU32: opcode = IR::ValueOpcode::BitwiseXor32; break;
|
||||
case IR::Opcode::BitwiseNotU32: opcode = IR::ValueOpcode::BitwiseNot32; break;
|
||||
case IR::Opcode::BitReverseU32: opcode = IR::ValueOpcode::BitReverse32; break;
|
||||
case IR::Opcode::BitCountU32: opcode = IR::ValueOpcode::BitCount32; break;
|
||||
case IR::Opcode::BitCountU64:
|
||||
opcode = IR::ValueOpcode::BitCount64;
|
||||
type = IR::Type::U64;
|
||||
break;
|
||||
case IR::Opcode::FindLsbU32: opcode = IR::ValueOpcode::FindILsb32; break;
|
||||
case IR::Opcode::BitwiseAndU64:
|
||||
opcode = IR::ValueOpcode::BitwiseAnd64;
|
||||
type = IR::Type::U64;
|
||||
break;
|
||||
case IR::Opcode::BitwiseOrU64:
|
||||
opcode = IR::ValueOpcode::BitwiseOr64;
|
||||
type = IR::Type::U64;
|
||||
break;
|
||||
case IR::Opcode::ShiftLeftLogicalU32: opcode = IR::ValueOpcode::ShiftLeftLogical32; break;
|
||||
case IR::Opcode::ShiftRightLogicalU32: opcode = IR::ValueOpcode::ShiftRightLogical32; break;
|
||||
case IR::Opcode::ShiftRightArithmeticI32:
|
||||
opcode = IR::ValueOpcode::ShiftRightArithmetic32;
|
||||
break;
|
||||
case IR::Opcode::ShiftLeftLogicalU64:
|
||||
opcode = IR::ValueOpcode::ShiftLeftLogical64;
|
||||
type = IR::Type::U64;
|
||||
break;
|
||||
case IR::Opcode::ShiftRightLogicalU64:
|
||||
opcode = IR::ValueOpcode::ShiftRightLogical64;
|
||||
type = IR::Type::U64;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
std::array<IR::Value, 3> args;
|
||||
for (uint32_t index = 0; index < inst.src_count; index++) {
|
||||
const auto arg_type = IR::ArgTypeOf(opcode, index);
|
||||
args[index] = ReadOperand(inst.src[index], arg_type == IR::Type::Void ? type : arg_type);
|
||||
if (index == 1u && (opcode == IR::ValueOpcode::ShiftLeftLogical32 ||
|
||||
opcode == IR::ValueOpcode::ShiftRightLogical32 ||
|
||||
opcode == IR::ValueOpcode::ShiftRightArithmetic32)) {
|
||||
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u)));
|
||||
} else if (index == 1u && (opcode == IR::ValueOpcode::ShiftLeftLogical64 ||
|
||||
opcode == IR::ValueOpcode::ShiftRightLogical64 ||
|
||||
opcode == IR::ValueOpcode::ShiftRightArithmetic64)) {
|
||||
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(63u)));
|
||||
}
|
||||
}
|
||||
IR::Value result;
|
||||
switch (inst.src_count) {
|
||||
case 1: result = ir.Emit(opcode, {args[0]}); break;
|
||||
case 2: result = ir.Emit(opcode, {args[0], args[1]}); break;
|
||||
case 3: result = ir.Emit(opcode, {args[0], args[1], args[2]}); break;
|
||||
default: EXIT("invalid simple integer source count: %u", inst.src_count);
|
||||
}
|
||||
WriteOperand(inst.dst, result);
|
||||
if (type == IR::Type::U64 && inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file == IR::RegisterFile::Scalar) {
|
||||
IR::U1 mask;
|
||||
if (inst.op == IR::Opcode::BitwiseAndU64) {
|
||||
mask = ir.LogicalAnd(ReadMask(inst.src[0]), ReadMask(inst.src[1]));
|
||||
} else if (inst.op == IR::Opcode::BitwiseOrU64) {
|
||||
mask = ir.LogicalOr(ReadMask(inst.src[0]), ReadMask(inst.src[1]));
|
||||
}
|
||||
if (!mask.IsEmpty()) {
|
||||
ir.SetThreadBitScalarReg(static_cast<IR::ScalarReg>(inst.dst.reg.index), mask);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateComposedInteger(const IR::Instruction& inst) {
|
||||
const auto binary_u32 = [&](auto operation) {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
return operation(lhs, rhs);
|
||||
};
|
||||
const auto binary_u64 = [&](auto operation) {
|
||||
const auto lhs = UnpackU64(ReadU64(inst.src[0]));
|
||||
const auto rhs = UnpackU64(ReadU64(inst.src[1]));
|
||||
return ir.PackUint2x32(operation(lhs[0], rhs[0]), operation(lhs[1], rhs[1]));
|
||||
};
|
||||
|
||||
IR::Value result;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::BitwiseAndNotU32:
|
||||
result = binary_u32(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseAnd(lhs, ir.BitwiseNot(rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseOrNotU32:
|
||||
result = binary_u32(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseOr(lhs, ir.BitwiseNot(rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseNandU32:
|
||||
result = binary_u32(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseNot(ir.BitwiseAnd(lhs, rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseNorU32:
|
||||
result = binary_u32(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseNot(ir.BitwiseOr(lhs, rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseXnorU32:
|
||||
result = binary_u32(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseNot(ir.BitwiseXor(lhs, rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseXorU64:
|
||||
result = binary_u64([&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseXor(lhs, rhs); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseAndNotU64:
|
||||
result = binary_u64(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseAnd(lhs, ir.BitwiseNot(rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseOrNotU64:
|
||||
result = binary_u64(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseOr(lhs, ir.BitwiseNot(rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseNandU64:
|
||||
result = binary_u64(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseNot(ir.BitwiseAnd(lhs, rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseNorU64:
|
||||
result = binary_u64(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseNot(ir.BitwiseOr(lhs, rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseXnorU64:
|
||||
result = binary_u64(
|
||||
[&](IR::U32 lhs, IR::U32 rhs) { return ir.BitwiseNot(ir.BitwiseXor(lhs, rhs)); });
|
||||
break;
|
||||
case IR::Opcode::BitwiseNotU64: {
|
||||
const auto source = UnpackU64(ReadU64(inst.src[0]));
|
||||
result = ir.PackUint2x32(ir.BitwiseNot(source[0]), ir.BitwiseNot(source[1]));
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitwiseAndOrU32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
const auto add = ReadU32(inst.src[2]);
|
||||
result = ir.BitwiseOr(ir.BitwiseAnd(lhs, rhs), add);
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitwiseOr3U32:
|
||||
result = ir.BitwiseOr(ir.BitwiseOr(ReadU32(inst.src[0]), ReadU32(inst.src[1])),
|
||||
ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::BitwiseXor3U32:
|
||||
result = ir.BitwiseXor(ir.BitwiseXor(ReadU32(inst.src[0]), ReadU32(inst.src[1])),
|
||||
ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::FindMsbFromHighU32: {
|
||||
const auto source = ReadU32(inst.src[0]);
|
||||
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {source}));
|
||||
const auto position = ir.ISub(IR::U32(IR::Value(31u)), msb);
|
||||
result = ir.Select(ir.INotEqual(source, IR::U32(IR::Value(0u))), position,
|
||||
IR::U32(IR::Value(0xffffffffu)));
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::FindMsbFromHighU64: {
|
||||
const auto source = ReadU64(inst.src[0]);
|
||||
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb64, {source}));
|
||||
const auto position = ir.ISub(IR::U32(IR::Value(63u)), msb);
|
||||
const auto nonzero = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::INotEqual64,
|
||||
{source, ir.PackUint2x32(IR::U32(IR::Value(0u)), IR::U32(IR::Value(0u)))}));
|
||||
result = ir.Select(nonzero, position, IR::U32(IR::Value(0xffffffffu)));
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
WriteOperand(inst.dst, result);
|
||||
if (result.GetType() == IR::Type::U64 && inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file == IR::RegisterFile::Scalar) {
|
||||
const auto lhs = ReadMask(inst.src[0]);
|
||||
IR::U1 mask;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::BitwiseXorU64:
|
||||
mask = IR::U1(ir.Emit(IR::ValueOpcode::LogicalXor, {lhs, ReadMask(inst.src[1])}));
|
||||
break;
|
||||
case IR::Opcode::BitwiseAndNotU64:
|
||||
mask = ir.LogicalAnd(lhs, ir.LogicalNot(ReadMask(inst.src[1])));
|
||||
break;
|
||||
case IR::Opcode::BitwiseOrNotU64:
|
||||
mask = ir.LogicalOr(lhs, ir.LogicalNot(ReadMask(inst.src[1])));
|
||||
break;
|
||||
case IR::Opcode::BitwiseNandU64:
|
||||
mask = ir.LogicalNot(ir.LogicalAnd(lhs, ReadMask(inst.src[1])));
|
||||
break;
|
||||
case IR::Opcode::BitwiseNorU64:
|
||||
mask = ir.LogicalNot(ir.LogicalOr(lhs, ReadMask(inst.src[1])));
|
||||
break;
|
||||
case IR::Opcode::BitwiseXnorU64:
|
||||
mask = ir.LogicalNot(
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::LogicalXor, {lhs, ReadMask(inst.src[1])})));
|
||||
break;
|
||||
case IR::Opcode::BitwiseNotU64: mask = ir.LogicalNot(lhs); break;
|
||||
default: break;
|
||||
}
|
||||
if (!mask.IsEmpty()) {
|
||||
ir.SetThreadBitScalarReg(static_cast<IR::ScalarReg>(inst.dst.reg.index), mask);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateExtendedInteger(const IR::Instruction& inst) {
|
||||
const auto imm = [](uint32_t value) { return IR::U32(IR::Value(value)); };
|
||||
const auto mask = [&](IR::U32 value, uint32_t bits) { return ir.BitwiseAnd(value, imm(bits)); };
|
||||
const auto extract = [&](IR::U32 value, IR::U32 offset, IR::U32 width, bool sign) {
|
||||
return IR::U32(
|
||||
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
|
||||
{value, offset, width}));
|
||||
};
|
||||
const auto right_mask32 = [&](IR::U32 count) {
|
||||
return IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldInsert, {imm(0), imm(0xffffffffu), imm(0), count}));
|
||||
};
|
||||
const auto select_u64 = [&](IR::U1 condition, IR::U64 true_value, IR::U64 false_value) {
|
||||
const auto lhs = UnpackU64(true_value);
|
||||
const auto rhs = UnpackU64(false_value);
|
||||
return ir.PackUint2x32(ir.Select(condition, lhs[0], rhs[0]),
|
||||
ir.Select(condition, lhs[1], rhs[1]));
|
||||
};
|
||||
const auto right_mask64 = [&](IR::U32 count) {
|
||||
const auto below32 = IR::U1(ir.Emit(IR::ValueOpcode::ULessThan32, {count, imm(32)}));
|
||||
const auto above32 = IR::U1(ir.Emit(IR::ValueOpcode::UGreaterThan32, {count, imm(32)}));
|
||||
const auto low_count = ir.Select(below32, count, imm(32));
|
||||
const auto high_count = ir.Select(above32, ir.ISub(count, imm(32)), imm(0));
|
||||
return ir.PackUint2x32(right_mask32(low_count), right_mask32(high_count));
|
||||
};
|
||||
|
||||
IR::Value result;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::IMadI24U32:
|
||||
case IR::Opcode::UMadU24U32:
|
||||
case IR::Opcode::IMulI24U32:
|
||||
case IR::Opcode::UMulU24U32: {
|
||||
const bool sign =
|
||||
inst.op == IR::Opcode::IMadI24U32 || inst.op == IR::Opcode::IMulI24U32;
|
||||
const auto lhs = extract(ReadU32(inst.src[0]), imm(0), imm(24), sign);
|
||||
const auto rhs = extract(ReadU32(inst.src[1]), imm(0), imm(24), sign);
|
||||
auto value = ir.IMul(lhs, rhs);
|
||||
if (inst.src_count == 3) {
|
||||
value = ir.IAdd(value, ReadU32(inst.src[2]));
|
||||
}
|
||||
result = value;
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::UMadU64U32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
const auto add = UnpackU64(ReadU64(inst.src[2]));
|
||||
const auto mul_low = ir.IMul(lhs, rhs);
|
||||
const auto mul_high = IR::U32(ir.Emit(IR::ValueOpcode::UMulHi, {lhs, rhs}));
|
||||
const auto low = ir.IAdd(mul_low, add[0]);
|
||||
const auto carry_low = ir.ULessThan(low, mul_low);
|
||||
const auto high0 = ir.IAdd(mul_high, add[1]);
|
||||
const auto carry0 = ir.ULessThan(high0, mul_high);
|
||||
const auto high = ir.IAdd(high0, ir.Select(carry_low, imm(1), imm(0)));
|
||||
const auto carry1 = ir.ULessThan(high, high0);
|
||||
WriteOperand(inst.dst, ir.PackUint2x32(low, high));
|
||||
if (inst.dst2.kind != IR::OperandKind::Null) {
|
||||
WriteMask(inst.dst2, ir.LogicalOr(carry0, carry1));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::SadU32: {
|
||||
const auto lhs = ReadU32(inst.src[0]);
|
||||
const auto rhs = ReadU32(inst.src[1]);
|
||||
const auto lo = IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {lhs, rhs}));
|
||||
const auto hi = IR::U32(ir.Emit(IR::ValueOpcode::UMax32, {lhs, rhs}));
|
||||
result = ir.IAdd(ir.ISub(hi, lo), ReadU32(inst.src[2]));
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::IAdd3U32:
|
||||
result =
|
||||
ir.IAdd(ir.IAdd(ReadU32(inst.src[0]), ReadU32(inst.src[1])), ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::BitClearU32:
|
||||
case IR::Opcode::BitSetU32: {
|
||||
const auto bit = ir.ShiftLeftLogical(imm(1), mask(ReadU32(inst.src[1]), 31u));
|
||||
result = inst.op == IR::Opcode::BitClearU32
|
||||
? IR::Value(ir.BitwiseAnd(ReadU32(inst.src[0]), ir.BitwiseNot(bit)))
|
||||
: IR::Value(ir.BitwiseOr(ReadU32(inst.src[0]), bit));
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitCountAddU32:
|
||||
result = ir.IAdd(IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {ReadU32(inst.src[0])})),
|
||||
ReadU32(inst.src[1]));
|
||||
break;
|
||||
case IR::Opcode::MaskedBitCountLowU32:
|
||||
case IR::Opcode::MaskedBitCountHighU32: {
|
||||
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
|
||||
const auto local = mask(lane, 31u);
|
||||
const auto below = ir.ISub(ir.ShiftLeftLogical(imm(1), local), imm(1));
|
||||
const auto high_lane =
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::UGreaterThanEqual32, {lane, imm(32)}));
|
||||
const auto thread_mask = inst.op == IR::Opcode::MaskedBitCountLowU32
|
||||
? ir.Select(high_lane, imm(0xffffffffu), below)
|
||||
: ir.Select(high_lane, below, imm(0));
|
||||
const auto active = ir.BitwiseAnd(ReadU32(inst.src[0]), thread_mask);
|
||||
result = ir.IAdd(IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {active})),
|
||||
ReadU32(inst.src[1]));
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitReplicateB64B32: {
|
||||
const auto replicate = [&](IR::U32 value) {
|
||||
auto bits = ir.BitwiseOr(value, ir.ShiftLeftLogical(value, imm(8)));
|
||||
bits = ir.BitwiseAnd(bits, imm(0x00ff00ffu));
|
||||
bits = ir.BitwiseOr(bits, ir.ShiftLeftLogical(bits, imm(4)));
|
||||
bits = ir.BitwiseAnd(bits, imm(0x0f0f0f0fu));
|
||||
bits = ir.BitwiseOr(bits, ir.ShiftLeftLogical(bits, imm(2)));
|
||||
bits = ir.BitwiseAnd(bits, imm(0x33333333u));
|
||||
bits = ir.BitwiseOr(bits, ir.ShiftLeftLogical(bits, imm(1)));
|
||||
bits = ir.BitwiseAnd(bits, imm(0x55555555u));
|
||||
return ir.BitwiseOr(bits, ir.ShiftLeftLogical(bits, imm(1)));
|
||||
};
|
||||
const auto source = ReadU32(inst.src[0]);
|
||||
result = ir.PackUint2x32(replicate(mask(source, 0xffffu)),
|
||||
replicate(ir.ShiftRightLogical(source, imm(16))));
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitFieldMaskU32: {
|
||||
const auto count = mask(ReadU32(inst.src[0]), 31u);
|
||||
const auto offset = mask(ReadU32(inst.src[1]), 31u);
|
||||
result =
|
||||
ir.Emit(IR::ValueOpcode::BitFieldInsert, {imm(0), imm(0xffffffffu), offset, count});
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitFieldMaskU64: {
|
||||
const auto count = mask(ReadU32(inst.src[0]), 63u);
|
||||
const auto offset = mask(ReadU32(inst.src[1]), 63u);
|
||||
result = ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {right_mask64(count), offset});
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitFieldExtractU32: {
|
||||
const auto source = ReadU32(inst.src[0]);
|
||||
const auto field = ReadU32(inst.src[1]);
|
||||
const auto offset = extract(field, imm(0), imm(5), false);
|
||||
const auto count =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {extract(field, imm(16), imm(7), false),
|
||||
ir.ISub(imm(32), offset)}));
|
||||
result = ir.Emit(IR::ValueOpcode::BitFieldUExtract, {source, offset, count});
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitFieldExtractU64: {
|
||||
const auto source = ReadU64(inst.src[0]);
|
||||
const auto field = ReadU32(inst.src[1]);
|
||||
const auto offset = extract(field, imm(0), imm(6), false);
|
||||
const auto raw_count = extract(field, imm(16), imm(7), false);
|
||||
const auto available = ir.ISub(imm(64), offset);
|
||||
const auto count = IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {raw_count, available}));
|
||||
const auto shifted =
|
||||
IR::U64(ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {source, offset}));
|
||||
result = ir.Emit(IR::ValueOpcode::BitwiseAnd64, {shifted, right_mask64(count)});
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitFieldExtract3U32:
|
||||
case IR::Opcode::BitFieldExtract3I32: {
|
||||
const auto source = ReadU32(inst.src[0]);
|
||||
const auto offset = mask(ReadU32(inst.src[1]), 31u);
|
||||
const auto count =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::UMin32,
|
||||
{mask(ReadU32(inst.src[2]), 31u), ir.ISub(imm(32), offset)}));
|
||||
if (inst.op == IR::Opcode::BitFieldExtract3I32) {
|
||||
result =
|
||||
ir.BitwiseAnd(ir.ShiftRightArithmetic(source, offset), right_mask32(count));
|
||||
} else {
|
||||
result = ir.Emit(IR::ValueOpcode::BitFieldUExtract, {source, offset, count});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitFieldInsertSelectU32: {
|
||||
const auto bits = ReadU32(inst.src[0]);
|
||||
const auto insert = ReadU32(inst.src[1]);
|
||||
const auto base = ReadU32(inst.src[2]);
|
||||
result =
|
||||
ir.BitwiseOr(ir.BitwiseAnd(bits, insert), ir.BitwiseAnd(ir.BitwiseNot(bits), base));
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::BitCompare0B32:
|
||||
case IR::Opcode::BitCompare1B32: {
|
||||
const auto value = ReadU32(inst.src[0]);
|
||||
const auto bit = extract(value, mask(ReadU32(inst.src[1]), 31u), imm(1), false);
|
||||
const auto expected = imm(inst.op == IR::Opcode::BitCompare1B32 ? 1u : 0u);
|
||||
WriteOperand(inst.dst, ir.IEqual(bit, expected));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::AlignBitU32: {
|
||||
const auto hi = ReadU32(inst.src[0]);
|
||||
const auto lo = ReadU32(inst.src[1]);
|
||||
const auto shift = mask(ReadU32(inst.src[2]), 31u);
|
||||
const auto lo_part = ir.ShiftRightLogical(lo, shift);
|
||||
const auto hi_part_raw = ir.ShiftLeftLogical(hi, mask(ir.ISub(imm(32), shift), 31u));
|
||||
const auto hi_part = ir.Select(ir.INotEqual(shift, imm(0)), hi_part_raw, imm(0));
|
||||
result = ir.BitwiseOr(lo_part, hi_part);
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::ShiftLeftAddU32:
|
||||
result =
|
||||
ir.IAdd(ir.ShiftLeftLogical(ReadU32(inst.src[0]), mask(ReadU32(inst.src[1]), 31u)),
|
||||
ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::AddShiftLeftU32:
|
||||
result = ir.ShiftLeftLogical(ir.IAdd(ReadU32(inst.src[0]), ReadU32(inst.src[1])),
|
||||
mask(ReadU32(inst.src[2]), 31u));
|
||||
break;
|
||||
case IR::Opcode::XorAddU32:
|
||||
result = ir.IAdd(ir.BitwiseXor(ReadU32(inst.src[0]), ReadU32(inst.src[1])),
|
||||
ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::ShiftLeftOrU32:
|
||||
result = ir.BitwiseOr(
|
||||
ir.ShiftLeftLogical(ReadU32(inst.src[0]), mask(ReadU32(inst.src[1]), 31u)),
|
||||
ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::SelectU32:
|
||||
result =
|
||||
ir.Select(ReadCondition(inst.src[0]), ReadU32(inst.src[1]), ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::SelectMaskU32:
|
||||
result = ir.Select(ReadMask(inst.src[0]), ReadU32(inst.src[1]), ReadU32(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::SelectF32Bits:
|
||||
case IR::Opcode::SelectMaskF32Bits: {
|
||||
const auto condition = inst.op == IR::Opcode::SelectMaskF32Bits
|
||||
? ReadMask(inst.src[0])
|
||||
: ReadCondition(inst.src[0]);
|
||||
result = ir.Emit(IR::ValueOpcode::SelectF32,
|
||||
{condition, ReadOperand(inst.src[1], IR::Type::F32),
|
||||
ReadOperand(inst.src[2], IR::Type::F32)});
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::SelectU64:
|
||||
result =
|
||||
select_u64(ReadCondition(inst.src[0]), ReadU64(inst.src[1]), ReadU64(inst.src[2]));
|
||||
break;
|
||||
case IR::Opcode::PackLowLowU16:
|
||||
case IR::Opcode::PackLowHighU16:
|
||||
case IR::Opcode::PackHighHighU16:
|
||||
case IR::Opcode::PackU16U32: {
|
||||
const bool high0 = inst.op == IR::Opcode::PackHighHighU16;
|
||||
const bool high1 = inst.op == IR::Opcode::PackLowHighU16 || high0;
|
||||
const auto lo =
|
||||
high0 ? ir.ShiftRightLogical(ReadU32(inst.src[0]), imm(16)) : ReadU32(inst.src[0]);
|
||||
const auto hi =
|
||||
high1 ? ir.ShiftRightLogical(ReadU32(inst.src[1]), imm(16)) : ReadU32(inst.src[1]);
|
||||
result =
|
||||
ir.BitwiseOr(mask(lo, 0xffffu), ir.ShiftLeftLogical(mask(hi, 0xffffu), imm(16)));
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
WriteOperand(inst.dst, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,648 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
IR::MemoryFlags Translator::AddMemoryInfo(const IR::Instruction& inst) {
|
||||
const auto index = static_cast<uint32_t>(value_program.memory_info.size());
|
||||
value_program.memory_info.push_back(inst.memory);
|
||||
return {.index = index, .pc = inst.pc};
|
||||
}
|
||||
|
||||
IR::U32 Translator::GetResourceDword(uint32_t index, uint32_t dword) {
|
||||
return ReadScalarCode(index * 4u + dword);
|
||||
}
|
||||
|
||||
IR::Value Translator::GetBufferResource(const IR::MemoryInfo& memory) {
|
||||
return ir.Emit(IR::ValueOpcode::GetBufferResource,
|
||||
{GetResourceDword(memory.resource, 0), GetResourceDword(memory.resource, 1),
|
||||
GetResourceDword(memory.resource, 2), GetResourceDword(memory.resource, 3)});
|
||||
}
|
||||
|
||||
IR::Value Translator::GetAddressResource(IR::Value low, IR::Value high) {
|
||||
return ir.Emit(IR::ValueOpcode::GetAddressResource, {low, high});
|
||||
}
|
||||
|
||||
Translator::AddressOperands Translator::ReadAddressOperands(const IR::Instruction& inst,
|
||||
uint32_t first_source) {
|
||||
const auto low = ReadU32(inst.src[first_source]);
|
||||
const auto& high_or_base = inst.src[first_source + 1u];
|
||||
if (inst.memory.kind == IR::ResourceKind::Scratch) {
|
||||
const auto offset = high_or_base.kind == IR::OperandKind::Register &&
|
||||
high_or_base.reg.file != IR::RegisterFile::Vector
|
||||
? ReadU32(high_or_base)
|
||||
: low;
|
||||
return {GetAddressResource(offset, IR::Value(0u)), offset, IR::Value(0u)};
|
||||
}
|
||||
if (inst.memory.kind == IR::ResourceKind::Global &&
|
||||
high_or_base.kind == IR::OperandKind::Register &&
|
||||
high_or_base.reg.file != IR::RegisterFile::Vector) {
|
||||
const auto base_low = ReadU32(high_or_base);
|
||||
const auto base_high = ReadU32(OffsetOperand(high_or_base, 1u));
|
||||
return {GetAddressResource(base_low, base_high), low, IR::Value(0u)};
|
||||
}
|
||||
const auto high = ReadU32(high_or_base);
|
||||
return {GetAddressResource(low, high), low, high};
|
||||
}
|
||||
|
||||
IR::Value Translator::GetScalarAddressResource(uint32_t base) {
|
||||
return GetAddressResource(ReadScalarCode(base), ReadScalarCode(base + 1u));
|
||||
}
|
||||
|
||||
IR::Value Translator::GetImageResource(const IR::MemoryInfo& memory) {
|
||||
return ir.Emit(IR::ValueOpcode::GetImageResource,
|
||||
{GetResourceDword(memory.resource, 0), GetResourceDword(memory.resource, 1),
|
||||
GetResourceDword(memory.resource, 2), GetResourceDword(memory.resource, 3),
|
||||
GetResourceDword(memory.resource, 4), GetResourceDword(memory.resource, 5),
|
||||
GetResourceDword(memory.resource, 6), GetResourceDword(memory.resource, 7)});
|
||||
}
|
||||
|
||||
IR::Value Translator::GetSamplerResource(const IR::MemoryInfo& memory) {
|
||||
return ir.Emit(IR::ValueOpcode::GetSamplerResource,
|
||||
{GetResourceDword(memory.sampler, 0), GetResourceDword(memory.sampler, 1),
|
||||
GetResourceDword(memory.sampler, 2), GetResourceDword(memory.sampler, 3)});
|
||||
}
|
||||
|
||||
IR::Value Translator::GetSharedResource(IR::ResourceKind kind) {
|
||||
return ir.Emit(kind == IR::ResourceKind::Gds ? IR::ValueOpcode::GetGdsResource
|
||||
: IR::ValueOpcode::GetLdsResource);
|
||||
}
|
||||
|
||||
IR::Value Translator::MakeImageAddress(const IR::Instruction& inst, const IR::Operand& base) {
|
||||
std::array<IR::Value, 13> components {};
|
||||
components[0] = ReadRawU32(PlainOperand(base));
|
||||
const auto nsa_components =
|
||||
std::min(inst.memory.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
|
||||
for (uint32_t index = 1; index < components.size(); index++) {
|
||||
if (index - 1u < nsa_components) {
|
||||
components[index] =
|
||||
ir.GetVectorReg(static_cast<IR::VectorReg>(inst.memory.image_nsa_addr[index - 1u]));
|
||||
} else {
|
||||
components[index] = ReadRawU32(OffsetOperand(PlainOperand(base), index));
|
||||
}
|
||||
}
|
||||
return ir.Emit(IR::ValueOpcode::MakeImageAddress,
|
||||
{components[0], components[1], components[2], components[3], components[4],
|
||||
components[5], components[6], components[7], components[8], components[9],
|
||||
components[10], components[11], components[12]});
|
||||
}
|
||||
|
||||
IR::Value Translator::ConstructU32x4(const IR::Operand& base, uint32_t count) {
|
||||
std::array<IR::Value, 4> components {IR::Value(0u), IR::Value(0u), IR::Value(0u),
|
||||
IR::Value(0u)};
|
||||
for (uint32_t index = 0; index < std::min(count, 4u); index++) {
|
||||
components[index] = ReadRawU32(OffsetOperand(PlainOperand(base), index));
|
||||
}
|
||||
return ir.Emit(IR::ValueOpcode::CompositeConstructU32x4,
|
||||
{components[0], components[1], components[2], components[3]});
|
||||
}
|
||||
|
||||
void Translator::WriteImageComponents(const IR::Operand& dst, IR::Value value, uint32_t dmask,
|
||||
uint32_t component_limit) {
|
||||
const auto mask = dmask != 0u ? dmask : 1u;
|
||||
uint32_t dst_index = 0;
|
||||
for (uint32_t component = 0; component < component_limit; component++) {
|
||||
if (((mask >> component) & 1u) == 0u) {
|
||||
continue;
|
||||
}
|
||||
WriteOperand(
|
||||
OffsetOperand(dst, dst_index++),
|
||||
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {value, IR::Value(component)}));
|
||||
}
|
||||
}
|
||||
|
||||
IR::ValueOpcode Translator::ImageAtomicOpcode(IR::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::Opcode::AtomicAddU32: return IR::ValueOpcode::ImageAtomicIAdd32;
|
||||
case IR::Opcode::AtomicUMinU32: return IR::ValueOpcode::ImageAtomicUMin32;
|
||||
case IR::Opcode::AtomicUMaxU32: return IR::ValueOpcode::ImageAtomicUMax32;
|
||||
case IR::Opcode::AtomicAndU32: return IR::ValueOpcode::ImageAtomicAnd32;
|
||||
case IR::Opcode::AtomicOrU32: return IR::ValueOpcode::ImageAtomicOr32;
|
||||
case IR::Opcode::AtomicXorU32: return IR::ValueOpcode::ImageAtomicXor32;
|
||||
default: EXIT("invalid image atomic opcode");
|
||||
}
|
||||
}
|
||||
|
||||
BufferAddressValues Translator::ReadBufferAddress(const IR::Instruction& inst,
|
||||
uint32_t first_source) {
|
||||
uint32_t cursor = first_source;
|
||||
const auto next = [&]() {
|
||||
return cursor < inst.src_count ? ReadU32(inst.src[cursor++]) : IR::U32(IR::Value(0u));
|
||||
};
|
||||
const auto index = inst.memory.idxen ? next() : IR::U32(IR::Value(0u));
|
||||
const auto offset = inst.memory.offen ? next() : IR::U32(IR::Value(0u));
|
||||
const auto soffset = next();
|
||||
return {index, offset, soffset};
|
||||
}
|
||||
|
||||
IR::U32 Translator::WidenSubdword(IR::Value value, uint32_t bits, bool sign) {
|
||||
IR::U32 widened = bits == 8u ? IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U8, {value}))
|
||||
: IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {value}));
|
||||
if (sign) {
|
||||
widened = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {widened, IR::Value(0u), IR::Value(bits)}));
|
||||
}
|
||||
return widened;
|
||||
}
|
||||
|
||||
IR::Value Translator::NarrowSubdword(IR::U32 value, uint32_t bits) {
|
||||
return bits == 8u ? ir.Emit(IR::ValueOpcode::ConvertU8U32, {value})
|
||||
: ir.Emit(IR::ValueOpcode::ConvertU16U32, {value});
|
||||
}
|
||||
|
||||
IR::ValueOpcode Translator::BufferAtomicOpcode(IR::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::Opcode::AtomicSwapU32: return IR::ValueOpcode::BufferAtomicSwap32;
|
||||
case IR::Opcode::AtomicAddU32: return IR::ValueOpcode::BufferAtomicIAdd32;
|
||||
case IR::Opcode::AtomicSubU32: return IR::ValueOpcode::BufferAtomicISub32;
|
||||
case IR::Opcode::AtomicSMinI32: return IR::ValueOpcode::BufferAtomicSMin32;
|
||||
case IR::Opcode::AtomicUMinU32: return IR::ValueOpcode::BufferAtomicUMin32;
|
||||
case IR::Opcode::AtomicSMaxI32: return IR::ValueOpcode::BufferAtomicSMax32;
|
||||
case IR::Opcode::AtomicUMaxU32: return IR::ValueOpcode::BufferAtomicUMax32;
|
||||
case IR::Opcode::AtomicAndU32: return IR::ValueOpcode::BufferAtomicAnd32;
|
||||
case IR::Opcode::AtomicOrU32: return IR::ValueOpcode::BufferAtomicOr32;
|
||||
case IR::Opcode::AtomicXorU32: return IR::ValueOpcode::BufferAtomicXor32;
|
||||
case IR::Opcode::AtomicFMinF32: return IR::ValueOpcode::BufferAtomicFMin32;
|
||||
case IR::Opcode::AtomicFMaxF32: return IR::ValueOpcode::BufferAtomicFMax32;
|
||||
default: EXIT("invalid buffer atomic opcode");
|
||||
}
|
||||
}
|
||||
|
||||
IR::ValueOpcode Translator::SharedAtomicOpcode(IR::Opcode opcode, bool gds) {
|
||||
if (gds) {
|
||||
switch (opcode) {
|
||||
case IR::Opcode::AtomicSwapU32: return IR::ValueOpcode::GdsAtomicSwap32;
|
||||
case IR::Opcode::AtomicAddU32: return IR::ValueOpcode::GdsAtomicIAdd32;
|
||||
case IR::Opcode::AtomicSubU32: return IR::ValueOpcode::GdsAtomicISub32;
|
||||
case IR::Opcode::AtomicSMinI32: return IR::ValueOpcode::GdsAtomicSMin32;
|
||||
case IR::Opcode::AtomicUMinU32: return IR::ValueOpcode::GdsAtomicUMin32;
|
||||
case IR::Opcode::AtomicSMaxI32: return IR::ValueOpcode::GdsAtomicSMax32;
|
||||
case IR::Opcode::AtomicUMaxU32: return IR::ValueOpcode::GdsAtomicUMax32;
|
||||
case IR::Opcode::AtomicAndU32: return IR::ValueOpcode::GdsAtomicAnd32;
|
||||
case IR::Opcode::AtomicOrU32: return IR::ValueOpcode::GdsAtomicOr32;
|
||||
case IR::Opcode::AtomicXorU32: return IR::ValueOpcode::GdsAtomicXor32;
|
||||
default: EXIT("invalid GDS atomic opcode");
|
||||
}
|
||||
}
|
||||
switch (opcode) {
|
||||
case IR::Opcode::AtomicSwapU32: return IR::ValueOpcode::SharedAtomicSwap32;
|
||||
case IR::Opcode::AtomicAddU32: return IR::ValueOpcode::SharedAtomicIAdd32;
|
||||
case IR::Opcode::AtomicSubU32: return IR::ValueOpcode::SharedAtomicISub32;
|
||||
case IR::Opcode::AtomicSMinI32: return IR::ValueOpcode::SharedAtomicSMin32;
|
||||
case IR::Opcode::AtomicUMinU32: return IR::ValueOpcode::SharedAtomicUMin32;
|
||||
case IR::Opcode::AtomicSMaxI32: return IR::ValueOpcode::SharedAtomicSMax32;
|
||||
case IR::Opcode::AtomicUMaxU32: return IR::ValueOpcode::SharedAtomicUMax32;
|
||||
case IR::Opcode::AtomicAndU32: return IR::ValueOpcode::SharedAtomicAnd32;
|
||||
case IR::Opcode::AtomicOrU32: return IR::ValueOpcode::SharedAtomicOr32;
|
||||
case IR::Opcode::AtomicXorU32: return IR::ValueOpcode::SharedAtomicXor32;
|
||||
default: EXIT("invalid LDS atomic opcode");
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::IsBufferLoadOperation(IR::Opcode opcode) {
|
||||
return opcode == IR::Opcode::BufferLoadUbyte || opcode == IR::Opcode::BufferLoadSbyte ||
|
||||
opcode == IR::Opcode::BufferLoadUshort || opcode == IR::Opcode::BufferLoadSshort ||
|
||||
opcode == IR::Opcode::BufferLoadDword;
|
||||
}
|
||||
|
||||
bool Translator::TranslateScalarMemory(const IR::Instruction& inst) {
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::LoadSrtDword: {
|
||||
const auto resource = ir.Emit(IR::ValueOpcode::GetSrtResource);
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(IR::ValueOpcode::ReadConst, {resource, ReadU32(inst.src[0])}));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::SBufferLoadDword: {
|
||||
const auto resource = GetBufferResource(inst.memory);
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::ReadConstBuffer,
|
||||
{resource, ReadU32(inst.src[0])}, AddMemoryInfo(inst)));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::SLoadDword: {
|
||||
const auto resource = GetScalarAddressResource(inst.memory.resource);
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(IR::ValueOpcode::LoadAddressU32,
|
||||
{resource, ReadU32(inst.src[0]), IR::Value(0u), IR::Value(true)},
|
||||
AddMemoryInfo(inst)));
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::TranslateBufferLoad(const IR::Instruction& inst,
|
||||
const BufferAddressValues* address_snapshot) {
|
||||
IR::ValueOpcode opcode;
|
||||
uint32_t bits;
|
||||
bool sign;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::BufferLoadUbyte:
|
||||
case IR::Opcode::BufferLoadSbyte:
|
||||
opcode = IR::ValueOpcode::LoadBufferU8;
|
||||
bits = 8u;
|
||||
sign = inst.op == IR::Opcode::BufferLoadSbyte;
|
||||
break;
|
||||
case IR::Opcode::BufferLoadUshort:
|
||||
case IR::Opcode::BufferLoadSshort:
|
||||
opcode = IR::ValueOpcode::LoadBufferU16;
|
||||
bits = 16u;
|
||||
sign = inst.op == IR::Opcode::BufferLoadSshort;
|
||||
break;
|
||||
case IR::Opcode::BufferLoadDword:
|
||||
opcode = IR::ValueOpcode::LoadBufferU32;
|
||||
bits = 32u;
|
||||
sign = false;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
const auto resource = GetBufferResource(inst.memory);
|
||||
const auto address =
|
||||
address_snapshot != nullptr ? *address_snapshot : ReadBufferAddress(inst, 0);
|
||||
const auto loaded =
|
||||
ir.Emit(opcode, {resource, address.index, address.offset, address.soffset, ir.GetExec()},
|
||||
AddMemoryInfo(inst));
|
||||
WriteOperand(inst.dst, bits == 32u ? loaded : WidenSubdword(loaded, bits, sign));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateBufferStore(const IR::Instruction& inst) {
|
||||
const auto resource = GetBufferResource(inst.memory);
|
||||
const auto address = ReadBufferAddress(inst, 1);
|
||||
const auto data = ReadU32(inst.src[0]);
|
||||
IR::ValueOpcode opcode;
|
||||
IR::Value value;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::BufferStoreByte:
|
||||
opcode = IR::ValueOpcode::StoreBufferU8;
|
||||
value = NarrowSubdword(data, 8u);
|
||||
break;
|
||||
case IR::Opcode::BufferStoreShort:
|
||||
opcode = IR::ValueOpcode::StoreBufferU16;
|
||||
value = NarrowSubdword(data, 16u);
|
||||
break;
|
||||
case IR::Opcode::BufferStoreDword:
|
||||
opcode = IR::ValueOpcode::StoreBufferU32;
|
||||
value = data;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
ir.Emit(opcode, {resource, address.index, address.offset, address.soffset, value, ir.GetExec()},
|
||||
AddMemoryInfo(inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateAtomicMemory(const IR::Instruction& inst) {
|
||||
IR::Value result;
|
||||
switch (inst.memory.kind) {
|
||||
case IR::ResourceKind::Buffer: {
|
||||
const auto resource = GetBufferResource(inst.memory);
|
||||
const auto address = ReadBufferAddress(inst, 1);
|
||||
result = ir.Emit(BufferAtomicOpcode(inst.op),
|
||||
{resource, address.index, address.offset, address.soffset,
|
||||
ReadU32(inst.src[0]), ir.GetExec()},
|
||||
AddMemoryInfo(inst));
|
||||
break;
|
||||
}
|
||||
case IR::ResourceKind::Image:
|
||||
case IR::ResourceKind::ImageUint:
|
||||
case IR::ResourceKind::StorageImage:
|
||||
case IR::ResourceKind::StorageImageUint: {
|
||||
const auto resource = GetImageResource(inst.memory);
|
||||
const auto address = MakeImageAddress(inst, inst.src[1]);
|
||||
const auto flags = AddMemoryInfo(inst);
|
||||
result = ir.Emit(ImageAtomicOpcode(inst.op),
|
||||
{resource, address, ReadU32(inst.src[0]), ir.GetExec()}, flags);
|
||||
break;
|
||||
}
|
||||
case IR::ResourceKind::Lds:
|
||||
case IR::ResourceKind::Gds: {
|
||||
const bool gds = inst.memory.kind == IR::ResourceKind::Gds;
|
||||
const auto resource = GetSharedResource(inst.memory.kind);
|
||||
const auto address =
|
||||
ir.IAdd(ReadU32(inst.src[1]), IR::U32(IR::Value(inst.memory.offset)));
|
||||
result = ir.Emit(SharedAtomicOpcode(inst.op, gds),
|
||||
{resource, address, ReadU32(inst.src[0]), ir.GetExec()},
|
||||
AddMemoryInfo(inst));
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
if (inst.dst.kind != IR::OperandKind::Null) {
|
||||
WriteOperand(inst.dst, result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateFlatLoad(const IR::Instruction& inst) {
|
||||
IR::ValueOpcode opcode;
|
||||
uint32_t bits;
|
||||
bool sign;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::FlatLoadUbyte:
|
||||
case IR::Opcode::FlatLoadSbyte:
|
||||
opcode = IR::ValueOpcode::LoadAddressU8;
|
||||
bits = 8u;
|
||||
sign = inst.op == IR::Opcode::FlatLoadSbyte;
|
||||
break;
|
||||
case IR::Opcode::FlatLoadUshort:
|
||||
case IR::Opcode::FlatLoadSshort:
|
||||
opcode = IR::ValueOpcode::LoadAddressU16;
|
||||
bits = 16u;
|
||||
sign = inst.op == IR::Opcode::FlatLoadSshort;
|
||||
break;
|
||||
case IR::Opcode::FlatLoadDword:
|
||||
opcode = IR::ValueOpcode::LoadAddressU32;
|
||||
bits = 32u;
|
||||
sign = false;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
const auto address = ReadAddressOperands(inst, 0);
|
||||
const auto active = ir.GetExec();
|
||||
const auto count = bits == 32u ? std::min(inst.memory.data_dwords, 4u) : 1u;
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
auto component = inst;
|
||||
component.memory.offset += index * 4u;
|
||||
component.memory.data_dwords = 1u;
|
||||
component.memory.component_index = index;
|
||||
const auto loaded = ir.Emit(opcode, {address.resource, address.low, address.high, active},
|
||||
AddMemoryInfo(component));
|
||||
WriteOperand(OffsetOperand(inst.dst, index),
|
||||
bits == 32u ? loaded : WidenSubdword(loaded, bits, sign));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateFlatStore(const IR::Instruction& inst) {
|
||||
const auto data = ReadU32(inst.src[0]);
|
||||
const auto address = ReadAddressOperands(inst, 1);
|
||||
IR::ValueOpcode opcode;
|
||||
IR::Value value;
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::FlatStoreByte:
|
||||
opcode = IR::ValueOpcode::StoreAddressU8;
|
||||
value = NarrowSubdword(data, 8u);
|
||||
break;
|
||||
case IR::Opcode::FlatStoreShort:
|
||||
opcode = IR::ValueOpcode::StoreAddressU16;
|
||||
value = NarrowSubdword(data, 16u);
|
||||
break;
|
||||
case IR::Opcode::FlatStoreDword:
|
||||
opcode = IR::ValueOpcode::StoreAddressU32;
|
||||
value = data;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
ir.Emit(opcode, {address.resource, address.low, address.high, value, ir.GetExec()},
|
||||
AddMemoryInfo(inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::TranslateImageMemory(const IR::Instruction& inst) {
|
||||
const bool image = inst.memory.kind == IR::ResourceKind::Image ||
|
||||
inst.memory.kind == IR::ResourceKind::ImageUint ||
|
||||
inst.memory.kind == IR::ResourceKind::StorageImage ||
|
||||
inst.memory.kind == IR::ResourceKind::StorageImageUint;
|
||||
if (!image) {
|
||||
return false;
|
||||
}
|
||||
const auto resource = GetImageResource(inst.memory);
|
||||
const auto address =
|
||||
MakeImageAddress(inst, inst.op == IR::Opcode::ImageStore ? inst.src[1] : inst.src[0]);
|
||||
const auto flags = AddMemoryInfo(inst);
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::ImageGetResinfo: {
|
||||
const auto result =
|
||||
ir.Emit(IR::ValueOpcode::ImageQueryDimensions, {resource, address}, flags);
|
||||
WriteImageComponents(inst.dst, result, inst.memory.dmask, 4u);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ImageGetLod: {
|
||||
const auto sampler = GetSamplerResource(inst.memory);
|
||||
const auto result =
|
||||
ir.Emit(IR::ValueOpcode::ImageQueryLod, {resource, sampler, address}, flags);
|
||||
WriteImageComponents(inst.dst, result, inst.memory.dmask, 2u);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ImageLoad: {
|
||||
const auto result =
|
||||
ir.Emit(IR::ValueOpcode::ImageRead, {resource, address, ir.GetExec()}, flags);
|
||||
WriteImageComponents(inst.dst, result, inst.memory.dmask, 4u);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ImageStore: {
|
||||
const auto data = ConstructU32x4(inst.src[0], inst.memory.data_dwords);
|
||||
ir.Emit(IR::ValueOpcode::ImageWrite, {resource, address, data, ir.GetExec()}, flags);
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::ImageSample:
|
||||
case IR::Opcode::ImageGather4: {
|
||||
const auto sampler = GetSamplerResource(inst.memory);
|
||||
const auto opcode = inst.op == IR::Opcode::ImageSample
|
||||
? IR::ValueOpcode::ImageSampleRaw
|
||||
: IR::ValueOpcode::ImageGatherRaw;
|
||||
const auto result = ir.Emit(opcode, {resource, sampler, address}, flags);
|
||||
const bool dref =
|
||||
(inst.memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0u;
|
||||
if (inst.op == IR::Opcode::ImageSample && dref) {
|
||||
const auto component =
|
||||
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(0u)});
|
||||
for (uint32_t index = 0; index < inst.memory.data_dwords; index++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index), component);
|
||||
}
|
||||
} else if (inst.op == IR::Opcode::ImageGather4) {
|
||||
for (uint32_t index = 0; index < inst.memory.data_dwords; index++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index),
|
||||
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4,
|
||||
{result, IR::Value(index)}));
|
||||
}
|
||||
} else {
|
||||
WriteImageComponents(inst.dst, result, inst.memory.dmask, 4u);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::TranslateSharedMemory(const IR::Instruction& inst) {
|
||||
const bool shared =
|
||||
inst.memory.kind == IR::ResourceKind::Lds || inst.memory.kind == IR::ResourceKind::Gds;
|
||||
if (!shared && inst.op != IR::Opcode::DsSwizzleB32) {
|
||||
return false;
|
||||
}
|
||||
const bool gds = inst.memory.kind == IR::ResourceKind::Gds;
|
||||
const auto resource = shared ? GetSharedResource(inst.memory.kind) : IR::Value {};
|
||||
const auto shared_address = [&](uint32_t source) {
|
||||
return ir.IAdd(ReadU32(inst.src[source]), IR::U32(IR::Value(inst.memory.offset)));
|
||||
};
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::DsReadUbyte:
|
||||
case IR::Opcode::DsReadSbyte:
|
||||
case IR::Opcode::DsReadUshort:
|
||||
case IR::Opcode::DsReadSshort:
|
||||
case IR::Opcode::DsReadB32: {
|
||||
IR::ValueOpcode opcode;
|
||||
uint32_t bits;
|
||||
bool sign;
|
||||
if (inst.op == IR::Opcode::DsReadUbyte || inst.op == IR::Opcode::DsReadSbyte) {
|
||||
opcode = gds ? IR::ValueOpcode::LoadGdsU8 : IR::ValueOpcode::LoadSharedU8;
|
||||
bits = 8u;
|
||||
sign = inst.op == IR::Opcode::DsReadSbyte;
|
||||
} else if (inst.op == IR::Opcode::DsReadUshort || inst.op == IR::Opcode::DsReadSshort) {
|
||||
opcode = gds ? IR::ValueOpcode::LoadGdsU16 : IR::ValueOpcode::LoadSharedU16;
|
||||
bits = 16u;
|
||||
sign = inst.op == IR::Opcode::DsReadSshort;
|
||||
} else {
|
||||
opcode = gds ? IR::ValueOpcode::LoadGdsU32 : IR::ValueOpcode::LoadSharedU32;
|
||||
bits = 32u;
|
||||
sign = false;
|
||||
}
|
||||
const auto loaded =
|
||||
ir.Emit(opcode, {resource, shared_address(0), ir.GetExec()}, AddMemoryInfo(inst));
|
||||
WriteOperand(inst.dst, bits == 32u ? loaded : WidenSubdword(loaded, bits, sign));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::DsWriteByte:
|
||||
case IR::Opcode::DsWriteShort:
|
||||
case IR::Opcode::DsWriteB32: {
|
||||
const auto data = ReadU32(inst.src[0]);
|
||||
const auto address = shared_address(1);
|
||||
IR::ValueOpcode opcode;
|
||||
IR::Value value;
|
||||
if (inst.op == IR::Opcode::DsWriteByte) {
|
||||
opcode = gds ? IR::ValueOpcode::WriteGdsU8 : IR::ValueOpcode::WriteSharedU8;
|
||||
value = NarrowSubdword(data, 8u);
|
||||
} else if (inst.op == IR::Opcode::DsWriteShort) {
|
||||
opcode = gds ? IR::ValueOpcode::WriteGdsU16 : IR::ValueOpcode::WriteSharedU16;
|
||||
value = NarrowSubdword(data, 16u);
|
||||
} else {
|
||||
opcode = gds ? IR::ValueOpcode::WriteGdsU32 : IR::ValueOpcode::WriteSharedU32;
|
||||
value = data;
|
||||
}
|
||||
ir.Emit(opcode, {resource, address, value, ir.GetExec()}, AddMemoryInfo(inst));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::DsMinF32:
|
||||
case IR::Opcode::DsMaxF32: {
|
||||
const auto opcode =
|
||||
gds ? (inst.op == IR::Opcode::DsMinF32 ? IR::ValueOpcode::GdsAtomicFMin32
|
||||
: IR::ValueOpcode::GdsAtomicFMax32)
|
||||
: (inst.op == IR::Opcode::DsMinF32 ? IR::ValueOpcode::SharedAtomicFMin32
|
||||
: IR::ValueOpcode::SharedAtomicFMax32);
|
||||
ir.Emit(opcode,
|
||||
{resource, shared_address(1), ReadU32(inst.src[0]), ReadU32(inst.src[2]),
|
||||
ir.GetExec()},
|
||||
AddMemoryInfo(inst));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::DsAppend:
|
||||
case IR::Opcode::DsConsume: {
|
||||
const bool append = inst.op == IR::Opcode::DsAppend;
|
||||
const auto opcode =
|
||||
gds ? (append ? IR::ValueOpcode::GdsDataAppend : IR::ValueOpcode::GdsDataConsume)
|
||||
: (append ? IR::ValueOpcode::DataAppend : IR::ValueOpcode::DataConsume);
|
||||
WriteOperand(inst.dst, ir.Emit(opcode,
|
||||
{resource, ReadU32(inst.src[0]), ir.GetExec(),
|
||||
ir.GetExecLo(), ir.GetExecHi()},
|
||||
AddMemoryInfo(inst)));
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::DsWriteAddtidB32:
|
||||
case IR::Opcode::DsReadAddtidB32: {
|
||||
const auto m0_index = inst.op == IR::Opcode::DsWriteAddtidB32 ? 1u : 0u;
|
||||
const auto base =
|
||||
ir.BitwiseAnd(ReadU32(inst.src[m0_index]), IR::U32(IR::Value(0xffffu)));
|
||||
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
|
||||
const auto address = ir.IAdd(ir.IAdd(base, IR::U32(IR::Value(inst.memory.offset))),
|
||||
ir.ShiftLeftLogical(lane, IR::U32(IR::Value(2u))));
|
||||
if (inst.op == IR::Opcode::DsWriteAddtidB32) {
|
||||
ir.Emit(IR::ValueOpcode::WriteSharedU32,
|
||||
{resource, address, ReadU32(inst.src[0]), ir.GetExec()},
|
||||
AddMemoryInfo(inst));
|
||||
} else {
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(IR::ValueOpcode::LoadSharedU32,
|
||||
{resource, address, ir.GetExec()}, AddMemoryInfo(inst)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case IR::Opcode::DsSwizzleB32:
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(IR::ValueOpcode::SwizzleU32,
|
||||
{ReadU32(inst.src[0]), ReadU32(inst.src[1]), ir.GetExec()}));
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::TranslateMemoryOperation(const IR::Instruction& inst,
|
||||
const BufferAddressValues* address_snapshot) {
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::LoadSrtDword:
|
||||
case IR::Opcode::SLoadDword:
|
||||
case IR::Opcode::SBufferLoadDword: return TranslateScalarMemory(inst);
|
||||
|
||||
case IR::Opcode::BufferLoadUbyte:
|
||||
case IR::Opcode::BufferLoadSbyte:
|
||||
case IR::Opcode::BufferLoadUshort:
|
||||
case IR::Opcode::BufferLoadSshort:
|
||||
case IR::Opcode::BufferLoadDword: return TranslateBufferLoad(inst, address_snapshot);
|
||||
|
||||
case IR::Opcode::BufferStoreByte:
|
||||
case IR::Opcode::BufferStoreShort:
|
||||
case IR::Opcode::BufferStoreDword: return TranslateBufferStore(inst);
|
||||
|
||||
case IR::Opcode::AtomicSwapU32:
|
||||
case IR::Opcode::AtomicAddU32:
|
||||
case IR::Opcode::AtomicSubU32:
|
||||
case IR::Opcode::AtomicSMinI32:
|
||||
case IR::Opcode::AtomicUMinU32:
|
||||
case IR::Opcode::AtomicSMaxI32:
|
||||
case IR::Opcode::AtomicUMaxU32:
|
||||
case IR::Opcode::AtomicAndU32:
|
||||
case IR::Opcode::AtomicOrU32:
|
||||
case IR::Opcode::AtomicXorU32:
|
||||
case IR::Opcode::AtomicFMinF32:
|
||||
case IR::Opcode::AtomicFMaxF32: return TranslateAtomicMemory(inst);
|
||||
|
||||
case IR::Opcode::FlatLoadUbyte:
|
||||
case IR::Opcode::FlatLoadSbyte:
|
||||
case IR::Opcode::FlatLoadUshort:
|
||||
case IR::Opcode::FlatLoadSshort:
|
||||
case IR::Opcode::FlatLoadDword: return TranslateFlatLoad(inst);
|
||||
|
||||
case IR::Opcode::FlatStoreByte:
|
||||
case IR::Opcode::FlatStoreShort:
|
||||
case IR::Opcode::FlatStoreDword: return TranslateFlatStore(inst);
|
||||
|
||||
case IR::Opcode::ImageGetResinfo:
|
||||
case IR::Opcode::ImageGetLod:
|
||||
case IR::Opcode::ImageLoad:
|
||||
case IR::Opcode::ImageStore:
|
||||
case IR::Opcode::ImageSample:
|
||||
case IR::Opcode::ImageGather4: return TranslateImageMemory(inst);
|
||||
|
||||
case IR::Opcode::DsReadUbyte:
|
||||
case IR::Opcode::DsReadSbyte:
|
||||
case IR::Opcode::DsReadUshort:
|
||||
case IR::Opcode::DsReadSshort:
|
||||
case IR::Opcode::DsReadB32:
|
||||
case IR::Opcode::DsWriteByte:
|
||||
case IR::Opcode::DsWriteShort:
|
||||
case IR::Opcode::DsWriteB32:
|
||||
case IR::Opcode::DsMinF32:
|
||||
case IR::Opcode::DsMaxF32:
|
||||
case IR::Opcode::DsSwizzleB32:
|
||||
case IR::Opcode::DsConsume:
|
||||
case IR::Opcode::DsAppend:
|
||||
case IR::Opcode::DsWriteAddtidB32:
|
||||
case IR::Opcode::DsReadAddtidB32: return TranslateSharedMemory(inst);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
@@ -0,0 +1,927 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fmt/format.h>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
namespace Detail {
|
||||
|
||||
bool Fail(std::string* error, const std::string& message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
IR::Operand Translator::OffsetOperand(const IR::Operand& operand, uint32_t offset) {
|
||||
if (offset == 0 || operand.kind != IR::OperandKind::Register) {
|
||||
return operand;
|
||||
}
|
||||
auto result = operand;
|
||||
result.reg.index += offset;
|
||||
result.sdwa_sel = 6;
|
||||
result.sdwa_dst_unused = 2;
|
||||
result.omod = 0;
|
||||
result.sdwa_sext = false;
|
||||
result.op_sel = false;
|
||||
result.op_sel_hi = false;
|
||||
result.negate = false;
|
||||
result.negate_hi = false;
|
||||
result.absolute = false;
|
||||
result.clamp = false;
|
||||
result.dpp_ctrl = 0;
|
||||
result.dpp_row_mask = 0xf;
|
||||
result.dpp_bank_mask = 0xf;
|
||||
result.dpp_fetch_inactive = false;
|
||||
result.dpp_bound_ctrl = false;
|
||||
result.dpp = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
IR::Operand Translator::PlainOperand(const IR::Operand& operand) {
|
||||
auto result = operand;
|
||||
result.sdwa_sel = 6;
|
||||
result.sdwa_dst_unused = 2;
|
||||
result.omod = 0;
|
||||
result.sdwa_sext = false;
|
||||
result.op_sel = false;
|
||||
result.op_sel_hi = false;
|
||||
result.negate = false;
|
||||
result.negate_hi = false;
|
||||
result.absolute = false;
|
||||
result.clamp = false;
|
||||
result.dpp_ctrl = 0;
|
||||
result.dpp_row_mask = 0xf;
|
||||
result.dpp_bank_mask = 0xf;
|
||||
result.dpp_fetch_inactive = false;
|
||||
result.dpp_bound_ctrl = false;
|
||||
result.dpp = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadPcRelativeU32(const IR::Operand& operand) {
|
||||
EXIT_IF(operand.kind != IR::OperandKind::PcRelativeU32 &&
|
||||
operand.kind != IR::OperandKind::PcRelativeHighU32);
|
||||
const auto address =
|
||||
ir.Emit(IR::ValueOpcode::IAdd64, {ir.Emit(IR::ValueOpcode::GetShaderBase),
|
||||
IR::Value(static_cast<uint64_t>(operand.imm))});
|
||||
const auto pair = ir.Emit(IR::ValueOpcode::UnpackUint2x32, {address});
|
||||
return IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::CompositeExtractU32x2,
|
||||
{pair, IR::Value(operand.kind == IR::OperandKind::PcRelativeU32 ? 0u : 1u)}));
|
||||
}
|
||||
|
||||
std::array<IR::U32, 2> Translator::BallotMask(IR::U1 value) {
|
||||
if (current_per_invocation_masks) {
|
||||
return {ir.Select(value, IR::U32(IR::Value(1u)), IR::U32(IR::Value(0u))),
|
||||
IR::U32(IR::Value(0u))};
|
||||
}
|
||||
const auto ballot = ir.Emit(IR::ValueOpcode::Ballot, {value});
|
||||
const auto low =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {ballot, IR::Value(0u)}));
|
||||
// RDNA2 wave32 masks have one architectural dword; the upper half is unused.
|
||||
const auto high =
|
||||
current_wave_size == 32u
|
||||
? IR::U32(IR::Value(0u))
|
||||
: IR::U32(ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {ballot, IR::Value(1u)}));
|
||||
return {low, high};
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadRawU32(const IR::Operand& operand) {
|
||||
if (operand.kind == IR::OperandKind::ImmediateU32) {
|
||||
return IR::U32(IR::Value(operand.imm));
|
||||
}
|
||||
if (operand.kind == IR::OperandKind::PcRelativeU32 ||
|
||||
operand.kind == IR::OperandKind::PcRelativeHighU32) {
|
||||
return ReadPcRelativeU32(operand);
|
||||
}
|
||||
if (operand.kind == IR::OperandKind::Null) {
|
||||
return IR::U32(IR::Value(0u));
|
||||
}
|
||||
EXIT_IF(operand.kind != IR::OperandKind::Register);
|
||||
switch (operand.reg.file) {
|
||||
case IR::RegisterFile::Scalar:
|
||||
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg.index));
|
||||
case IR::RegisterFile::Vector:
|
||||
return ir.GetVectorReg(static_cast<IR::VectorReg>(operand.reg.index));
|
||||
case IR::RegisterFile::Vcc: return operand.reg.index == 0 ? ir.GetVccLo() : ir.GetVccHi();
|
||||
case IR::RegisterFile::M0: return ir.GetM0();
|
||||
case IR::RegisterFile::Exec:
|
||||
return operand.reg.index == 0 ? ir.GetExecLo() : ir.GetExecHi();
|
||||
case IR::RegisterFile::Scc:
|
||||
return ir.Select(ir.GetScc(), IR::U32(IR::Value(1u)), IR::U32(IR::Value(0u)));
|
||||
}
|
||||
EXIT("invalid register file used as a raw U32 source");
|
||||
}
|
||||
|
||||
// Scalar operands share one encoded namespace with VCC, M0, and EXEC aliases.
|
||||
IR::U32 Translator::ReadScalarCode(uint32_t code) {
|
||||
if (code < 106u) {
|
||||
return ir.GetScalarReg(static_cast<IR::ScalarReg>(code));
|
||||
}
|
||||
switch (code) {
|
||||
case 106u: return ir.GetVccLo();
|
||||
case 107u: return ir.GetVccHi();
|
||||
case 124u: return ir.GetM0();
|
||||
case 126u:
|
||||
case 127u: {
|
||||
const auto mask = BallotMask(ir.GetExec());
|
||||
return mask[code - 126u];
|
||||
}
|
||||
default: return IR::U32(IR::Value(0u));
|
||||
}
|
||||
}
|
||||
|
||||
IR::U32 Translator::ApplyBitSourceModifiers(const IR::Operand& operand, IR::U32 value) {
|
||||
if (operand.dpp) {
|
||||
const IR::DppMoveFlags flags {
|
||||
.control = static_cast<uint16_t>(operand.dpp_ctrl),
|
||||
.row_mask = static_cast<uint8_t>(operand.dpp_row_mask),
|
||||
.bank_mask = static_cast<uint8_t>(operand.dpp_bank_mask),
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
};
|
||||
value = IR::U32(ir.Emit(IR::ValueOpcode::DppMoveU32, {value, ir.GetExec()}, flags));
|
||||
}
|
||||
if (operand.sdwa_sel != 6u) {
|
||||
uint32_t offset = 0;
|
||||
uint32_t width = 0;
|
||||
if (operand.sdwa_sel <= 3u) {
|
||||
offset = operand.sdwa_sel * 8u;
|
||||
width = 8u;
|
||||
} else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
width = 16u;
|
||||
} else {
|
||||
EXIT("invalid SDWA source selector");
|
||||
}
|
||||
const auto opcode = operand.sdwa_sext ? IR::ValueOpcode::BitFieldSExtract
|
||||
: IR::ValueOpcode::BitFieldUExtract;
|
||||
value = IR::U32(ir.Emit(opcode, {value, IR::Value(offset), IR::Value(width)}));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
IR::Value Translator::ReadOperand(const IR::Operand& operand, IR::Type type) {
|
||||
if (type == IR::Type::U16) {
|
||||
return ir.Emit(IR::ValueOpcode::ConvertU16U32,
|
||||
{ApplyBitSourceModifiers(operand, ReadRawU32(operand))});
|
||||
}
|
||||
if (type == IR::Type::F16) {
|
||||
const auto bits = IR::U16(ir.Emit(IR::ValueOpcode::ConvertU16U32,
|
||||
{ApplyBitSourceModifiers(operand, ReadRawU32(operand))}));
|
||||
return ir.Emit(IR::ValueOpcode::BitCastF16U16, {bits});
|
||||
}
|
||||
if (type == IR::Type::U1) {
|
||||
EXIT_IF(operand.kind != IR::OperandKind::Register);
|
||||
switch (operand.reg.file) {
|
||||
case IR::RegisterFile::Scc: return ir.GetScc();
|
||||
case IR::RegisterFile::Exec: return ir.GetExec();
|
||||
case IR::RegisterFile::Vcc: return ir.GetVcc();
|
||||
default: break;
|
||||
}
|
||||
return ir.INotEqual(ReadRawU32(operand), IR::U32(IR::Value(0u)));
|
||||
}
|
||||
if (type == IR::Type::U64) {
|
||||
if (operand.kind == IR::OperandKind::Register &&
|
||||
operand.reg.file == IR::RegisterFile::Exec) {
|
||||
return ir.PackUint2x32(ir.GetExecLo(), ir.GetExecHi());
|
||||
}
|
||||
if (operand.kind == IR::OperandKind::Register &&
|
||||
operand.reg.file == IR::RegisterFile::Vcc) {
|
||||
return ir.PackUint2x32(ir.GetVccLo(), ir.GetVccHi());
|
||||
}
|
||||
const auto low = ApplyBitSourceModifiers(operand, ReadRawU32(operand));
|
||||
IR::U32 high(IR::Value(0u));
|
||||
if (operand.kind == IR::OperandKind::Register) {
|
||||
high = ReadRawU32(OffsetOperand(operand, 1));
|
||||
} else if (operand.kind == IR::OperandKind::ImmediateU32 && operand.sext_64) {
|
||||
high = IR::U32(IR::Value(0xffffffffu));
|
||||
}
|
||||
return ir.PackUint2x32(low, high);
|
||||
}
|
||||
auto bits = ApplyBitSourceModifiers(operand, ReadRawU32(operand));
|
||||
if (TypesOverlap(type, IR::Type::F32) && !TypesOverlap(type, IR::Type::U32)) {
|
||||
auto value = ir.BitCastF32(bits);
|
||||
if (operand.absolute) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {value}));
|
||||
}
|
||||
if (operand.negate) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {value}));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (operand.absolute) {
|
||||
bits = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x7fffffffu)));
|
||||
}
|
||||
if (operand.negate) {
|
||||
bits = ir.BitwiseXor(bits, IR::U32(IR::Value(0x80000000u)));
|
||||
}
|
||||
if (!TypesOverlap(type, IR::Type::U32)) {
|
||||
EXIT("opcode %s at 0x%08x requested unsupported operand type %s",
|
||||
IR::OpcodeName(current_opcode).data(), current_pc, IR::TypeName(type).c_str());
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
void Translator::WriteRawU32(const IR::Operand& operand, IR::U32 value) {
|
||||
if (operand.kind == IR::OperandKind::Null) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(operand.kind != IR::OperandKind::Register);
|
||||
if (operand.sdwa_sel != 6u) {
|
||||
uint32_t offset = 0;
|
||||
uint32_t width = 0;
|
||||
if (operand.sdwa_sel <= 3u) {
|
||||
offset = operand.sdwa_sel * 8u;
|
||||
width = 8u;
|
||||
} else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
width = 16u;
|
||||
} else {
|
||||
EXIT("invalid SDWA destination selector");
|
||||
}
|
||||
switch (operand.sdwa_dst_unused) {
|
||||
case 0:
|
||||
value =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldInsert,
|
||||
{IR::Value(0u), value, IR::Value(offset), IR::Value(width)}));
|
||||
break;
|
||||
case 1: {
|
||||
const auto extended = IR::U32(ir.Emit(IR::ValueOpcode::BitFieldSExtract,
|
||||
{value, IR::Value(0u), IR::Value(width)}));
|
||||
value = ir.ShiftLeftLogical(extended, IR::U32(IR::Value(offset)));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const uint32_t field_mask = width == 32u ? 0xffffffffu : (1u << width) - 1u;
|
||||
const auto inserted =
|
||||
ir.ShiftLeftLogical(ir.BitwiseAnd(value, IR::U32(IR::Value(field_mask))),
|
||||
IR::U32(IR::Value(offset)));
|
||||
const auto cleared = ir.BitwiseAnd(ReadRawU32(PlainOperand(operand)),
|
||||
IR::U32(IR::Value(~(field_mask << offset))));
|
||||
value = ir.BitwiseOr(cleared, inserted);
|
||||
} break;
|
||||
default: EXIT("reserved SDWA DST_U mode");
|
||||
}
|
||||
}
|
||||
switch (operand.reg.file) {
|
||||
case IR::RegisterFile::Scalar:
|
||||
ir.SetScalarReg(static_cast<IR::ScalarReg>(operand.reg.index), value);
|
||||
break;
|
||||
case IR::RegisterFile::Vector: {
|
||||
const auto reg = static_cast<IR::VectorReg>(operand.reg.index);
|
||||
const auto old = ir.GetVectorReg(reg);
|
||||
if (operand.dpp) {
|
||||
const IR::DppMoveFlags flags {
|
||||
.control = static_cast<uint16_t>(operand.dpp_ctrl),
|
||||
.row_mask = static_cast<uint8_t>(operand.dpp_row_mask),
|
||||
.bank_mask = static_cast<uint8_t>(operand.dpp_bank_mask),
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
};
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()}, flags));
|
||||
} else {
|
||||
value = ir.Select(ir.GetExec(), value, old);
|
||||
}
|
||||
ir.SetVectorReg(reg, value);
|
||||
break;
|
||||
}
|
||||
case IR::RegisterFile::Vcc:
|
||||
if (operand.reg.index == 0) {
|
||||
ir.SetVccLo(IR::U32(value));
|
||||
ir.SetVcc(ThreadBit(IR::U32(value), ir.GetVccHi()));
|
||||
} else {
|
||||
ir.SetVccHi(IR::U32(value));
|
||||
ir.SetVcc(ThreadBit(ir.GetVccLo(), IR::U32(value)));
|
||||
}
|
||||
break;
|
||||
case IR::RegisterFile::M0: ir.SetM0(IR::U32(value)); break;
|
||||
case IR::RegisterFile::Exec:
|
||||
if (operand.reg.index == 0) {
|
||||
ir.SetExecLo(IR::U32(value));
|
||||
ir.SetExec(ThreadBit(IR::U32(value), ir.GetExecHi()));
|
||||
} else {
|
||||
ir.SetExecHi(IR::U32(value));
|
||||
ir.SetExec(ThreadBit(ir.GetExecLo(), IR::U32(value)));
|
||||
}
|
||||
break;
|
||||
case IR::RegisterFile::Scc: ir.SetScc(ir.INotEqual(value, IR::U32(IR::Value(0u)))); break;
|
||||
}
|
||||
}
|
||||
|
||||
IR::U64 Translator::ExpandWholeQuadMask(IR::U64 value) {
|
||||
const auto shifted1 =
|
||||
IR::U64(ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {value, IR::Value(1u)}));
|
||||
const auto shifted2 =
|
||||
IR::U64(ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {value, IR::Value(2u)}));
|
||||
const auto shifted3 =
|
||||
IR::U64(ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {value, IR::Value(3u)}));
|
||||
const auto merged1 = ir.Emit(IR::ValueOpcode::BitwiseOr64, {value, shifted1});
|
||||
const auto merged2 = ir.Emit(IR::ValueOpcode::BitwiseOr64, {merged1, shifted2});
|
||||
const auto merged3 = ir.Emit(IR::ValueOpcode::BitwiseOr64, {merged2, shifted3});
|
||||
const auto low_bits =
|
||||
ir.Emit(IR::ValueOpcode::BitwiseAnd64, {merged3, IR::Value(0x1111111111111111ull)});
|
||||
return IR::U64(ir.Emit(IR::ValueOpcode::IMul64, {low_bits, IR::Value(uint64_t {0xfull})}));
|
||||
}
|
||||
|
||||
IR::F32 Translator::ApplyF32ResultModifiers(const IR::Operand& operand, IR::F32 value) {
|
||||
if (operand.omod != 0u) {
|
||||
float multiplier = 0.5f;
|
||||
switch (operand.omod) {
|
||||
case 1u: multiplier = 2.0f; break;
|
||||
case 2u: multiplier = 4.0f; break;
|
||||
default: break;
|
||||
}
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPMul32, {value, IR::Value::F32(multiplier)}));
|
||||
}
|
||||
if (operand.clamp) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPSaturate32, {value}));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void Translator::WriteOperand(const IR::Operand& operand, IR::Value value) {
|
||||
if (operand.kind == IR::OperandKind::Null) {
|
||||
return;
|
||||
}
|
||||
auto type = value.GetType();
|
||||
if (type == IR::Type::F32) {
|
||||
value = ApplyF32ResultModifiers(operand, IR::F32(value));
|
||||
type = IR::Type::F32;
|
||||
}
|
||||
if (type == IR::Type::Opaque) {
|
||||
EXIT("opcode %s at 0x%08x produced an untyped value", IR::OpcodeName(current_opcode).data(),
|
||||
current_pc);
|
||||
}
|
||||
if (type == IR::Type::U1) {
|
||||
EXIT_IF(operand.kind != IR::OperandKind::Register);
|
||||
switch (operand.reg.file) {
|
||||
case IR::RegisterFile::Scc: ir.SetScc(IR::U1(value)); return;
|
||||
case IR::RegisterFile::Exec: {
|
||||
const auto mask = BallotMask(IR::U1(value));
|
||||
ir.SetExec(IR::U1(value));
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
case IR::RegisterFile::Vcc: {
|
||||
const auto mask = BallotMask(IR::U1(value));
|
||||
ir.SetVcc(IR::U1(value));
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
WriteRawU32(operand, ir.Select(IR::U1(value), IR::U32(IR::Value(1u)),
|
||||
IR::U32(IR::Value(0u))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (type == IR::Type::U16) {
|
||||
WriteRawU32(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {IR::U16(value)})));
|
||||
return;
|
||||
}
|
||||
if (type == IR::Type::F16) {
|
||||
const auto bits = IR::U16(ir.Emit(IR::ValueOpcode::BitCastU16F16, {value}));
|
||||
WriteRawU32(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {bits})));
|
||||
return;
|
||||
}
|
||||
if (type == IR::Type::U64) {
|
||||
const auto unpacked = ir.Emit(IR::ValueOpcode::UnpackUint2x32, {value});
|
||||
const auto low = ir.CompositeExtract(unpacked, 0);
|
||||
const auto high = ir.CompositeExtract(unpacked, 1);
|
||||
if (operand.kind == IR::OperandKind::Register) {
|
||||
const auto thread_bit = ThreadBit(low, high);
|
||||
switch (operand.reg.file) {
|
||||
case IR::RegisterFile::Exec:
|
||||
ir.SetExec(thread_bit);
|
||||
ir.SetExecLo(low);
|
||||
ir.SetExecHi(high);
|
||||
return;
|
||||
case IR::RegisterFile::Vcc:
|
||||
ir.SetVcc(thread_bit);
|
||||
ir.SetVccLo(low);
|
||||
ir.SetVccHi(high);
|
||||
return;
|
||||
case IR::RegisterFile::Scalar:
|
||||
ir.SetThreadBitScalarReg(static_cast<IR::ScalarReg>(operand.reg.index),
|
||||
thread_bit);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
WriteRawU32(operand, low);
|
||||
WriteRawU32(OffsetOperand(operand, 1), high);
|
||||
return;
|
||||
}
|
||||
if (type == IR::Type::F32) {
|
||||
WriteRawU32(operand, ir.BitCastU32(IR::F32(value)));
|
||||
return;
|
||||
}
|
||||
EXIT_IF(type != IR::Type::U32);
|
||||
WriteRawU32(operand, IR::U32(value));
|
||||
}
|
||||
|
||||
IR::U32 Translator::PackHalf2x16(IR::F32 low, IR::F32 high) {
|
||||
const auto pair = ir.Emit(IR::ValueOpcode::CompositeConstructF32x2, {low, high});
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::PackHalf2x16, {pair}));
|
||||
}
|
||||
|
||||
void Translator::WriteF16(const IR::Operand& operand, IR::F32 value) {
|
||||
value = ApplyF32ResultModifiers(operand, value);
|
||||
const auto half = IR::F16(ir.Emit(IR::ValueOpcode::ConvertF16F32, {value}));
|
||||
const auto bits = IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16,
|
||||
{IR::U16(ir.Emit(IR::ValueOpcode::BitCastU16F16, {half}))}));
|
||||
auto merged = bits;
|
||||
if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
merged = IR::U32(ir.Emit(IR::ValueOpcode::BitFieldInsert,
|
||||
{ReadRawU32(PlainOperand(operand)), bits,
|
||||
IR::Value(operand.sdwa_sel == 5u ? 16u : 0u), IR::Value(16u)}));
|
||||
}
|
||||
auto raw = operand;
|
||||
raw.sdwa_sel = 6u;
|
||||
raw.omod = 0u;
|
||||
raw.op_sel = false;
|
||||
raw.clamp = false;
|
||||
WriteRawU32(raw, merged);
|
||||
}
|
||||
|
||||
void Translator::WriteU16(const IR::Operand& operand, IR::U32 value) {
|
||||
auto merged = ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu)));
|
||||
if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
merged = IR::U32(ir.Emit(IR::ValueOpcode::BitFieldInsert,
|
||||
{ReadRawU32(PlainOperand(operand)), merged,
|
||||
IR::Value(operand.sdwa_sel == 5u ? 16u : 0u), IR::Value(16u)}));
|
||||
}
|
||||
auto raw = operand;
|
||||
raw.sdwa_sel = 6u;
|
||||
raw.omod = 0u;
|
||||
raw.op_sel = false;
|
||||
raw.clamp = false;
|
||||
WriteRawU32(raw, merged);
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadU32(const IR::Operand& operand) {
|
||||
return IR::U32(ReadOperand(operand, IR::Type::U32));
|
||||
}
|
||||
|
||||
IR::U64 Translator::ReadU64(const IR::Operand& operand) {
|
||||
return IR::U64(ReadOperand(operand, IR::Type::U64));
|
||||
}
|
||||
|
||||
IR::F32 Translator::ReadF16LaneAsF32(const IR::Operand& operand, bool high_lane) {
|
||||
auto raw_operand = operand;
|
||||
raw_operand.sdwa_sel = 6;
|
||||
raw_operand.sdwa_sext = false;
|
||||
const auto bits = ApplyBitSourceModifiers(raw_operand, ReadRawU32(operand));
|
||||
uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u;
|
||||
if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
}
|
||||
const auto half_u32 = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(16u)}));
|
||||
const auto half_u16 = IR::U16(ir.Emit(IR::ValueOpcode::ConvertU16U32, {half_u32}));
|
||||
const auto half = IR::F16(ir.Emit(IR::ValueOpcode::BitCastF16U16, {half_u16}));
|
||||
auto value = IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32F16, {half}));
|
||||
if (operand.absolute) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {value}));
|
||||
}
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {value}));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
IR::F32 Translator::ReadF16AsF32(const IR::Operand& operand) {
|
||||
return ReadF16LaneAsF32(operand, false);
|
||||
}
|
||||
|
||||
IR::F32 Translator::ReadMixF32(const IR::Operand& operand) {
|
||||
if (operand.op_sel_hi) {
|
||||
return ReadF16AsF32(operand);
|
||||
}
|
||||
auto value_operand = operand;
|
||||
value_operand.op_sel = false;
|
||||
value_operand.op_sel_hi = false;
|
||||
value_operand.negate_hi = false;
|
||||
return IR::F32(ReadOperand(value_operand, IR::Type::F32));
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadU16LaneRaw(const IR::Operand& operand, bool high_lane) {
|
||||
auto raw_operand = operand;
|
||||
raw_operand.sdwa_sel = 6;
|
||||
raw_operand.sdwa_sext = false;
|
||||
const auto bits = ApplyBitSourceModifiers(raw_operand, ReadRawU32(operand));
|
||||
uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u;
|
||||
uint32_t width = 16u;
|
||||
if (operand.sdwa_sel <= 3u) {
|
||||
offset = operand.sdwa_sel * 8u;
|
||||
width = 8u;
|
||||
} else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
}
|
||||
return IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadU16LaneAsU32(const IR::Operand& operand, bool high_lane, bool sign_extend) {
|
||||
auto value = ReadU16LaneRaw(operand, high_lane);
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
value = ir.BitwiseAnd(ir.ISub(IR::U32(IR::Value(0u)), value), IR::U32(IR::Value(0xffffu)));
|
||||
}
|
||||
if (sign_extend || operand.sdwa_sext) {
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {value, IR::Value(0u), IR::Value(16u)}));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadU16AsU32(const IR::Operand& operand, bool sign_extend) {
|
||||
return ReadU16LaneAsU32(operand, false, sign_extend);
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadF16LaneBits(const IR::Operand& operand, bool high_lane) {
|
||||
auto value = ReadU16LaneRaw(operand, high_lane);
|
||||
if (operand.absolute) {
|
||||
value = ir.BitwiseAnd(value, IR::U32(IR::Value(0x7fffu)));
|
||||
}
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
value = ir.BitwiseXor(value, IR::U32(IR::Value(0x8000u)));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::array<IR::U32, 2> Translator::UnpackU64(IR::U64 value) {
|
||||
const auto unpacked = ir.Emit(IR::ValueOpcode::UnpackUint2x32, {value});
|
||||
return {ir.CompositeExtract(unpacked, 0), ir.CompositeExtract(unpacked, 1)};
|
||||
}
|
||||
|
||||
IR::U1 Translator::ThreadBit(IR::U32 low, IR::U32 high) {
|
||||
if (current_per_invocation_masks) {
|
||||
return ir.INotEqual(low, IR::U32(IR::Value(0u)));
|
||||
}
|
||||
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
|
||||
auto word = low;
|
||||
if (current_wave_size == 64u) {
|
||||
const auto high_lane =
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::UGreaterThanEqual32, {lane, IR::Value(32u)}));
|
||||
word = ir.Select(high_lane, high, low);
|
||||
}
|
||||
const auto bit =
|
||||
ir.BitwiseAnd(ir.ShiftRightLogical(word, ir.BitwiseAnd(lane, IR::U32(IR::Value(31u)))),
|
||||
IR::U32(IR::Value(1u)));
|
||||
return ir.INotEqual(bit, IR::U32(IR::Value(0u)));
|
||||
}
|
||||
|
||||
IR::U1 Translator::ReadCondition(const IR::Operand& operand) {
|
||||
return IR::U1(ReadOperand(operand, IR::Type::U1));
|
||||
}
|
||||
|
||||
IR::U32 Translator::ConditionBit(const IR::Operand& operand) {
|
||||
if (operand.kind == IR::OperandKind::ImmediateU32) {
|
||||
return IR::U32(IR::Value(operand.imm & 1u));
|
||||
}
|
||||
return ir.Select(ReadCondition(operand), IR::U32(IR::Value(1u)), IR::U32(IR::Value(0u)));
|
||||
}
|
||||
|
||||
IR::U1 Translator::ReadMask(const IR::Operand& operand) {
|
||||
if (operand.kind == IR::OperandKind::ImmediateU32) {
|
||||
return IR::U1(IR::Value(operand.imm != 0u || operand.sext_64));
|
||||
}
|
||||
EXIT_IF(operand.kind != IR::OperandKind::Register);
|
||||
switch (operand.reg.file) {
|
||||
case IR::RegisterFile::Scalar: {
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg.index);
|
||||
const auto high =
|
||||
current_wave_size == 64u && IR::RegIndex(reg) + 1u < IR::NumScalarRegs
|
||||
? ir.GetScalarReg(static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u))
|
||||
: IR::U32(IR::Value(0u));
|
||||
return ThreadBit(ir.GetScalarReg(reg), high);
|
||||
}
|
||||
case IR::RegisterFile::Exec: return ir.GetExec();
|
||||
case IR::RegisterFile::Vcc: return ir.GetVcc();
|
||||
case IR::RegisterFile::Scc: return ir.GetScc();
|
||||
default: return ir.INotEqual(ReadRawU32(operand), IR::U32(IR::Value(0u)));
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::WriteMask(const IR::Operand& operand, IR::U1 value) {
|
||||
EXIT_IF(operand.kind != IR::OperandKind::Register);
|
||||
switch (operand.reg.file) {
|
||||
case IR::RegisterFile::Scalar: {
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg.index);
|
||||
const auto mask = BallotMask(value);
|
||||
ir.SetThreadBitScalarReg(reg, value);
|
||||
ir.SetScalarReg(reg, mask[0]);
|
||||
// A wave32 VALU mask destination must not overwrite the neighboring SGPR.
|
||||
if (current_wave_size == 64u && IR::RegIndex(reg) + 1u < IR::NumScalarRegs) {
|
||||
ir.SetScalarReg(static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u), mask[1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IR::RegisterFile::Exec: {
|
||||
const auto mask = BallotMask(value);
|
||||
ir.SetExec(value);
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
case IR::RegisterFile::Vcc: {
|
||||
const auto mask = BallotMask(value);
|
||||
ir.SetVcc(value);
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
case IR::RegisterFile::Scc: ir.SetScc(value); return;
|
||||
default:
|
||||
WriteRawU32(operand, ir.Select(value, IR::U32(IR::Value(1u)), IR::U32(IR::Value(0u))));
|
||||
WriteRawU32(OffsetOperand(operand, 1), IR::U32(IR::Value(0u)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::WriteMask64(const IR::Operand& operand, IR::U1 value) {
|
||||
if (operand.kind != IR::OperandKind::Register || operand.reg.file != IR::RegisterFile::Scalar) {
|
||||
WriteMask(operand, value);
|
||||
return;
|
||||
}
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg.index);
|
||||
const auto mask = BallotMask(value);
|
||||
ir.SetThreadBitScalarReg(reg, value);
|
||||
ir.SetScalarReg(reg, mask[0]);
|
||||
if (IR::RegIndex(reg) + 1u < IR::NumScalarRegs) {
|
||||
ir.SetScalarReg(static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u), mask[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::WriteCompareResult(const IR::Operand& operand, IR::U1 value) {
|
||||
if (operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Scc) {
|
||||
WriteOperand(operand, value);
|
||||
return;
|
||||
}
|
||||
// VALU instructions only write lanes enabled by the incoming EXEC mask. This is
|
||||
// especially important for V_CMPX: replacing EXEC with the ungated comparison would
|
||||
// reactivate lanes disabled by an enclosing divergent region.
|
||||
WriteMask(operand, ir.LogicalAnd(ir.GetExec(), value));
|
||||
}
|
||||
|
||||
bool Translator::TranslateBlock(const IR::BasicBlock& source, std::string* error) {
|
||||
BufferAddressValues buffer_address_snapshot {};
|
||||
uint32_t buffer_address_pc = 0;
|
||||
bool has_buffer_address_snapshot = false;
|
||||
for (const auto& source_inst: source.instructions) {
|
||||
current_opcode = source_inst.op;
|
||||
current_pc = source_inst.pc;
|
||||
const bool grouped_buffer_load =
|
||||
IsBufferLoadOperation(source_inst.op) && source_inst.memory.component_count > 1u;
|
||||
if (grouped_buffer_load &&
|
||||
(!has_buffer_address_snapshot || buffer_address_pc != source_inst.pc ||
|
||||
source_inst.memory.component_index == 0u)) {
|
||||
buffer_address_snapshot = ReadBufferAddress(source_inst, 0);
|
||||
buffer_address_pc = source_inst.pc;
|
||||
has_buffer_address_snapshot = true;
|
||||
} else if (!grouped_buffer_load) {
|
||||
has_buffer_address_snapshot = false;
|
||||
}
|
||||
if (static_cast<size_t>(source_inst.op) >= static_cast<size_t>(IR::Opcode::Count)) {
|
||||
return Fail(error, "value IR input opcode is out of range");
|
||||
}
|
||||
if (TranslateInstruction(source_inst,
|
||||
grouped_buffer_load ? &buffer_address_snapshot : nullptr)) {
|
||||
continue;
|
||||
}
|
||||
return Fail(error, fmt::format("opcode {} at 0x{:08x} has no typed Value IR lowering",
|
||||
IR::OpcodeName(source_inst.op), source_inst.pc));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::AddBranchCondition(const IR::BasicBlock& source, IR::ValueBlockInfo& info,
|
||||
std::string* error) {
|
||||
if (source.terminator.goto_value >= 0) {
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
return Fail(error, fmt::format("block {} sets an invalid goto variable", source.id));
|
||||
}
|
||||
ir.SetGotoVariable(source.terminator.goto_variable,
|
||||
IR::U1(IR::Value(source.terminator.goto_value != 0)));
|
||||
}
|
||||
if (source.terminator.kind == CFG::TerminatorKind::IndirectBranch) {
|
||||
if (source.terminator.indirect_selector_code != UINT32_MAX) {
|
||||
info.indirect_target = ReadScalarCode(source.terminator.indirect_selector_code);
|
||||
} else if (source.terminator.indirect_pc_sgpr != UINT32_MAX) {
|
||||
info.indirect_target =
|
||||
ir.GetScalarReg(static_cast<IR::ScalarReg>(source.terminator.indirect_pc_sgpr));
|
||||
} else {
|
||||
return Fail(error, fmt::format("block {} has no indirect branch selector", source.id));
|
||||
}
|
||||
ir.Emit(IR::ValueOpcode::ReferenceU32, {info.indirect_target});
|
||||
return true;
|
||||
}
|
||||
if (source.terminator.kind != CFG::TerminatorKind::ConditionalBranch) {
|
||||
return true;
|
||||
}
|
||||
IR::U1 condition;
|
||||
const auto mask_non_zero = [&](IR::U1 value) {
|
||||
const auto mask = BallotMask(value);
|
||||
return ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u)));
|
||||
};
|
||||
switch (source.terminator.condition) {
|
||||
case CFG::BranchCondition::Always: condition = IR::U1(IR::Value(true)); break;
|
||||
case CFG::BranchCondition::SccZero: condition = ir.LogicalNot(ir.GetScc()); break;
|
||||
case CFG::BranchCondition::SccNonZero: condition = ir.GetScc(); break;
|
||||
case CFG::BranchCondition::VccZero:
|
||||
condition = ir.LogicalNot(mask_non_zero(ir.GetVcc()));
|
||||
break;
|
||||
case CFG::BranchCondition::VccNonZero: condition = mask_non_zero(ir.GetVcc()); break;
|
||||
case CFG::BranchCondition::ExecZero:
|
||||
condition = ir.LogicalNot(mask_non_zero(ir.GetExec()));
|
||||
break;
|
||||
case CFG::BranchCondition::ExecNonZero: condition = mask_non_zero(ir.GetExec()); break;
|
||||
case CFG::BranchCondition::GotoVariable:
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
return Fail(error,
|
||||
fmt::format("block {} reads an invalid goto variable", source.id));
|
||||
}
|
||||
condition = ir.GetGotoVariable(source.terminator.goto_variable);
|
||||
break;
|
||||
case CFG::BranchCondition::Unknown:
|
||||
return Fail(error, fmt::format("block {} has an unknown branch condition", source.id));
|
||||
}
|
||||
info.condition = condition;
|
||||
ir.Emit(IR::ValueOpcode::Reference, {condition});
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result,
|
||||
const ShaderVertexInputInfo* vertex_input_info,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::string* error) {
|
||||
result = {};
|
||||
result.dispatcher_fallback = source.dispatcher_fallback;
|
||||
const uint32_t wave_size = source.wave_size;
|
||||
const bool per_invocation_masks = source.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
||||
uint32_t vector_limit = 1u;
|
||||
const auto include_vector = [&](const IR::Operand& operand) {
|
||||
if (operand.kind == IR::OperandKind::Register &&
|
||||
operand.reg.file == IR::RegisterFile::Vector) {
|
||||
vector_limit =
|
||||
std::min(IR::NumVectorRegs, std::max(vector_limit, operand.reg.index + 1u));
|
||||
}
|
||||
};
|
||||
for (const auto& block: source.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
include_vector(inst.dst);
|
||||
include_vector(inst.dst2);
|
||||
for (uint32_t index = 0; index < inst.src_count; index++) {
|
||||
include_vector(inst.src[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
result.block_storage.reserve(source.blocks.size() + 1u);
|
||||
result.blocks.reserve(source.blocks.size() + 1u);
|
||||
result.block_info.reserve(source.blocks.size() + 1u);
|
||||
if (!source.blocks.empty()) {
|
||||
const auto max_id =
|
||||
std::ranges::max_element(source.blocks, {}, [](const IR::BasicBlock& block) {
|
||||
return block.id;
|
||||
})->id;
|
||||
if (max_id == UINT32_MAX) {
|
||||
return Detail::Fail(error, "cannot allocate a typed prologue block id");
|
||||
}
|
||||
CFG::Terminator terminator;
|
||||
terminator.kind = CFG::TerminatorKind::Branch;
|
||||
terminator.true_block = source.blocks.front().id;
|
||||
result.block_storage.push_back(std::make_unique<IR::Block>());
|
||||
result.blocks.push_back(result.block_storage.back().get());
|
||||
result.block_info.push_back({max_id + 1u, source.blocks.front().start_pc,
|
||||
source.blocks.front().start_pc, std::move(terminator)});
|
||||
}
|
||||
for (const auto& source_block: source.blocks) {
|
||||
result.block_storage.push_back(std::make_unique<IR::Block>());
|
||||
result.blocks.push_back(result.block_storage.back().get());
|
||||
result.block_info.push_back(
|
||||
{source_block.id, source_block.start_pc, source_block.end_pc, source_block.terminator});
|
||||
}
|
||||
for (size_t index = 0; index < source.blocks.size(); index++) {
|
||||
for (const auto successor: source.blocks[index].successors) {
|
||||
if (successor >= source.blocks.size()) {
|
||||
return Detail::Fail(error, "value IR block successor is out of range");
|
||||
}
|
||||
result.blocks[index + 1u]->AddBranch(result.blocks[successor + 1u]);
|
||||
}
|
||||
}
|
||||
if (!result.blocks.empty()) {
|
||||
result.blocks.front()->AddBranch(result.blocks[1]);
|
||||
IR::IREmitter prologue(result.blocks.front());
|
||||
const auto builtin = [&](IR::StageInputKind kind, uint32_t component = 0u) {
|
||||
return IR::U32(
|
||||
prologue.Emit(IR::ValueOpcode::GetBuiltin,
|
||||
{IR::Value(static_cast<uint32_t>(kind)), IR::Value(component)}));
|
||||
};
|
||||
prologue.Emit(IR::ValueOpcode::Prologue);
|
||||
const IR::U32 zero(IR::Value(0u));
|
||||
for (uint32_t index = 0; index < IR::NumScalarRegs; index++) {
|
||||
prologue.SetScalarReg(static_cast<IR::ScalarReg>(index), zero);
|
||||
}
|
||||
for (uint32_t index = 0; index < vector_limit; index++) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(index), zero);
|
||||
}
|
||||
for (uint32_t index = 0; index < source.user_data_count; index++) {
|
||||
const auto reg = static_cast<IR::ScalarReg>(source.user_data_base + index);
|
||||
if (IR::RegIndex(reg) >= IR::NumScalarRegs) {
|
||||
break;
|
||||
}
|
||||
prologue.SetScalarReg(reg, prologue.GetUserData(reg));
|
||||
}
|
||||
prologue.SetScc(IR::U1(IR::Value(false)));
|
||||
prologue.SetExec(IR::U1(IR::Value(true)));
|
||||
prologue.SetExecLo(IR::U32(IR::Value(0xffffffffu)));
|
||||
prologue.SetExecHi(IR::U32(IR::Value(source.wave_size > 32u ? 0xffffffffu : 0u)));
|
||||
prologue.SetVcc(IR::U1(IR::Value(false)));
|
||||
prologue.SetVccLo(IR::U32(IR::Value(0u)));
|
||||
prologue.SetVccHi(IR::U32(IR::Value(0u)));
|
||||
prologue.SetM0(IR::U32(IR::Value(0u)));
|
||||
if (source.stage == ShaderType::Compute && compute_input_info != nullptr) {
|
||||
const auto* cs = compute_input_info;
|
||||
const auto thread_ids =
|
||||
cs->thread_ids_num > 0 ? std::min<uint32_t>(cs->thread_ids_num, 3u) : 0u;
|
||||
for (uint32_t index = 0; index < thread_ids; index++) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(index),
|
||||
builtin(IR::StageInputKind::LocalInvocationId, index));
|
||||
}
|
||||
uint32_t reg_offset = 0;
|
||||
for (uint32_t index = 0; index < 3u; index++) {
|
||||
if (cs->group_id[index]) {
|
||||
prologue.SetScalarReg(
|
||||
static_cast<IR::ScalarReg>(cs->workgroup_register + reg_offset++),
|
||||
builtin(IR::StageInputKind::WorkgroupId, index));
|
||||
}
|
||||
}
|
||||
if (cs->tg_size_en) {
|
||||
const auto wave_size = cs->wave_size != 0u ? cs->wave_size : 64u;
|
||||
const auto total_threads = std::max(cs->threads_num[0], 1u) *
|
||||
std::max(cs->threads_num[1], 1u) *
|
||||
std::max(cs->threads_num[2], 1u);
|
||||
const auto waves = std::min((total_threads + wave_size - 1u) / wave_size, 0x3fu);
|
||||
const auto local_index = builtin(IR::StageInputKind::LocalInvocationIndex);
|
||||
const auto wave_id = IR::U32(
|
||||
prologue.Emit(IR::ValueOpcode::UDiv32, {local_index, IR::Value(wave_size)}));
|
||||
const auto wave_bits = prologue.ShiftLeftLogical(wave_id, IR::U32(IR::Value(20u)));
|
||||
const auto first_bit =
|
||||
prologue.Select(prologue.IEqual(wave_id, IR::U32(IR::Value(0u))),
|
||||
IR::U32(IR::Value(0x80000000u)), IR::U32(IR::Value(0u)));
|
||||
prologue.SetScalarReg(
|
||||
static_cast<IR::ScalarReg>(cs->workgroup_register + reg_offset),
|
||||
prologue.BitwiseOr(prologue.BitwiseOr(wave_bits, IR::U32(IR::Value(waves))),
|
||||
first_bit));
|
||||
}
|
||||
} else if (source.stage == ShaderType::Pixel && pixel_input_info != nullptr) {
|
||||
const auto* ps = pixel_input_info;
|
||||
uint32_t reg = ps->ps_system_input_base;
|
||||
if (ps->ps_pos_x) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++),
|
||||
builtin(IR::StageInputKind::FragCoord, 0));
|
||||
}
|
||||
if (ps->ps_pos_y) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++),
|
||||
builtin(IR::StageInputKind::FragCoord, 1));
|
||||
}
|
||||
if (ps->ps_pos_z) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++),
|
||||
builtin(IR::StageInputKind::FragCoord, 2));
|
||||
}
|
||||
if (ps->ps_pos_w) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++),
|
||||
builtin(IR::StageInputKind::FragCoord, 3));
|
||||
}
|
||||
if (ps->ps_front_face) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg),
|
||||
builtin(IR::StageInputKind::FrontFacing));
|
||||
}
|
||||
} else if (source.stage == ShaderType::Vertex && vertex_input_info != nullptr) {
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||
builtin(IR::StageInputKind::VertexIndex));
|
||||
prologue.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||
builtin(IR::StageInputKind::InstanceIndex));
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < source.blocks.size(); index++) {
|
||||
Detail::Translator translator(result, result.blocks[index + 1u], vector_limit, wave_size,
|
||||
per_invocation_masks);
|
||||
if (!translator.TranslateBlock(source.blocks[index], error) ||
|
||||
!translator.AddBranchCondition(source.blocks[index], result.block_info[index + 1u],
|
||||
error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return IR::ValidateValueProgram(result, false, error);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ValueProgram.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result,
|
||||
const ShaderVertexInputInfo* vertex_input_info,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translate.h"
|
||||
#include "graphics/shader/recompiler/ir/IREmitter.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail {
|
||||
|
||||
struct BufferAddressValues {
|
||||
IR::U32 index;
|
||||
IR::U32 offset;
|
||||
IR::U32 soffset;
|
||||
};
|
||||
|
||||
bool Fail(std::string* error, const std::string& message);
|
||||
|
||||
class Translator {
|
||||
public:
|
||||
Translator(IR::ValueProgram& program, IR::Block* block, uint32_t vector_limit,
|
||||
uint32_t wave_size, bool per_invocation_masks)
|
||||
: value_program(program), ir(block), current_vector_limit(vector_limit),
|
||||
current_wave_size(wave_size), current_per_invocation_masks(per_invocation_masks) {}
|
||||
|
||||
bool TranslateBlock(const IR::BasicBlock& source, std::string* error);
|
||||
bool AddBranchCondition(const IR::BasicBlock& source, IR::ValueBlockInfo& info,
|
||||
std::string* error);
|
||||
|
||||
private:
|
||||
IR::Operand OffsetOperand(const IR::Operand& operand, uint32_t offset);
|
||||
IR::Operand PlainOperand(const IR::Operand& operand);
|
||||
IR::U32 ReadPcRelativeU32(const IR::Operand& operand);
|
||||
std::array<IR::U32, 2> BallotMask(IR::U1 value);
|
||||
IR::U32 ReadRawU32(const IR::Operand& operand);
|
||||
IR::U32 ReadScalarCode(uint32_t code);
|
||||
IR::U32 ApplyBitSourceModifiers(const IR::Operand& operand, IR::U32 value);
|
||||
IR::Value ReadOperand(const IR::Operand& operand, IR::Type type);
|
||||
IR::U1 ThreadBit(IR::U32 low, IR::U32 high);
|
||||
void WriteRawU32(const IR::Operand& operand, IR::U32 value);
|
||||
IR::U64 ExpandWholeQuadMask(IR::U64 value);
|
||||
IR::F32 ApplyF32ResultModifiers(const IR::Operand& operand, IR::F32 value);
|
||||
void WriteOperand(const IR::Operand& operand, IR::Value value);
|
||||
IR::U32 PackHalf2x16(IR::F32 low, IR::F32 high);
|
||||
void WriteF16(const IR::Operand& operand, IR::F32 value);
|
||||
void WriteU16(const IR::Operand& operand, IR::U32 value);
|
||||
IR::U32 ReadU32(const IR::Operand& operand);
|
||||
IR::U64 ReadU64(const IR::Operand& operand);
|
||||
IR::F32 ReadF16LaneAsF32(const IR::Operand& operand, bool high_lane);
|
||||
IR::F32 ReadF16AsF32(const IR::Operand& operand);
|
||||
IR::F32 ReadMixF32(const IR::Operand& operand);
|
||||
IR::U32 ReadU16LaneRaw(const IR::Operand& operand, bool high_lane);
|
||||
IR::U32 ReadU16LaneAsU32(const IR::Operand& operand, bool high_lane, bool sign_extend);
|
||||
IR::U32 ReadU16AsU32(const IR::Operand& operand, bool sign_extend);
|
||||
IR::U32 ReadF16LaneBits(const IR::Operand& operand, bool high_lane);
|
||||
std::array<IR::U32, 2> UnpackU64(IR::U64 value);
|
||||
IR::U1 ReadCondition(const IR::Operand& operand);
|
||||
IR::U32 ConditionBit(const IR::Operand& operand);
|
||||
IR::U1 ReadMask(const IR::Operand& operand);
|
||||
void WriteMask(const IR::Operand& operand, IR::U1 value);
|
||||
void WriteMask64(const IR::Operand& operand, IR::U1 value);
|
||||
void WriteCompareResult(const IR::Operand& operand, IR::U1 value);
|
||||
|
||||
IR::MemoryFlags AddMemoryInfo(const IR::Instruction& inst);
|
||||
IR::ExportFlags AddExportInfo(const IR::Instruction& inst);
|
||||
struct AddressOperands {
|
||||
IR::Value resource;
|
||||
IR::Value low;
|
||||
IR::Value high;
|
||||
};
|
||||
AddressOperands ReadAddressOperands(const IR::Instruction& inst, uint32_t first_source);
|
||||
IR::U32 GetResourceDword(uint32_t index, uint32_t dword);
|
||||
IR::Value GetBufferResource(const IR::MemoryInfo& memory);
|
||||
IR::Value GetAddressResource(IR::Value low, IR::Value high);
|
||||
IR::Value GetScalarAddressResource(uint32_t base);
|
||||
IR::Value GetImageResource(const IR::MemoryInfo& memory);
|
||||
IR::Value GetSamplerResource(const IR::MemoryInfo& memory);
|
||||
IR::Value GetSharedResource(IR::ResourceKind kind);
|
||||
IR::Value MakeImageAddress(const IR::Instruction& inst, const IR::Operand& base);
|
||||
IR::Value ConstructU32x4(const IR::Operand& base, uint32_t count);
|
||||
void WriteImageComponents(const IR::Operand& dst, IR::Value value, uint32_t dmask,
|
||||
uint32_t component_limit);
|
||||
IR::ValueOpcode ImageAtomicOpcode(IR::Opcode opcode);
|
||||
BufferAddressValues ReadBufferAddress(const IR::Instruction& inst, uint32_t source_offset);
|
||||
IR::U32 WidenSubdword(IR::Value value, uint32_t bits, bool sign);
|
||||
IR::Value NarrowSubdword(IR::U32 value, uint32_t bits);
|
||||
IR::ValueOpcode BufferAtomicOpcode(IR::Opcode opcode);
|
||||
IR::ValueOpcode SharedAtomicOpcode(IR::Opcode opcode, bool gds);
|
||||
bool IsBufferLoadOperation(IR::Opcode opcode);
|
||||
bool TranslateScalarMemory(const IR::Instruction& inst);
|
||||
bool TranslateBufferLoad(const IR::Instruction& inst,
|
||||
const BufferAddressValues* address_snapshot);
|
||||
bool TranslateBufferStore(const IR::Instruction& inst);
|
||||
bool TranslateAtomicMemory(const IR::Instruction& inst);
|
||||
bool TranslateFlatLoad(const IR::Instruction& inst);
|
||||
bool TranslateFlatStore(const IR::Instruction& inst);
|
||||
bool TranslateImageMemory(const IR::Instruction& inst);
|
||||
bool TranslateSharedMemory(const IR::Instruction& inst);
|
||||
|
||||
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
||||
IR::U32 ConvertF32ToU32Saturated(IR::F32 value, float upper_bound, float safe_upper,
|
||||
uint32_t high_result);
|
||||
IR::U32 ConvertF32ToI32Saturated(IR::F32 value, float lower_bound, float upper_bound,
|
||||
float safe_upper, uint32_t lower_result,
|
||||
uint32_t upper_result);
|
||||
IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high);
|
||||
|
||||
bool TranslateInstruction(const IR::Instruction& inst,
|
||||
const BufferAddressValues* address_snapshot);
|
||||
bool TranslateStateOperation(const IR::Instruction& inst);
|
||||
bool TranslateControlOperation(const IR::Instruction& inst);
|
||||
bool TranslateMove(const IR::Instruction& inst);
|
||||
bool TranslateLaneOperation(const IR::Instruction& inst);
|
||||
bool TranslateAttributeOperation(const IR::Instruction& inst);
|
||||
bool TranslateMemoryOperation(const IR::Instruction& inst,
|
||||
const BufferAddressValues* address_snapshot = nullptr);
|
||||
bool TranslateIntegerCompare(const IR::Instruction& inst);
|
||||
bool TranslateInteger16Compare(const IR::Instruction& inst);
|
||||
bool TranslateFloatCompare(const IR::Instruction& inst);
|
||||
bool TranslateConversion(const IR::Instruction& inst);
|
||||
bool TranslateInteger16Operation(const IR::Instruction& inst);
|
||||
bool TranslatePackedInteger16(const IR::Instruction& inst);
|
||||
bool TranslatePackedFloat16(const IR::Instruction& inst);
|
||||
bool TranslateFloat16Operation(const IR::Instruction& inst);
|
||||
bool TranslateFloatOperation(const IR::Instruction& inst);
|
||||
bool TranslatePerInvocationU64Mask(const IR::Instruction& inst);
|
||||
bool TranslateU64MaskOperation(const IR::Instruction& inst);
|
||||
bool TranslateSimpleInteger(const IR::Instruction& inst);
|
||||
bool TranslateComposedInteger(const IR::Instruction& inst);
|
||||
bool TranslateExtendedInteger(const IR::Instruction& inst);
|
||||
|
||||
IR::ValueProgram& value_program;
|
||||
IR::IREmitter ir;
|
||||
IR::Opcode current_opcode = IR::Opcode::ControlNop;
|
||||
uint32_t current_pc = 0;
|
||||
uint32_t current_vector_limit = 1;
|
||||
uint32_t current_wave_size = 64;
|
||||
bool current_per_invocation_masks = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend::Detail
|
||||
Reference in New Issue
Block a user