|
|
@@ -2,57 +2,158 @@
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
+#include <map>
|
|
|
+#include <set>
|
|
|
#include <string>
|
|
|
-#include <queue>
|
|
|
#include "common/assert.h"
|
|
|
#include "common/common_types.h"
|
|
|
+#include "video_core/engines/shader_bytecode.h"
|
|
|
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
|
|
|
|
|
-namespace Maxwell3D {
|
|
|
+namespace Tegra {
|
|
|
namespace Shader {
|
|
|
namespace Decompiler {
|
|
|
|
|
|
constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
|
|
|
|
|
|
-class Impl {
|
|
|
+class DecompileFail : public std::runtime_error {
|
|
|
public:
|
|
|
- Impl(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
|
|
|
- const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, u32 main_offset,
|
|
|
- const std::function<std::string(u32)>& inputreg_getter,
|
|
|
- const std::function<std::string(u32)>& outputreg_getter, bool sanitize_mul,
|
|
|
- const std::string& emit_cb, const std::string& setemit_cb)
|
|
|
- : program_code(program_code), swizzle_data(swizzle_data), main_offset(main_offset),
|
|
|
- inputreg_getter(inputreg_getter), outputreg_getter(outputreg_getter),
|
|
|
- sanitize_mul(sanitize_mul), emit_cb(emit_cb), setemit_cb(setemit_cb) {}
|
|
|
+ using std::runtime_error::runtime_error;
|
|
|
+};
|
|
|
+
|
|
|
+/// Describes the behaviour of code path of a given entry point and a return point.
|
|
|
+enum class ExitMethod {
|
|
|
+ Undetermined, ///< Internal value. Only occur when analyzing JMP loop.
|
|
|
+ AlwaysReturn, ///< All code paths reach the return point.
|
|
|
+ Conditional, ///< Code path reaches the return point or an END instruction conditionally.
|
|
|
+ AlwaysEnd, ///< All code paths reach a END instruction.
|
|
|
+};
|
|
|
+
|
|
|
+/// A subroutine is a range of code refereced by a CALL, IF or LOOP instruction.
|
|
|
+struct Subroutine {
|
|
|
+ /// Generates a name suitable for GLSL source code.
|
|
|
+ std::string GetName() const {
|
|
|
+ return "sub_" + std::to_string(begin) + "_" + std::to_string(end);
|
|
|
+ }
|
|
|
+
|
|
|
+ u32 begin; ///< Entry point of the subroutine.
|
|
|
+ u32 end; ///< Return point of the subroutine.
|
|
|
+ ExitMethod exit_method; ///< Exit method of the subroutine.
|
|
|
+ std::set<u32> labels; ///< Addresses refereced by JMP instructions.
|
|
|
+
|
|
|
+ bool operator<(const Subroutine& rhs) const {
|
|
|
+ return std::tie(begin, end) < std::tie(rhs.begin, rhs.end);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/// Analyzes shader code and produces a set of subroutines.
|
|
|
+class ControlFlowAnalyzer {
|
|
|
+public:
|
|
|
+ ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset)
|
|
|
+ : program_code(program_code) {
|
|
|
+
|
|
|
+ // Recursively finds all subroutines.
|
|
|
+ const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END);
|
|
|
+ if (program_main.exit_method != ExitMethod::AlwaysEnd)
|
|
|
+ throw DecompileFail("Program does not always end");
|
|
|
+ }
|
|
|
+
|
|
|
+ std::set<Subroutine> GetSubroutines() {
|
|
|
+ return std::move(subroutines);
|
|
|
+ }
|
|
|
+
|
|
|
+private:
|
|
|
+ const ProgramCode& program_code;
|
|
|
+ std::set<Subroutine> subroutines;
|
|
|
+ std::map<std::pair<u32, u32>, ExitMethod> exit_method_map;
|
|
|
+
|
|
|
+ /// Adds and analyzes a new subroutine if it is not added yet.
|
|
|
+ const Subroutine& AddSubroutine(u32 begin, u32 end) {
|
|
|
+ auto iter = subroutines.find(Subroutine{begin, end});
|
|
|
+ if (iter != subroutines.end())
|
|
|
+ return *iter;
|
|
|
+
|
|
|
+ Subroutine subroutine{begin, end};
|
|
|
+ subroutine.exit_method = Scan(begin, end, subroutine.labels);
|
|
|
+ if (subroutine.exit_method == ExitMethod::Undetermined)
|
|
|
+ throw DecompileFail("Recursive function detected");
|
|
|
+ return *subroutines.insert(std::move(subroutine)).first;
|
|
|
+ }
|
|
|
|
|
|
- std::string Decompile() {
|
|
|
- UNREACHABLE();
|
|
|
- return {};
|
|
|
+ /// Scans a range of code for labels and determines the exit method.
|
|
|
+ ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) {
|
|
|
+ auto [iter, inserted] =
|
|
|
+ exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined);
|
|
|
+ ExitMethod& exit_method = iter->second;
|
|
|
+ if (!inserted)
|
|
|
+ return exit_method;
|
|
|
+
|
|
|
+ for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
|
|
|
+ const Instruction instr = {program_code[offset]};
|
|
|
+ switch (instr.opcode.Value().EffectiveOpCode()) {
|
|
|
+ case OpCode::Id::EXIT: {
|
|
|
+ return exit_method = ExitMethod::AlwaysEnd;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return exit_method = ExitMethod::AlwaysReturn;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+class ShaderWriter {
|
|
|
+public:
|
|
|
+ void AddLine(const std::string& text) {
|
|
|
+ DEBUG_ASSERT(scope >= 0);
|
|
|
+ if (!text.empty()) {
|
|
|
+ shader_source += std::string(static_cast<size_t>(scope) * 4, ' ');
|
|
|
+ }
|
|
|
+ shader_source += text + '\n';
|
|
|
+ }
|
|
|
+
|
|
|
+ std::string GetResult() {
|
|
|
+ return std::move(shader_source);
|
|
|
+ }
|
|
|
+
|
|
|
+ int scope = 0;
|
|
|
+
|
|
|
+private:
|
|
|
+ std::string shader_source;
|
|
|
+};
|
|
|
+
|
|
|
+class GLSLGenerator {
|
|
|
+public:
|
|
|
+ GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code,
|
|
|
+ u32 main_offset)
|
|
|
+ : subroutines(subroutines), program_code(program_code), main_offset(main_offset) {
|
|
|
+
|
|
|
+ Generate();
|
|
|
+ }
|
|
|
+
|
|
|
+ std::string GetShaderCode() {
|
|
|
+ return shader.GetResult();
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
- const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code;
|
|
|
- const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data;
|
|
|
- u32 main_offset;
|
|
|
- const std::function<std::string(u32)>& inputreg_getter;
|
|
|
- const std::function<std::string(u32)>& outputreg_getter;
|
|
|
- bool sanitize_mul;
|
|
|
- const std::string& emit_cb;
|
|
|
- const std::string& setemit_cb;
|
|
|
+ const std::set<Subroutine>& subroutines;
|
|
|
+ const ProgramCode& program_code;
|
|
|
+ const u32 main_offset;
|
|
|
+
|
|
|
+ ShaderWriter shader;
|
|
|
+
|
|
|
+ void Generate() {}
|
|
|
};
|
|
|
|
|
|
-std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
|
|
|
- const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data,
|
|
|
- u32 main_offset,
|
|
|
- const std::function<std::string(u32)>& inputreg_getter,
|
|
|
- const std::function<std::string(u32)>& outputreg_getter,
|
|
|
- bool sanitize_mul, const std::string& emit_cb,
|
|
|
- const std::string& setemit_cb) {
|
|
|
- Impl impl(program_code, swizzle_data, main_offset, inputreg_getter, outputreg_getter,
|
|
|
- sanitize_mul, emit_cb, setemit_cb);
|
|
|
- return impl.Decompile();
|
|
|
+boost::optional<std::string> DecompileProgram(const ProgramCode& program_code, u32 main_offset) {
|
|
|
+ try {
|
|
|
+ auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines();
|
|
|
+ GLSLGenerator generator(subroutines, program_code, main_offset);
|
|
|
+ return generator.GetShaderCode();
|
|
|
+ } catch (const DecompileFail& exception) {
|
|
|
+ LOG_ERROR(HW_GPU, "Shader decompilation failed: %s", exception.what());
|
|
|
+ }
|
|
|
+ return boost::none;
|
|
|
}
|
|
|
|
|
|
} // namespace Decompiler
|
|
|
} // namespace Shader
|
|
|
-} // namespace Maxwell3D
|
|
|
+} // namespace Tegra
|