summaryrefslogtreecommitdiff
path: root/src/video_core/shader/decode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/shader/decode.cpp')
-rw-r--r--src/video_core/shader/decode.cpp206
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
16namespace VideoCommon::Shader {
17
18using Tegra::Shader::Instruction;
19using Tegra::Shader::OpCode;
20
21namespace {
22
23/// Merges exit method of two parallel branches.
24constexpr 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 */
41constexpr 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
50void 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
74ExitMethod 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
124BasicBlock 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
132u32 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