From 38fc995f6cc2c2af29abc976ddb45b72873b2cc4 Mon Sep 17 00:00:00 2001 From: Fernando Sahmkow Date: Sat, 29 Jun 2019 01:44:07 -0400 Subject: [PATCH] gl_shader_decompiler: Implement AST decompiling --- .../renderer_opengl/gl_shader_decompiler.cpp | 271 ++++++++++++++++-- src/video_core/shader/ast.cpp | 10 +- src/video_core/shader/ast.h | 18 +- src/video_core/shader/control_flow.cpp | 2 +- src/video_core/shader/control_flow.h | 2 +- src/video_core/shader/decode.cpp | 70 ++++- src/video_core/shader/decode/other.cpp | 8 +- src/video_core/shader/expr.cpp | 7 + src/video_core/shader/expr.h | 2 + src/video_core/shader/shader_ir.cpp | 6 +- src/video_core/shader/shader_ir.h | 25 +- 11 files changed, 358 insertions(+), 63 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 8fa9e65340..2955c6abf1 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -20,6 +20,7 @@ #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/shader/node.h" +#include "video_core/shader/ast.h" #include "video_core/shader/shader_ir.h" namespace OpenGL::GLShader { @@ -334,43 +335,26 @@ constexpr bool IsVertexShader(ProgramType stage) { return stage == ProgramType::VertexA || stage == ProgramType::VertexB; } +class ASTDecompiler; +class ExprDecompiler; + class GLSLDecompiler final { public: explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage, std::string suffix) : device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {} - void Decompile() { - DeclareVertex(); - DeclareGeometry(); - DeclareRegisters(); - DeclarePredicates(); - DeclareLocalMemory(); - DeclareSharedMemory(); - DeclareInternalFlags(); - DeclareInputAttributes(); - DeclareOutputAttributes(); - DeclareConstantBuffers(); - DeclareGlobalMemory(); - DeclareSamplers(); - DeclarePhysicalAttributeReader(); - DeclareImages(); - - code.AddLine("void execute_{}() {{", suffix); - ++code.scope; - + void DecompileBranchMode() { // VM's program counter const auto first_address = ir.GetBasicBlocks().begin()->first; code.AddLine("uint jmp_to = {}U;", first_address); // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems // unlikely that shaders will use 20 nested SSYs and PBKs. - if (!ir.IsFlowStackDisabled()) { - constexpr u32 FLOW_STACK_SIZE = 20; - for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { - code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); - code.AddLine("uint {} = 0U;", FlowStackTopName(stack)); - } + constexpr u32 FLOW_STACK_SIZE = 20; + for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { + code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); + code.AddLine("uint {} = 0u;", FlowStackTopName(stack)); } code.AddLine("while (true) {{"); @@ -392,10 +376,37 @@ public: code.AddLine("default: return;"); code.AddLine("}}"); - for (std::size_t i = 0; i < 2; ++i) { - --code.scope; - code.AddLine("}}"); + --code.scope; + code.AddLine("}}"); + } + + void DecompileAST(); + + void Decompile() { + DeclareVertex(); + DeclareGeometry(); + DeclareRegisters(); + DeclarePredicates(); + DeclareLocalMemory(); + DeclareInternalFlags(); + DeclareInputAttributes(); + DeclareOutputAttributes(); + DeclareConstantBuffers(); + DeclareGlobalMemory(); + DeclareSamplers(); + DeclarePhysicalAttributeReader(); + + code.AddLine("void execute_{}() {{", suffix); + ++code.scope; + + if (ir.IsDecompiled()) { + DecompileAST(); + } else { + DecompileBranchMode(); } + + --code.scope; + code.AddLine("}}"); } std::string GetResult() { @@ -424,6 +435,9 @@ public: } private: + friend class ASTDecompiler; + friend class ExprDecompiler; + void DeclareVertex() { if (!IsVertexShader(stage)) return; @@ -1821,7 +1835,7 @@ private: return {}; } - Expression Exit(Operation operation) { + Expression WriteExit() { if (stage != ProgramType::Fragment) { code.AddLine("return;"); return {}; @@ -1861,6 +1875,10 @@ private: return {}; } + Expression Exit(Operation operation) { + return WriteExit(); + } + Expression Discard(Operation operation) { // Enclose "discard" in a conditional, so that GLSL compilation does not complain // about unexecuted instructions that may follow this. @@ -2253,6 +2271,201 @@ private: ShaderWriter code; }; +const std::string flow_var = "flow_var_"; + +class ExprDecompiler { +public: + ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {} + + void operator()(VideoCommon::Shader::ExprAnd& expr) { + inner += "( "; + std::visit(*this, *expr.operand1); + inner += " && "; + std::visit(*this, *expr.operand2); + inner += ')'; + } + + void operator()(VideoCommon::Shader::ExprOr& expr) { + inner += "( "; + std::visit(*this, *expr.operand1); + inner += " || "; + std::visit(*this, *expr.operand2); + inner += ')'; + } + + void operator()(VideoCommon::Shader::ExprNot& expr) { + inner += '!'; + std::visit(*this, *expr.operand1); + } + + void operator()(VideoCommon::Shader::ExprPredicate& expr) { + auto pred = static_cast(expr.predicate); + inner += decomp.GetPredicate(pred); + } + + void operator()(VideoCommon::Shader::ExprCondCode& expr) { + Node cc = decomp.ir.GetConditionCode(expr.cc); + std::string target; + + if (const auto pred = std::get_if(&*cc)) { + const auto index = pred->GetIndex(); + switch (index) { + case Tegra::Shader::Pred::NeverExecute: + target = "false"; + case Tegra::Shader::Pred::UnusedIndex: + target = "true"; + default: + target = decomp.GetPredicate(index); + } + } else if (const auto flag = std::get_if(&*cc)) { + target = decomp.GetInternalFlag(flag->GetFlag()); + } + inner += target; + } + + void operator()(VideoCommon::Shader::ExprVar& expr) { + inner += flow_var + std::to_string(expr.var_index); + } + + void operator()(VideoCommon::Shader::ExprBoolean& expr) { + inner += expr.value ? "true" : "false"; + } + + std::string& GetResult() { + return inner; + } + +private: + std::string inner{}; + GLSLDecompiler& decomp; +}; + +class ASTDecompiler { +public: + ASTDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {} + + void operator()(VideoCommon::Shader::ASTProgram& ast) { + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + } + + void operator()(VideoCommon::Shader::ASTIfThen& ast) { + ExprDecompiler expr_parser{decomp}; + std::visit(expr_parser, *ast.condition); + decomp.code.AddLine("if ({}) {{", expr_parser.GetResult()); + decomp.code.scope++; + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + decomp.code.scope--; + decomp.code.AddLine("}}"); + } + + void operator()(VideoCommon::Shader::ASTIfElse& ast) { + decomp.code.AddLine("else {{"); + decomp.code.scope++; + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + decomp.code.scope--; + decomp.code.AddLine("}}"); + } + + void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) { + UNREACHABLE(); + } + + void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) { + decomp.VisitBlock(ast.nodes); + } + + void operator()(VideoCommon::Shader::ASTVarSet& ast) { + ExprDecompiler expr_parser{decomp}; + std::visit(expr_parser, *ast.condition); + decomp.code.AddLine("{}{} = {};", flow_var, ast.index, expr_parser.GetResult()); + } + + void operator()(VideoCommon::Shader::ASTLabel& ast) { + decomp.code.AddLine("// Label_{}:", ast.index); + } + + void operator()(VideoCommon::Shader::ASTGoto& ast) { + UNREACHABLE(); + } + + void operator()(VideoCommon::Shader::ASTDoWhile& ast) { + ExprDecompiler expr_parser{decomp}; + std::visit(expr_parser, *ast.condition); + decomp.code.AddLine("do {{"); + decomp.code.scope++; + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + decomp.code.scope--; + decomp.code.AddLine("}} while({});", expr_parser.GetResult()); + } + + void operator()(VideoCommon::Shader::ASTReturn& ast) { + bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition); + if (!is_true) { + ExprDecompiler expr_parser{decomp}; + std::visit(expr_parser, *ast.condition); + decomp.code.AddLine("if ({}) {{", expr_parser.GetResult()); + decomp.code.scope++; + } + if (ast.kills) { + decomp.code.AddLine("discard;"); + } else { + decomp.WriteExit(); + } + if (!is_true) { + decomp.code.scope--; + decomp.code.AddLine("}}"); + } + } + + void operator()(VideoCommon::Shader::ASTBreak& ast) { + bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition); + if (!is_true) { + ExprDecompiler expr_parser{decomp}; + std::visit(expr_parser, *ast.condition); + decomp.code.AddLine("if ({}) {{", expr_parser.GetResult()); + decomp.code.scope++; + } + decomp.code.AddLine("break;"); + if (!is_true) { + decomp.code.scope--; + decomp.code.AddLine("}}"); + } + } + + void Visit(VideoCommon::Shader::ASTNode& node) { + std::visit(*this, *node->GetInnerData()); + } + +private: + GLSLDecompiler& decomp; +}; + +void GLSLDecompiler::DecompileAST() { + u32 num_flow_variables = ir.GetASTNumVariables(); + for (u32 i = 0; i < num_flow_variables; i++) { + code.AddLine("bool {}{} = false;", flow_var, i); + } + ASTDecompiler decompiler{*this}; + VideoCommon::Shader::ASTNode program = ir.GetASTProgram(); + decompiler.Visit(program); +} + } // Anonymous namespace std::string GetCommonDeclarations() { diff --git a/src/video_core/shader/ast.cpp b/src/video_core/shader/ast.cpp index 0bf289f987..68a96cc79e 100644 --- a/src/video_core/shader/ast.cpp +++ b/src/video_core/shader/ast.cpp @@ -372,13 +372,13 @@ ASTManager::~ASTManager() { void ASTManager::Init() { main_node = ASTBase::Make(ASTNode{}); program = std::get_if(main_node->GetInnerData()); - true_condition = MakeExpr(true); + false_condition = MakeExpr(false); } ASTManager::ASTManager(ASTManager&& other) : labels_map(std::move(other.labels_map)), labels_count{other.labels_count}, gotos(std::move(other.gotos)), labels(std::move(other.labels)), variables{other.variables}, - program{other.program}, main_node{other.main_node}, true_condition{other.true_condition} { + program{other.program}, main_node{other.main_node}, false_condition{other.false_condition} { other.main_node.reset(); } @@ -390,7 +390,7 @@ ASTManager& ASTManager::operator=(ASTManager&& other) { variables = other.variables; program = other.program; main_node = other.main_node; - true_condition = other.true_condition; + false_condition = other.false_condition; other.main_node.reset(); return *this; @@ -594,7 +594,7 @@ void ASTManager::MoveOutward(ASTNode goto_node) { u32 var_index = NewVariable(); Expr var_condition = MakeExpr(var_index); ASTNode var_node = ASTBase::Make(parent, var_index, condition); - ASTNode var_node_init = ASTBase::Make(parent, var_index, true_condition); + ASTNode var_node_init = ASTBase::Make(parent, var_index, false_condition); zipper2.InsertBefore(var_node_init, parent); zipper.InsertAfter(var_node, prev); goto_node->SetGotoCondition(var_condition); @@ -605,7 +605,7 @@ void ASTManager::MoveOutward(ASTNode goto_node) { u32 var_index = NewVariable(); Expr var_condition = MakeExpr(var_index); ASTNode var_node = ASTBase::Make(parent, var_index, condition); - ASTNode var_node_init = ASTBase::Make(parent, var_index, true_condition); + ASTNode var_node_init = ASTBase::Make(parent, var_index, false_condition); if (is_if) { zipper2.InsertBefore(var_node_init, parent); } else { diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h index 958989bcd5..06ab20cc52 100644 --- a/src/video_core/shader/ast.h +++ b/src/video_core/shader/ast.h @@ -141,8 +141,6 @@ public: Expr condition; }; -using TransformCallback = std::function; - class ASTBase { public: explicit ASTBase(ASTNode parent, ASTData data) : parent{parent}, data{data} {} @@ -233,11 +231,7 @@ public: return std::holds_alternative(data); } - void TransformBlockEncoded(TransformCallback& callback) { - auto block = std::get_if(&data); - const u32 start = block->start; - const u32 end = block->end; - NodeBlock nodes = callback(start, end); + void TransformBlockEncoded(NodeBlock& nodes) { data = ASTBlockDecoded(nodes); } @@ -309,16 +303,20 @@ public: void SanityCheck(); - bool IsFullyDecompiled() { + bool IsFullyDecompiled() const { return gotos.size() == 0; } - ASTNode GetProgram() { + ASTNode GetProgram() const { return main_node; } void Clear(); + u32 GetVariables() const { + return variables; + } + private: bool IndirectlyRelated(ASTNode first, ASTNode second); @@ -343,7 +341,7 @@ private: u32 variables{}; ASTProgram* program{}; ASTNode main_node{}; - Expr true_condition{}; + Expr false_condition{}; }; } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp index deb3d3ebd9..a299228150 100644 --- a/src/video_core/shader/control_flow.cpp +++ b/src/video_core/shader/control_flow.cpp @@ -425,7 +425,7 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) { } if (cond.predicate != Pred::UnusedIndex) { u32 pred = static_cast(cond.predicate); - bool negate; + bool negate = false; if (pred > 7) { negate = true; pred -= 8; diff --git a/src/video_core/shader/control_flow.h b/src/video_core/shader/control_flow.h index 2805d975ca..347a35dcfe 100644 --- a/src/video_core/shader/control_flow.h +++ b/src/video_core/shader/control_flow.h @@ -74,6 +74,6 @@ struct ShaderCharacteristics { }; std::unique_ptr ScanFlow(const ProgramCode& program_code, u32 program_size, - u32 start_address, ASTManager& manager); + u32 start_address, ASTManager& manager); } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp index 381e874157..e7e0903f67 100644 --- a/src/video_core/shader/decode.cpp +++ b/src/video_core/shader/decode.cpp @@ -35,10 +35,73 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) { } // namespace +class ASTDecoder { +public: + ASTDecoder(ShaderIR& ir) : ir(ir) {} + + void operator()(ASTProgram& ast) { + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + } + + void operator()(ASTIfThen& ast) { + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + } + + void operator()(ASTIfElse& ast) { + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + } + + void operator()(ASTBlockEncoded& ast) {} + + void operator()(ASTBlockDecoded& ast) {} + + void operator()(ASTVarSet& ast) {} + + void operator()(ASTLabel& ast) {} + + void operator()(ASTGoto& ast) {} + + void operator()(ASTDoWhile& ast) { + ASTNode current = ast.nodes.GetFirst(); + while (current) { + Visit(current); + current = current->GetNext(); + } + } + + void operator()(ASTReturn& ast) {} + + void operator()(ASTBreak& ast) {} + + void Visit(ASTNode& node) { + std::visit(*this, *node->GetInnerData()); + if (node->IsBlockEncoded()) { + auto block = std::get_if(node->GetInnerData()); + NodeBlock bb = ir.DecodeRange(block->start, block->end); + node->TransformBlockEncoded(bb); + } + } + +private: + ShaderIR& ir; +}; + void ShaderIR::Decode() { std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); - disable_flow_stack = false; + decompiled = false; const auto info = ScanFlow(program_code, program_size, main_offset, program_manager); if (info) { @@ -46,7 +109,10 @@ void ShaderIR::Decode() { coverage_begin = shader_info.start; coverage_end = shader_info.end; if (shader_info.decompiled) { - disable_flow_stack = true; + decompiled = true; + ASTDecoder decoder{*this}; + ASTNode program = GetASTProgram(); + decoder.Visit(program); return; } LOG_WARNING(HW_GPU, "Flow Stack Removing Failed! Falling back to old method"); diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp index d46e0f8232..6f678003c8 100644 --- a/src/video_core/shader/decode/other.cpp +++ b/src/video_core/shader/decode/other.cpp @@ -157,7 +157,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, "Constant buffer flow is not supported"); - if (disable_flow_stack) { + if (decompiled) { break; } @@ -171,7 +171,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, "Constant buffer PBK is not supported"); - if (disable_flow_stack) { + if (decompiled) { break; } @@ -186,7 +186,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}", static_cast(cc)); - if (disable_flow_stack) { + if (decompiled) { break; } @@ -198,7 +198,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}", static_cast(cc)); - if (disable_flow_stack) { + if (decompiled) { break; } diff --git a/src/video_core/shader/expr.cpp b/src/video_core/shader/expr.cpp index ebce6339bd..ca633ffb10 100644 --- a/src/video_core/shader/expr.cpp +++ b/src/video_core/shader/expr.cpp @@ -72,4 +72,11 @@ bool ExprAreOpposite(Expr first, Expr second) { return false; } +bool ExprIsTrue(Expr first) { + if (ExprIsBoolean(first)) { + return ExprBooleanGet(first); + } + return false; +} + } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/expr.h b/src/video_core/shader/expr.h index f012f6fcff..b954cffb03 100644 --- a/src/video_core/shader/expr.h +++ b/src/video_core/shader/expr.h @@ -115,4 +115,6 @@ Expr MakeExprAnd(Expr first, Expr second); Expr MakeExprOr(Expr first, Expr second); +bool ExprIsTrue(Expr first); + } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp index c79f80e04d..004b1e16f9 100644 --- a/src/video_core/shader/shader_ir.cpp +++ b/src/video_core/shader/shader_ir.cpp @@ -137,7 +137,7 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff return MakeNode(index, static_cast(element), std::move(buffer)); } -Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) { +Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) const { const Node node = MakeNode(flag); if (negated) { return Operation(OperationCode::LogicalNegate, node); @@ -367,13 +367,13 @@ OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) { return op->second; } -Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) { +Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) const { switch (cc) { case Tegra::Shader::ConditionCode::NEU: return GetInternalFlag(InternalFlag::Zero, true); default: UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast(cc)); - return GetPredicate(static_cast(Pred::NeverExecute)); + return MakeNode(Pred::NeverExecute, false); } } diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index a91cd7d67b..48c7b722ee 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -15,8 +15,8 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/shader_bytecode.h" #include "video_core/engines/shader_header.h" -#include "video_core/shader/node.h" #include "video_core/shader/ast.h" +#include "video_core/shader/node.h" namespace VideoCommon::Shader { @@ -141,15 +141,27 @@ public: return header; } - bool IsFlowStackDisabled() const { - return disable_flow_stack; + bool IsDecompiled() const { + return decompiled; + } + + ASTNode GetASTProgram() const { + return program_manager.GetProgram(); + } + + u32 GetASTNumVariables() const { + return program_manager.GetVariables(); } u32 ConvertAddressToNvidiaSpace(const u32 address) const { return (address - main_offset) * sizeof(Tegra::Shader::Instruction); } + /// Returns a condition code evaluated from internal flags + Node GetConditionCode(Tegra::Shader::ConditionCode cc) const; + private: + friend class ASTDecoder; void Decode(); NodeBlock DecodeRange(u32 begin, u32 end); @@ -214,7 +226,7 @@ private: /// Generates a node representing an output attribute. Keeps track of used attributes. Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer); /// Generates a node representing an internal flag - Node GetInternalFlag(InternalFlag flag, bool negated = false); + Node GetInternalFlag(InternalFlag flag, bool negated = false) const; /// Generates a node representing a local memory address Node GetLocalMemory(Node address); /// Generates a node representing a shared memory address @@ -272,9 +284,6 @@ private: /// Returns a predicate combiner operation OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation); - /// Returns a condition code evaluated from internal flags - Node GetConditionCode(Tegra::Shader::ConditionCode cc); - /// Accesses a texture sampler const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler, Tegra::Shader::TextureType type, bool is_array, bool is_shadow); @@ -358,7 +367,7 @@ private: const ProgramCode& program_code; const u32 main_offset; const std::size_t program_size; - bool disable_flow_stack{}; + bool decompiled{}; u32 coverage_begin{}; u32 coverage_end{};