diff options
| author | 2019-01-25 23:42:14 -0500 | |
|---|---|---|
| committer | 2019-01-25 23:42:14 -0500 | |
| commit | 1f4ca1e841cd0b0427218d787efe10a3fa62df33 (patch) | |
| tree | 00cc1743c6a6ba593e3b56897b13c2272a71d779 /src/video_core/shader/decode.cpp | |
| parent | Merge pull request #2054 from bunnei/scope-context-refactor (diff) | |
| parent | shader_ir: Fixup clang build (diff) | |
| download | yuzu-1f4ca1e841cd0b0427218d787efe10a3fa62df33.tar.gz yuzu-1f4ca1e841cd0b0427218d787efe10a3fa62df33.tar.xz yuzu-1f4ca1e841cd0b0427218d787efe10a3fa62df33.zip | |
Merge pull request #1927 from ReinUsesLisp/shader-ir
video_core: Replace gl_shader_decompiler with an IR based decompiler
Diffstat (limited to 'src/video_core/shader/decode.cpp')
| -rw-r--r-- | src/video_core/shader/decode.cpp | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp new file mode 100644 index 000000000..6fdcac784 --- /dev/null +++ b/src/video_core/shader/decode.cpp | |||
| @@ -0,0 +1,206 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <cstring> | ||
| 6 | #include <set> | ||
| 7 | |||
| 8 | #include <fmt/format.h> | ||
| 9 | |||
| 10 | #include "common/assert.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "video_core/engines/shader_bytecode.h" | ||
| 13 | #include "video_core/engines/shader_header.h" | ||
| 14 | #include "video_core/shader/shader_ir.h" | ||
| 15 | |||
| 16 | namespace VideoCommon::Shader { | ||
| 17 | |||
| 18 | using Tegra::Shader::Instruction; | ||
| 19 | using Tegra::Shader::OpCode; | ||
| 20 | |||
| 21 | namespace { | ||
| 22 | |||
| 23 | /// Merges exit method of two parallel branches. | ||
| 24 | constexpr ExitMethod ParallelExit(ExitMethod a, ExitMethod b) { | ||
| 25 | if (a == ExitMethod::Undetermined) { | ||
| 26 | return b; | ||
| 27 | } | ||
| 28 | if (b == ExitMethod::Undetermined) { | ||
| 29 | return a; | ||
| 30 | } | ||
| 31 | if (a == b) { | ||
| 32 | return a; | ||
| 33 | } | ||
| 34 | return ExitMethod::Conditional; | ||
| 35 | } | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Returns whether the instruction at the specified offset is a 'sched' instruction. | ||
| 39 | * Sched instructions always appear before a sequence of 3 instructions. | ||
| 40 | */ | ||
| 41 | constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) { | ||
| 42 | constexpr u32 SchedPeriod = 4; | ||
| 43 | u32 absolute_offset = offset - main_offset; | ||
| 44 | |||
| 45 | return (absolute_offset % SchedPeriod) == 0; | ||
| 46 | } | ||
| 47 | |||
| 48 | } // namespace | ||
| 49 | |||
| 50 | void ShaderIR::Decode() { | ||
| 51 | std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); | ||
| 52 | |||
| 53 | std::set<u32> labels; | ||
| 54 | const ExitMethod exit_method = Scan(main_offset, MAX_PROGRAM_LENGTH, labels); | ||
| 55 | if (exit_method != ExitMethod::AlwaysEnd) { | ||
| 56 | UNREACHABLE_MSG("Program does not always end"); | ||
| 57 | } | ||
| 58 | |||
| 59 | if (labels.empty()) { | ||
| 60 | basic_blocks.insert({main_offset, DecodeRange(main_offset, MAX_PROGRAM_LENGTH)}); | ||
| 61 | return; | ||
| 62 | } | ||
| 63 | |||
| 64 | labels.insert(main_offset); | ||
| 65 | |||
| 66 | for (const u32 label : labels) { | ||
| 67 | const auto next_it = labels.lower_bound(label + 1); | ||
| 68 | const u32 next_label = next_it == labels.end() ? MAX_PROGRAM_LENGTH : *next_it; | ||
| 69 | |||
| 70 | basic_blocks.insert({label, DecodeRange(label, next_label)}); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | ExitMethod ShaderIR::Scan(u32 begin, u32 end, std::set<u32>& labels) { | ||
| 75 | const auto [iter, inserted] = | ||
| 76 | exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined); | ||
| 77 | ExitMethod& exit_method = iter->second; | ||
| 78 | if (!inserted) | ||
| 79 | return exit_method; | ||
| 80 | |||
| 81 | for (u32 offset = begin; offset != end && offset != MAX_PROGRAM_LENGTH; ++offset) { | ||
| 82 | coverage_begin = std::min(coverage_begin, offset); | ||
| 83 | coverage_end = std::max(coverage_end, offset + 1); | ||
| 84 | |||
| 85 | const Instruction instr = {program_code[offset]}; | ||
| 86 | const auto opcode = OpCode::Decode(instr); | ||
| 87 | if (!opcode) | ||
| 88 | continue; | ||
| 89 | switch (opcode->get().GetId()) { | ||
| 90 | case OpCode::Id::EXIT: { | ||
| 91 | // The EXIT instruction can be predicated, which means that the shader can conditionally | ||
| 92 | // end on this instruction. We have to consider the case where the condition is not met | ||
| 93 | // and check the exit method of that other basic block. | ||
| 94 | using Tegra::Shader::Pred; | ||
| 95 | if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) { | ||
| 96 | return exit_method = ExitMethod::AlwaysEnd; | ||
| 97 | } else { | ||
| 98 | const ExitMethod not_met = Scan(offset + 1, end, labels); | ||
| 99 | return exit_method = ParallelExit(ExitMethod::AlwaysEnd, not_met); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | case OpCode::Id::BRA: { | ||
| 103 | const u32 target = offset + instr.bra.GetBranchTarget(); | ||
| 104 | labels.insert(target); | ||
| 105 | const ExitMethod no_jmp = Scan(offset + 1, end, labels); | ||
| 106 | const ExitMethod jmp = Scan(target, end, labels); | ||
| 107 | return exit_method = ParallelExit(no_jmp, jmp); | ||
| 108 | } | ||
| 109 | case OpCode::Id::SSY: | ||
| 110 | case OpCode::Id::PBK: { | ||
| 111 | // The SSY and PBK use a similar encoding as the BRA instruction. | ||
| 112 | UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, | ||
| 113 | "Constant buffer branching is not supported"); | ||
| 114 | const u32 target = offset + instr.bra.GetBranchTarget(); | ||
| 115 | labels.insert(target); | ||
| 116 | // Continue scanning for an exit method. | ||
| 117 | break; | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | return exit_method = ExitMethod::AlwaysReturn; | ||
| 122 | } | ||
| 123 | |||
| 124 | BasicBlock ShaderIR::DecodeRange(u32 begin, u32 end) { | ||
| 125 | BasicBlock basic_block; | ||
| 126 | for (u32 pc = begin; pc < (begin > end ? MAX_PROGRAM_LENGTH : end);) { | ||
| 127 | pc = DecodeInstr(basic_block, pc); | ||
| 128 | } | ||
| 129 | return std::move(basic_block); | ||
| 130 | } | ||
| 131 | |||
| 132 | u32 ShaderIR::DecodeInstr(BasicBlock& bb, u32 pc) { | ||
| 133 | // Ignore sched instructions when generating code. | ||
| 134 | if (IsSchedInstruction(pc, main_offset)) { | ||
| 135 | return pc + 1; | ||
| 136 | } | ||
| 137 | |||
| 138 | const Instruction instr = {program_code[pc]}; | ||
| 139 | const auto opcode = OpCode::Decode(instr); | ||
| 140 | |||
| 141 | // Decoding failure | ||
| 142 | if (!opcode) { | ||
| 143 | UNIMPLEMENTED_MSG("Unhandled instruction: {0:x}", instr.value); | ||
| 144 | return pc + 1; | ||
| 145 | } | ||
| 146 | |||
| 147 | bb.push_back( | ||
| 148 | Comment(fmt::format("{}: {} (0x{:016x})", pc, opcode->get().GetName(), instr.value))); | ||
| 149 | |||
| 150 | using Tegra::Shader::Pred; | ||
| 151 | UNIMPLEMENTED_IF_MSG(instr.pred.full_pred == Pred::NeverExecute, | ||
| 152 | "NeverExecute predicate not implemented"); | ||
| 153 | |||
| 154 | static const std::map<OpCode::Type, u32 (ShaderIR::*)(BasicBlock&, const BasicBlock&, u32)> | ||
| 155 | decoders = { | ||
| 156 | {OpCode::Type::Arithmetic, &ShaderIR::DecodeArithmetic}, | ||
| 157 | {OpCode::Type::ArithmeticImmediate, &ShaderIR::DecodeArithmeticImmediate}, | ||
| 158 | {OpCode::Type::Bfe, &ShaderIR::DecodeBfe}, | ||
| 159 | {OpCode::Type::Bfi, &ShaderIR::DecodeBfi}, | ||
| 160 | {OpCode::Type::Shift, &ShaderIR::DecodeShift}, | ||
| 161 | {OpCode::Type::ArithmeticInteger, &ShaderIR::DecodeArithmeticInteger}, | ||
| 162 | {OpCode::Type::ArithmeticIntegerImmediate, &ShaderIR::DecodeArithmeticIntegerImmediate}, | ||
| 163 | {OpCode::Type::ArithmeticHalf, &ShaderIR::DecodeArithmeticHalf}, | ||
| 164 | {OpCode::Type::ArithmeticHalfImmediate, &ShaderIR::DecodeArithmeticHalfImmediate}, | ||
| 165 | {OpCode::Type::Ffma, &ShaderIR::DecodeFfma}, | ||
| 166 | {OpCode::Type::Hfma2, &ShaderIR::DecodeHfma2}, | ||
| 167 | {OpCode::Type::Conversion, &ShaderIR::DecodeConversion}, | ||
| 168 | {OpCode::Type::Memory, &ShaderIR::DecodeMemory}, | ||
| 169 | {OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate}, | ||
| 170 | {OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate}, | ||
| 171 | {OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate}, | ||
| 172 | {OpCode::Type::PredicateSetRegister, &ShaderIR::DecodePredicateSetRegister}, | ||
| 173 | {OpCode::Type::PredicateSetPredicate, &ShaderIR::DecodePredicateSetPredicate}, | ||
| 174 | {OpCode::Type::RegisterSetPredicate, &ShaderIR::DecodeRegisterSetPredicate}, | ||
| 175 | {OpCode::Type::FloatSet, &ShaderIR::DecodeFloatSet}, | ||
| 176 | {OpCode::Type::IntegerSet, &ShaderIR::DecodeIntegerSet}, | ||
| 177 | {OpCode::Type::HalfSet, &ShaderIR::DecodeHalfSet}, | ||
| 178 | {OpCode::Type::Video, &ShaderIR::DecodeVideo}, | ||
| 179 | {OpCode::Type::Xmad, &ShaderIR::DecodeXmad}, | ||
| 180 | }; | ||
| 181 | |||
| 182 | std::vector<Node> tmp_block; | ||
| 183 | if (const auto decoder = decoders.find(opcode->get().GetType()); decoder != decoders.end()) { | ||
| 184 | pc = (this->*decoder->second)(tmp_block, bb, pc); | ||
| 185 | } else { | ||
| 186 | pc = DecodeOther(tmp_block, bb, pc); | ||
| 187 | } | ||
| 188 | |||
| 189 | // Some instructions (like SSY) don't have a predicate field, they are always unconditionally | ||
| 190 | // executed. | ||
| 191 | const bool can_be_predicated = OpCode::IsPredicatedInstruction(opcode->get().GetId()); | ||
| 192 | const auto pred_index = static_cast<u32>(instr.pred.pred_index); | ||
| 193 | |||
| 194 | if (can_be_predicated && pred_index != static_cast<u32>(Pred::UnusedIndex)) { | ||
| 195 | bb.push_back( | ||
| 196 | Conditional(GetPredicate(pred_index, instr.negate_pred != 0), std::move(tmp_block))); | ||
| 197 | } else { | ||
| 198 | for (auto& node : tmp_block) { | ||
| 199 | bb.push_back(std::move(node)); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | return pc + 1; | ||
| 204 | } | ||
| 205 | |||
| 206 | } // namespace VideoCommon::Shader \ No newline at end of file | ||