// Copyright 2018 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #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 Tegra { namespace Shader { namespace Decompiler { constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; class DecompileFail : public std::runtime_error { public: 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 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 GetSubroutines() { return std::move(subroutines); } private: const ProgramCode& program_code; std::set subroutines; std::map, 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; } /// Scans a range of code for labels and determines the exit method. ExitMethod Scan(u32 begin, u32 end, std::set& 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(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& 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::set& subroutines; const ProgramCode& program_code; const u32 main_offset; ShaderWriter shader; void Generate() {} }; boost::optional 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 Tegra